前一段时间在做项目的时候遇到了一个问题,美工在设计的时候设计的是一个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();
- }
- }
- <?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" />
- 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);
- }
- }
- }
- }
- <?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>