zoukankan      html  css  js  c++  java
  • 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>
    运行结果:







  • 相关阅读:
    chrome书签插件
    Js箭头函数和lambda
    CSS水平或垂直居中技巧
    前端需要注意的SEO优化
    OpenCV图像识别初探-50行代码教机器玩2D游戏
    机器学习笔记(十一)----降维
    基于Docker搭建分布式消息队列Kafka
    一个经典面试题:如何保证缓存与数据库的双写一致性?
    Flask 蓝图机制及应用
    软件开发团队如何管理琐碎、突发性任务
  • 原文地址:https://www.cnblogs.com/lanzhi/p/6469224.html
Copyright © 2011-2022 走看看