android仿ios开关按钮

时间:2022-04-24
本文章向大家介绍android仿ios开关按钮,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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

运行结果: