Android中实现iPhone开关
前一段时间在做项目的时候遇到了一个问题,美工在设计的时候设计的是一个iPhone中的开关,但是都知道Android中的Switch开关和IOS中的不同,这样就需要通过动画来实现一个iPhone开关了。
通常我们设置界面采用的是PreferenceActivity
package me.imid.movablecheckbox; import android.os.Bundle;
import android.preference.PreferenceActivity; public class MovableCheckboxActivity extends PreferenceActivity { @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.testpreference);
}
}
有关PreferenceActivity请看:http://blog.csdn.net/dawanganban/article/details/19082949
我们的基本思路是将CheckBox自定义成我们想要的样子,然后再重写CheckBoxPreference将自定义的CheckBox载入。
1、重写CheckBox
package me.imid.view; import me.imid.movablecheckbox.R; import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.widget.CheckBox; public class SwitchButton extends CheckBox {
private Paint mPaint; private ViewParent mParent; private Bitmap mBottom; private Bitmap mCurBtnPic; private Bitmap mBtnPressed; private Bitmap mBtnNormal; private Bitmap mFrame; private Bitmap mMask; private RectF mSaveLayerRectF; private PorterDuffXfermode mXfermode; private float mFirstDownY; // 首次按下的Y private float mFirstDownX; // 首次按下的X private float mRealPos; // 图片的绘制位置 private float mBtnPos; // 按钮的位置 private float mBtnOnPos; // 开关打开的位置 private float mBtnOffPos; // 开关关闭的位置 private float mMaskWidth; private float mMaskHeight; private float mBtnWidth; private float mBtnInitPos; private int mClickTimeout; private int mTouchSlop; private final int MAX_ALPHA = 255; private int mAlpha = MAX_ALPHA; private boolean mChecked = false; private boolean mBroadcasting; private boolean mTurningOn; private PerformClick mPerformClick; private OnCheckedChangeListener mOnCheckedChangeListener; private OnCheckedChangeListener mOnCheckedChangeWidgetListener; private boolean mAnimating; private final float VELOCITY = 350; private float mVelocity; private final float EXTENDED_OFFSET_Y = 15; private float mExtendOffsetY; // Y轴方向扩大的区域,增大点击区域 private float mAnimationPosition; private float mAnimatedVelocity; public SwitchButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.checkboxStyle);
} public SwitchButton(Context context) {
this(context, null);
} public SwitchButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
} private void initView(Context context) {
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
Resources resources = context.getResources(); // get viewConfiguration
mClickTimeout = ViewConfiguration.getPressedStateDuration()
+ ViewConfiguration.getTapTimeout();
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); // get Bitmap
mBottom = BitmapFactory.decodeResource(resources, R.drawable.bottom);
mBtnPressed = BitmapFactory.decodeResource(resources, R.drawable.btn_pressed);
mBtnNormal = BitmapFactory.decodeResource(resources, R.drawable.btn_unpressed);
mFrame = BitmapFactory.decodeResource(resources, R.drawable.frame);
mMask = BitmapFactory.decodeResource(resources, R.drawable.mask);
mCurBtnPic = mBtnNormal; mBtnWidth = mBtnPressed.getWidth();
mMaskWidth = mMask.getWidth();
mMaskHeight = mMask.getHeight(); mBtnOffPos = mBtnWidth / 2;
mBtnOnPos = mMaskWidth - mBtnWidth / 2; mBtnPos = mChecked ? mBtnOnPos : mBtnOffPos;
mRealPos = getRealPos(mBtnPos); final float density = getResources().getDisplayMetrics().density;
mVelocity = (int) (VELOCITY * density + 0.5f);
mExtendOffsetY = (int) (EXTENDED_OFFSET_Y * density + 0.5f); mSaveLayerRectF = new RectF(0, mExtendOffsetY, mMask.getWidth(), mMask.getHeight()
+ mExtendOffsetY);
mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
} @Override
public void setEnabled(boolean enabled) {
mAlpha = enabled ? MAX_ALPHA : MAX_ALPHA / 2;
super.setEnabled(enabled);
} public boolean isChecked() {
return mChecked;
} public void toggle() {
setChecked(!mChecked);
} /**
* 内部调用此方法设置checked状态,此方法会延迟执行各种回调函数,保证动画的流畅度
*
* @param checked
*/
private void setCheckedDelayed(final boolean checked) {
this.postDelayed(new Runnable() { @Override
public void run() {
setChecked(checked);
}
}, 10);
} /**
* <p>
* Changes the checked state of this button.
* </p>
*
* @param checked true to check the button, false to uncheck it
*/
public void setChecked(boolean checked) { if (mChecked != checked) {
mChecked = checked; mBtnPos = checked ? mBtnOnPos : mBtnOffPos;
mRealPos = getRealPos(mBtnPos);
invalidate(); // Avoid infinite recursions if setChecked() is called from a
// listener
if (mBroadcasting) {
return;
} mBroadcasting = true;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(SwitchButton.this, mChecked);
}
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(SwitchButton.this, mChecked);
} mBroadcasting = false;
}
} /**
* Register a callback to be invoked when the checked state of this button
* changes.
*
* @param listener the callback to call on checked state change
*/
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
} /**
* Register a callback to be invoked when the checked state of this button
* changes. This callback is used for internal purpose only.
*
* @param listener the callback to call on checked state change
* @hide
*/
void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
mOnCheckedChangeWidgetListener = listener;
} @Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
float x = event.getX();
float y = event.getY();
float deltaX = Math.abs(x - mFirstDownX);
float deltaY = Math.abs(y - mFirstDownY);
switch (action) {
case MotionEvent.ACTION_DOWN:
attemptClaimDrag();
mFirstDownX = x;
mFirstDownY = y;
mCurBtnPic = mBtnPressed;
mBtnInitPos = mChecked ? mBtnOnPos : mBtnOffPos;
break;
case MotionEvent.ACTION_MOVE:
float time = event.getEventTime() - event.getDownTime();
mBtnPos = mBtnInitPos + event.getX() - mFirstDownX;
if (mBtnPos >= mBtnOffPos) {
mBtnPos = mBtnOffPos;
}
if (mBtnPos <= mBtnOnPos) {
mBtnPos = mBtnOnPos;
}
mTurningOn = mBtnPos > (mBtnOffPos - mBtnOnPos) / 2 + mBtnOnPos; mRealPos = getRealPos(mBtnPos);
break;
case MotionEvent.ACTION_UP:
mCurBtnPic = mBtnNormal;
time = event.getEventTime() - event.getDownTime();
if (deltaY < mTouchSlop && deltaX < mTouchSlop && time < mClickTimeout) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
} else {
startAnimation(!mTurningOn);
}
break;
} invalidate();
return isEnabled();
} private final class PerformClick implements Runnable {
public void run() {
performClick();
}
} @Override
public boolean performClick() {
startAnimation(!mChecked);
return true;
} /**
* Tries to claim the user's drag motion, and requests disallowing any
* ancestors from stealing events in the drag.
*/
private void attemptClaimDrag() {
mParent = getParent();
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(true);
}
} /**
* 将btnPos转换成RealPos
*
* @param btnPos
* @return
*/
private float getRealPos(float btnPos) {
return btnPos - mBtnWidth / 2;
} @Override
protected void onDraw(Canvas canvas) {
canvas.saveLayerAlpha(mSaveLayerRectF, mAlpha, Canvas.MATRIX_SAVE_FLAG
| Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
| Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
// 绘制蒙板
canvas.drawBitmap(mMask, 0, mExtendOffsetY, mPaint);
mPaint.setXfermode(mXfermode); // 绘制底部图片
canvas.drawBitmap(mBottom, mRealPos, mExtendOffsetY, mPaint);
mPaint.setXfermode(null);
// 绘制边框
canvas.drawBitmap(mFrame, 0, mExtendOffsetY, mPaint); // 绘制按钮
canvas.drawBitmap(mCurBtnPic, mRealPos, mExtendOffsetY, mPaint);
canvas.restore();
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension((int) mMaskWidth, (int) (mMaskHeight + 2 * mExtendOffsetY));
} private void startAnimation(boolean turnOn) {
mAnimating = true;
mAnimatedVelocity = turnOn ? -mVelocity : mVelocity;
mAnimationPosition = mBtnPos; new SwitchAnimation().run();
} private void stopAnimation() {
mAnimating = false;
} private final class SwitchAnimation implements Runnable { @Override
public void run() {
if (!mAnimating) {
return;
}
doAnimation();
FrameAnimationController.requestAnimationFrame(this);
}
} private void doAnimation() {
mAnimationPosition += mAnimatedVelocity * FrameAnimationController.ANIMATION_FRAME_DURATION
/ 1000;
if (mAnimationPosition <= mBtnOnPos) {
stopAnimation();
mAnimationPosition = mBtnOnPos;
setCheckedDelayed(true);
} else if (mAnimationPosition >= mBtnOffPos) {
stopAnimation();
mAnimationPosition = mBtnOffPos;
setCheckedDelayed(false);
}
moveView(mAnimationPosition);
} private void moveView(float position) {
mBtnPos = position;
mRealPos = getRealPos(mBtnPos);
invalidate();
}
}
2、新建一个布局文件preference_widget_checkbox.xml
<?xml version="1.0" encoding="utf-8"?>
<me.imid.view.SwitchButton xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center" />
3、重写CheckBoxPreference并通过Inflater加载布局文件,同时屏蔽原有点击事件
package me.imid.preference; import me.imid.movablecheckbox.R;
import me.imid.view.SwitchButton; import android.app.Service;
import android.content.Context;
import android.preference.PreferenceActivity;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Checkable;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView; public class CheckBoxPreference extends android.preference.CheckBoxPreference {
private Context mContext;
private int mLayoutResId = R.layout.preference;
private int mWidgetLayoutResId = R.layout.preference_widget_checkbox; private boolean mShouldDisableView = true; private CharSequence mSummaryOn;
private CharSequence mSummaryOff; private boolean mSendAccessibilityEventViewClickedType; private AccessibilityManager mAccessibilityManager; public CheckBoxPreference(Context context, AttributeSet attrset,
int defStyle) {
super(context, attrset);
mContext = context;
mSummaryOn = getSummaryOn();
mSummaryOff = getSummaryOff();
mAccessibilityManager = (AccessibilityManager) mContext
.getSystemService(Service.ACCESSIBILITY_SERVICE);
} public CheckBoxPreference(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.checkBoxPreferenceStyle);
} public CheckBoxPreference(Context context) {
this(context, null);
} /**
* Creates the View to be shown for this Preference in the
* {@link PreferenceActivity}. The default behavior is to inflate the main
* layout of this Preference (see {@link #setLayoutResource(int)}. If
* changing this behavior, please specify a {@link ViewGroup} with ID
* {@link android.R.id#widget_frame}.
* <p>
* Make sure to call through to the superclass's implementation.
*
* @param parent
* The parent that this View will eventually be attached to.
* @return The View that displays this Preference.
* @see #onBindView(View)
*/
protected View onCreateView(ViewGroup parent) {
final LayoutInflater layoutInflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); final View layout = layoutInflater.inflate(mLayoutResId, parent, false); if (mWidgetLayoutResId != 0) {
final ViewGroup widgetFrame = (ViewGroup) layout
.findViewById(R.id.widget_frame);
layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
}
return layout;
} @Override
protected void onBindView(View view) {
// 屏蔽item点击事件
view.setClickable(false); TextView textView = (TextView) view.findViewById(R.id.title);
if (textView != null) {
textView.setText(getTitle());
} textView = (TextView) view.findViewById(R.id.summary);
if (textView != null) {
final CharSequence summary = getSummary();
if (!TextUtils.isEmpty(summary)) {
if (textView.getVisibility() != View.VISIBLE) {
textView.setVisibility(View.VISIBLE);
} textView.setText(getSummary());
} else {
if (textView.getVisibility() != View.GONE) {
textView.setVisibility(View.GONE);
}
}
} if (mShouldDisableView) {
setEnabledStateOnViews(view, isEnabled());
} View checkboxView = view.findViewById(R.id.checkbox);
if (checkboxView != null && checkboxView instanceof Checkable) {
((Checkable) checkboxView).setChecked(isChecked());
SwitchButton switchButton = (SwitchButton) checkboxView;
switchButton
.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
// TODO Auto-generated method stub
mSendAccessibilityEventViewClickedType = true;
if (!callChangeListener(isChecked)) {
return;
}
setChecked(isChecked);
}
});
// send an event to announce the value change of the CheckBox and is
// done here
// because clicking a preference does not immediately change the
// checked state
// for example when enabling the WiFi
if (mSendAccessibilityEventViewClickedType
&& mAccessibilityManager.isEnabled()
&& checkboxView.isEnabled()) {
mSendAccessibilityEventViewClickedType = false; int eventType = AccessibilityEvent.TYPE_VIEW_CLICKED;
checkboxView.sendAccessibilityEventUnchecked(AccessibilityEvent
.obtain(eventType));
}
} // Sync the summary view
TextView summaryView = (TextView) view.findViewById(R.id.summary);
if (summaryView != null) {
boolean useDefaultSummary = true;
if (isChecked() && mSummaryOn != null) {
summaryView.setText(mSummaryOn);
useDefaultSummary = false;
} else if (!isChecked() && mSummaryOff != null) {
summaryView.setText(mSummaryOff);
useDefaultSummary = false;
} if (useDefaultSummary) {
final CharSequence summary = getSummary();
if (summary != null) {
summaryView.setText(summary);
useDefaultSummary = false;
}
} int newVisibility = View.GONE;
if (!useDefaultSummary) {
// Someone has written to it
newVisibility = View.VISIBLE;
}
if (newVisibility != summaryView.getVisibility()) {
summaryView.setVisibility(newVisibility);
}
}
} /**
* Makes sure the view (and any children) get the enabled state changed.
*/
private void setEnabledStateOnViews(View v, boolean enabled) {
v.setEnabled(enabled); if (v instanceof ViewGroup) {
final ViewGroup vg = (ViewGroup) v;
for (int i = vg.getChildCount() - 1; i >= 0; i--) {
setEnabledStateOnViews(vg.getChildAt(i), enabled);
}
}
} }
4、在res/xml下新建选项设置布局文件
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <me.imid.preference.CheckBoxPreference
android:defaultValue="true"
android:enabled="false"
android:summary="summary"
android:title="MyCheckbox(disabled)" />
<me.imid.preference.CheckBoxPreference
android:defaultValue="true"
android:dependency="checkbox"
android:summaryOff="off"
android:summaryOn="on"
android:title="MyCheckbox(enabled)" />
<me.imid.preference.CheckBoxPreference
android:defaultValue="false"
android:key="checkbox"
android:summaryOff="off"
android:summaryOn="on"
android:title="MyCheckbox(enabled)" /> <CheckBoxPreference
android:defaultValue="true"
android:enabled="false"
android:summaryOff="off"
android:summaryOn="on"
android:title="defalt checkbox(disabled)" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="checkbox1"
android:summaryOff="off"
android:summaryOn="on"
android:title="defalt checkbox(enabled)" />
<CheckBoxPreference
android:defaultValue="false"
android:key="checkbox1"
android:summaryOff="off"
android:summaryOn="on"
android:title="defalt checkbox(enabled)" /> </PreferenceScreen>
运行结果:
Android中实现iPhone开关的更多相关文章
- Android中的自定义控件(二)
案例四: 自定义开关 功能介绍:本案例实现的功能是创建一个自定义的开关,可以自行决定开关的背景.当滑动开关时,开关的滑块可跟随手指移动.当手指松开后,滑块根据开关的状态,滑到最右边或者滑到 ...
- Android 中常见控件的介绍和使用
1 TextView文本框 1.1 TextView类的结构 TextView 是用于显示字符串的组件,对于用户来说就是屏幕中一块用于显示文本的区域.TextView类的层次关系如下: java.la ...
- Android自定义控件7--自定义开关--绘制界面内容
本文实现全自定义控件--自定义开关 本文地址:http://www.cnblogs.com/wuyudong/p/5922316.html,转载请注明源地址. 自定义开关 (View),本文完成下面内 ...
- Android中SQLite应用详解
上次我向大家介绍了SQLite的基本信息和使用过程,相信朋友们对SQLite已经有所了解了,那今天呢,我就和大家分享一下在Android中如何使用SQLite. 现在的主流移动设备像Android.i ...
- Android中如何监听GPS开启和关闭
转自 chenming 原文 Android中如何监听GPS开启和关闭 摘要: 本文简单总结了如何监听GPS开关的小技巧 有时需要监听GPS的开关(这种需求并不多见).实现的思路是监听代表 GPS ...
- Android笔记——Android中数据的存储方式(三)
Android系统集成了一个轻量级的数据库:SQLite,所以Android对数据库的支持很好,每个应用都可以方便的使用它.SQLite作为一个嵌入式的数据库引擎,专门适用于资源有限的设备上适量数据存 ...
- Android中SQLite应用详解(转)
上次我向大家介绍了SQLite的基本信息和使用过程,相信朋友们对SQLite已经有所了解了,那今天呢,我就和大家分享一下在Android中如何使用SQLite. 现在的主流移动设备像Android.i ...
- Android中AppWidget的分析与应用:AppWidgetProvider .
from: http://blog.csdn.net/thl789/article/details/7887968 本文从开发AppWidgetProvider角度出发,看一个AppWidgetPrv ...
- 【转】Android菜单详解——理解android中的Menu--不错
原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...
随机推荐
- bitmap2drawable-两者的转化实现
先来看今天遇到的一个问题,是关于mms报错的.后来发现报的地方如下 Bitmap deleteBitMap = ((BitmapDrawable)mChipDelete).getBitmap(); D ...
- mysql中配置ssl_key、ssl-cert、ssl-ca的路径及建立ssl连接(适用于5.7以下版本,5.7及以上请看本文末尾的备注)
1.创建 CA 私钥和 CA 证书 (1)下载并安装openssl,将bin目录配置到环境变量: (2)设置openssl.cfg路径(若不设置会报错,找不到openssl配置文件) \bin\ope ...
- Java Web学习总结(7)——HttpServletRequest对象
一.HttpServletRequest介绍 HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象 ...
- 【iOS开发系列】颜色渐变
记录: //Transparent Gradient Layer - (void) insertTransparentGradient { UIColor *colorOne = [UIColor c ...
- 接口如何使用(以笑话大全api为例)
接口如何使用(以笑话大全api为例) 一.总结 一句话总结:直接用ajax,或者post,get方式向接口网址请求数据,然后接收网站传过来的数据就好,和我们写网站的时候前台向后台请求数据的方式一样. ...
- 【Codeforces Round #442 (Div. 2) A】Alex and broken contest
[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 注意是所有的名字里面,只出现了其中某一个名字一次. [代码] #include <bits/stdc++.h> usin ...
- Spring MVC学习总结(1)——Spring MVC单元测试
关于spring MVC单元测试常规的方法则是启动WEB服务器,测试出错 ,停掉WEB 改代码,重启WEB,测试,大量的时间都浪费在WEB服务器的启动上,下面介绍个实用的方法,spring MVC单元 ...
- Java Scheduler ScheduledExecutorService ScheduledThreadPoolExecutor Example(ScheduledThreadPoolExecutor例子——了解如何创建一个周期任务)
Welcome to the Java Scheduler Example. Today we will look into ScheduledExecutorService and it's imp ...
- shiro 静态页面资源不显示 解决方案(转)
最近做一个ssm+shiro的框架整和 不加shiro之前ssm中css和图片显示正常.加上以后无法显示. 解决方案: shiro有静态资源过滤. 配置资源匿名访问即可 <property na ...
- Topological Spaces(拓扑空间)
拓扑空间的定义有多种形式,通过 open sets(开集)的形式定义是最为常见的拓扑空间定义形式. 1. 通过开集(open sets)定义 拓扑空间由一个有序对 (X,τ) 表示,X 表示非空集合, ...