Some time ago, when I was working on a project, I encountered a problem. The Artist designed a Switch in the iPhone during the design, but we all know that the Switch in Android is different from the Switch in IOS, in this way, an iPhone switch needs to be implemented through animation.
Generally, we set the interface to 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); }}
For PreferenceActivity see: http://blog.csdn.net/dawanganban/article/details/19082949
Our basic idea is to customize the CheckBox as we want, and then rewrite the CheckBoxPreference to load the custom CheckBox.
1. Rewrite 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. porterduxfermode; import android. graphics. rectF; import android Oid. 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 porterduxfermode mXfermode; private float mFirstDownY; // Y private float mFirstDownX pressed for the first time; // X private float mRealPos pressed for the first time; // The Position of the image to be drawn is private float mBtnPos; // The Position of the button is private float mBtnOnPos; // the position of the switch is private float mBtnOffPos; // the position of the switch is 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 Xiaoming mclick mPerformClick; private OnCheckedChangeListener mOnCheckedChangeListener; private OnCheckedChangeListener mOnCheckedChangeWidgetListener; private boolean mAnimating; private final f Loat VELOCITY = 350; private float mVelocity; private final float capacity = 15; private float mExtendOffsetY; // expand the area in the Y axis to the 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 SwitchButto N (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. dra Wable. 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) (density * density + 0.5f); priority = new RectF (0, mExtendOffsetY, mMask. getWidth (), mMask. getHeight () + mExtendOffsetY); mXfermode = new porterduxfermode (PorterDuff. mode. SRC_IN) ;}@ Overr Ide 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);}/*** call this method internally to set the checked status. This method delays the execution of various callback functions, ensure smoothness of the animation ** @ param checked */private void setCheckedDelayed (final boolean checked) {this. postDelayed (new Runnable () {@ Override public void run () {setChecked (checked) ;}, 10 );}/**** Changes the checked state of this button .*
** @ 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;}/*** Registe R 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 listener (OnCheckedChangeListener listener) {mOnCheckedChangeWidgetListener = listener;} @ Override public boolean onTouchEvent (MotionEvent event) {int action = event. ge TAction (); 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. ACTIO N_UP: mCurBtnPic = mBtnNormal; time = event. getEventTime ()-event. getDownTime (); if (deltaY <mTouchSlop & deltaX <mTouchSlop & time <mClickTimeout) {if (mPerformClick = null) {mPerformClick = new multiple mclick ();} if (! Post (mPerformClick) {initialize mclick () ;}} else {startAnimation (! MTurningOn);} break;} invalidate (); return isEnabled ();} private final class implements mclick implements Runnable {public void run () {extends mclick ();}} @ Override public boolean initialize mclick () {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) ;}/ *** convert btnPos to 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_SAV E_FLAG | Canvas. CLIP_TO_LAYER_SAVE_FLAG); // draw the canvas. drawBitmap (mMask, 0, mExtendOffsetY, mPaint); mPaint. setXfermode (mXfermode); // draw the canvas of the bottom image. drawBitmap (mBottom, mRealPos, mExtendOffsetY, mPaint); mPaint. setXfermode (null); // draw the border canvas. drawBitmap (mFrame, 0, mExtendOffsetY, mPaint); // draw the canvas button. drawBitmap (mCurBtnPic, mRealPos, mExtendOffsetY, mPaint); canvas. restore () ;}@ Override pro Tected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {trim (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. increment/1000; if (mAnimationPosition <= mBtnOnPos) {stopAnimation (); mAnimationPosition = mBtnOnPos; increment (true);} else if (mAnimationPosition> = mBtnOffPos) {stopAnimation (); mAnimationPosition = mBtnOffPos; setCheckedDelayed (false);} moveView (mAnimationPosition);} private void moveView (float position) {mBtnPos = position; mRealPos = getRealPos (mBtnPos ); invalidate ();}}
2. Create a layout file preference_widget_checkbox.xml.
3. Rewrite CheckBoxPreference and load the layout file through Inflater, while shielding the original Click Event
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. response; private boolean response = true; private CharSequence mSummaryOn; private CharSequence mSummaryOff; private boolean response; 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 }. ** Make sure to call through to the superclass's implementation. ** @ param parent * The parent that this View will eventually be attached. * @ 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 = l AyoutInflater. inflate (mLayoutResId, parent, false); if (mWidgetLayoutResId! = 0) {final ViewGroup widgetFrame = (ViewGroup) layout. findViewById (R. id. widget_frame); layoutInflater. inflate (mWidgetLayoutResId, widgetFrame);} return layout;} @ Overrideprotected void onBindView (View view) {// block the view of the item click event. 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 enabled = 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 WiFiif (mSendAccessibilityEventViewClickedType & mAccessibilityManager. isEnabled () & checkboxView. isEnabled () {mSendAccessibilityEventViewCli CkedType = false; int eventType = AccessibilityEvent. TYPE_VIEW_CLICKED; checkboxView. sendAccessibilityEventUnchecked (AccessibilityEvent. obtain (eventType); }}// Sync the summary viewTextView 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 itnewVisibility = 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. Create a new option under res/xml to set the layout File
Running result: