自定义可滑动、可点击的开关

来源:互联网 发布:诈骗罪立案后网络追逃 编辑:程序博客网 时间:2024/05/16 09:26

单击:动画效果改变开关的状态
滑动:根据拖动距离设置颜色渐变,拖动距离小于某一值返回原状态,否则返回另一状态最下面的绿色开关,没有动态图
主要步骤:
1onMeasure()
defaultWidth=200dp,defaultHeight=defaultWidth*0.55

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));    }    //设置宽度private int measureWidth(int widthMeasureSpec) {        int resutltWidth = defaultWidth;        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        //如果模式是match_parent或者给定宽度值(例如100dp)        if (widthMode == MeasureSpec.EXACTLY) {            resutltWidth = widthSize;        } else {        //如果模式是wrap_content,宽度取为计算值和默认值的最小值            if (widthMode == MeasureSpec.AT_MOST) {                resutltWidth = Math.min(resutltWidth, widthSize);            }        }        return resutltWidth;    }//高度与宽度测量方式一样private int measureHeight(int heightMeasureSpec) {        int resutltHeight = (int) (defaultWidth * DEFAULT_WIDTH_HEIGHT_PERCENT);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        if (heightMode == MeasureSpec.EXACTLY) {            resutltHeight = heightSize;        } else {            if (heightMode == MeasureSpec.AT_MOST) {                resutltHeight = Math.min(resutltHeight, heightSize);            }        }        return resutltHeight;    }

2onDraw()

计算得到图形的Path @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        //设置padding属性,否则在XML文件中Padding属性不生效        final int paddingTop = getPaddingTop();        final int paddingBottom = getPaddingBottom();        final int paddingRight = getPaddingRight();        final int paddingLeft = getPaddingLeft();        //根据padding计算得到的控件宽高        mWidth = w - paddingLeft - paddingRight;        mHeight = h - paddingBottom - paddingTop;        //左边X坐标        mLeftX = paddingLeft;        //右边X坐标        mRightX = paddingLeft + mWidth;        float top = paddingTop;        float left = paddingLeft;        float right = left + mWidth;        float bottom = top + mHeight;        //白色圆点需要移动的最大距离        mTransitionLength = mWidth - mHeight;        RectF backgroundRecf = new RectF(left, top, left + mHeight, bottom);        backgroundPath = new Path();        //左边圆弧        backgroundPath.arcTo(backgroundRecf, 90, 180);        //右边圆弧        backgroundRecf.left = right - mHeight;        backgroundRecf.right = right;        backgroundRecf.top = top;        backgroundRecf.bottom = bottom;        backgroundPath.arcTo(backgroundRecf, 270, 180);        //画圆形        float radius = (mHeight / 2) * 0.95f;        mCenterX = (left + left + mHeight) / 2;        mCenterY = (top + bottom) / 2;        RectF circleRectF = new RectF(mCenterX - radius, mCenterY - radius, mCenterX + radius, mCenterY + radius);        circlePath = new Path();        circlePath.arcTo(circleRectF, 90, 180);        circlePath.arcTo(circleRectF, 270, 180);    }//画椭圆背景,根据是否滑动设置不同的画笔//单击mCurrentColor根据动画改变//滑动渐变 nCurrentColor根据手指拖动距离计算private void drawBackground(Canvas canvas, boolean isScrolled) {        if (isScrolled) {            mPaint.setColor(nCurrentColor);        } else {            mPaint.setColor(mCurrentColor);        }        mPaint.setStyle(Paint.Style.FILL);        canvas.drawPath(backgroundPath, mPaint);        mPaint.reset();    }//画白色圆形private void drawForeground(Canvas canvas) {//保存画布canvas.save();//移动画布,移动的距离即为小球移动的距离,同样分单击和滑动两种情况        canvas.translate(getForegroundTransitionValue(isScrolled), 0);        mPaint.setColor(spotColor);        mPaint.setStyle(Paint.Style.FILL);        canvas.drawPath(circlePath, mPaint);        mPaint.reset();        canvas.restore();    }//计算画布移动距离private float getForegroundTransitionValue(boolean hasSlide) {        float result = 0;        //单击        if (!hasSlide) {        //开关打开            if (isOpen) {                if (mIsDuringAnimation) {                    result = mAnimationFraction * mTransitionLength;} else                    result = mTransitionLength;            }//开关关闭             else {                if (mIsDuringAnimation) {                    result = mAnimationFraction * mTransitionLength;                } else {                    result = 0;                }            }        } else {            //滑动时偏移的距离,mTransitionDiatance根据手指移动距离计算            result = isOpen ? mWidth - mHeight + mTransitionDiatance : mTransitionDiatance;        }        return result;    }

3onTouchEven@Override

public boolean onTouchEvent(MotionEvent event) {        int x = (int) event.getX();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                lastX = x;                return true;            case MotionEvent.ACTION_CANCEL:                break;            case MotionEvent.ACTION_MOVE:                currentX = (int) event.getX();                //手指偏移距离,大于5判断为滑动,否则为单击                int offSetX = currentX - lastX;                if (Math.abs(offSetX) > 5) {                    isScrolled = true;                }                //开关关闭左滑,开关打开右滑,设置无效果                if ((!isOpen && offSetX < 0) || (isOpen && offSetX > 0)) {                    offSetX = 0;                }                //超出边界后设置为最大滑动距离                if (Math.abs(offSetX) > mTransitionLength) {                    offSetX = (int) (offSetX > 0 ? mTransitionLength : -mTransitionLength);                }           mTransitionDiatance = offSetX;                if (isScrolled) {//滑动过程中背景变换                    backgroundByDistance(mTransitionDiatance, isOpen);                }                break;                        case MotionEvent.ACTION_UP:                //没有滑动 是一次单击过程 使用动画改变开关效果                if (isScrolled == false) {                    if (mIsDuringAnimation) {                        return true;                    }                    if (isOpen) {                        startCloseAnimation();                        isOpen = false;                    } else {                        startOpenAnimation();                        isOpen = true;                    }                } else {                    //滑动之后的操作                    //滑动距离小于1/2总长度 退回到之前位置 否则自动靠近                    if (!isOpen) {                        Log.d(TAG, "mTransitionDiatance=" + mTransitionDiatance);                        //1/2无效 四舍五入后为0                    if (mTransitionDiatance < (0.5 * mTransitionLength)) {                            //是否可以考虑动画实现??弹性滑动                            mTransitionDiatance = 0;                            isOpen = false;                            invalidate();                            isScrolled = false;                        } else {                            mTransitionDiatance = mWidth - mHeight;                            invalidate();                            isOpen = true;                            isScrolled = false;                        }                    }                    if (isOpen) {                        if (mTransitionDiatance > (-0.5) * mTransitionLength) {                            mTransitionDiatance = -(mWidth - mHeight);                            invalidate();                            isOpen = true;                            isScrolled = false;                        } else {                            mTransitionDiatance = 0;                            isOpen = false;                            invalidate();                            isScrolled = false;                        }                    }                }                //滑动或单击动画完成之后 背景颜色的改变                backgroundByState(isOpen);                //滑动或单击动画完成之后 监听开关状态                if (onToggleListener != null) {                    onToggleListener.onToggleChanged(isOpen);                }        }        return true;    }4完整代码import android.animation.Animator;import android.animation.ArgbEvaluator;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.RectF;import android.os.Parcel;import android.os.Parcelable;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.animation.DecelerateInterpolator;import android.view.animation.Interpolator;import java.math.BigDecimal;import java.math.RoundingMode;/** * Created by chenmeng on 2016/10/25. */public class MyToggleView extends View implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {    private static float DEFAULT_WIDTH_HEIGHT_PERCENT = 0.55f;    private static float CIRCLE_ANIM_MAX_FRACTION;    private Paint mPaint;    /**     * 边框颜色 灰色     */    private int mOffBackgroundColor = Color.parseColor("#dadbda");    /**     * 开关打开颜色 绿色     */    private int mOnBackgroundColor = Color.parseColor("#4ebb7f");    /**     * 手柄颜色 白色     */    private int spotColor = Color.parseColor("#ffffff");    //动画模式下背景色 设置画笔    private int mCurrentColor = mOffBackgroundColor;    //滑动模式下背景色 设置画笔    private int nCurrentColor = mOffBackgroundColor;    private Path backgroundPath;    private Path circlePath;    private float mWidth;    private float mHeight;    private float mLeftX;    private float mRightX;    private float mCenterX;    private float mCenterY;    //开关状态监听接口    private OnToggleChanged onToggleListener;    //开关状态    private boolean isOpen = false;    //是否在动画过程中    private boolean mIsDuringAnimation = false;    private boolean isScrolled = false;//是否已经滑动    private int lastX;//手指按下位置    private int currentX;//    private ValueAnimator mValueAnimator;    //动画插值器    private Interpolator mInterpolator = new DecelerateInterpolator();    private long mOffAnimationDuration = 1000L;    private long mAnimationOnDuration = 1000L;    private float mAnimationFraction;    private float mTransitionLength;    private float mTransitionDiatance;//滑动时偏移的距离    //控件默认宽度    private int defaultWidth = 200;    private final static String TAG = MyToggleView.class.getSimpleName();    public MyToggleView(Context context) {        super(context);        init();    }    public MyToggleView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public MyToggleView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init() {        mPaint = new Paint();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));    }    private int measureWidth(int widthMeasureSpec) {        int resutltWidth = defaultWidth;        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        if (widthMode == MeasureSpec.EXACTLY) {            resutltWidth = widthSize;        } else {            if (widthMode == MeasureSpec.AT_MOST) {                resutltWidth = Math.min(resutltWidth, widthSize);            }        }        return resutltWidth;    }    private int measureHeight(int heightMeasureSpec) {        int resutltHeight = (int) (defaultWidth * DEFAULT_WIDTH_HEIGHT_PERCENT);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        if (heightMode == MeasureSpec.EXACTLY) {            resutltHeight = heightSize;        } else {            if (heightMode == MeasureSpec.AT_MOST) {                resutltHeight = Math.min(resutltHeight, heightSize);            }        }        return resutltHeight;    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        //设置padding属性        final int paddingTop = getPaddingTop();        final int paddingBottom = getPaddingBottom();        final int paddingRight = getPaddingRight();        final int paddingLeft = getPaddingLeft();        mWidth = w - paddingLeft - paddingRight;        mHeight = h - paddingBottom - paddingTop;        mLeftX = paddingLeft;        mRightX = paddingLeft + mWidth;        float top = paddingTop;        float left = paddingLeft;        float right = left + mWidth;        float bottom = top + mHeight;        mTransitionLength = mWidth - mHeight;        RectF backgroundRecf = new RectF(left, top, left + mHeight, bottom);        backgroundPath = new Path();        //左边圆弧        backgroundPath.arcTo(backgroundRecf, 90, 180);        //右边圆弧        backgroundRecf.left = right - mHeight;        backgroundRecf.right = right;        backgroundRecf.top = top;        backgroundRecf.bottom = bottom;        backgroundPath.arcTo(backgroundRecf, 270, 180);        //画圆形        float radius = (mHeight / 2) * 0.95f;        mCenterX = (left + left + mHeight) / 2;        mCenterY = (top + bottom) / 2;        RectF circleRectF = new RectF(mCenterX - radius, mCenterY - radius, mCenterX + radius, mCenterY + radius);        circlePath = new Path();        circlePath.arcTo(circleRectF, 90, 180);        circlePath.arcTo(circleRectF, 270, 180);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //画背景图 椭圆        drawBackground(canvas, isScrolled);        //前景图 圆形        drawForeground(canvas);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        int x = (int) event.getX();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                lastX = x;                return true;            case MotionEvent.ACTION_CANCEL:                break;            case MotionEvent.ACTION_MOVE:                currentX = (int) event.getX();                int offSetX = currentX - lastX;                if (Math.abs(offSetX) > 5) {                    isScrolled = true;                }                if ((!isOpen && offSetX < 0) || (isOpen && offSetX > 0)) {                    offSetX = 0;                }                if (Math.abs(offSetX) > mTransitionLength) {                    offSetX = (int) (offSetX > 0 ? mTransitionLength : -mTransitionLength);                }                mTransitionDiatance = offSetX;                if (isScrolled) {                    backgroundByDistance(mTransitionDiatance, isOpen);                }                break;            case MotionEvent.ACTION_UP:                //没有滑动 是一次单击过程                if (isScrolled == false) {                    if (mIsDuringAnimation) {                        return true;                    }                    if (isOpen) {                        startCloseAnimation();                        isOpen = false;                    } else {                        startOpenAnimation();                        isOpen = true;                    }                } else {                    //滑动之后的操作                    //滑动距离小于1/2总长度 退回到之前位置 否则自动靠近                    if (!isOpen) {                        Log.d(TAG, "mTransitionDiatance=" + mTransitionDiatance);                        //1/2无效 四舍五入后为0                        if (mTransitionDiatance < (0.5 * mTransitionLength)) {                            //是否可以考虑动画实现??弹性滑动                            mTransitionDiatance = 0;                            isOpen = false;                            invalidate();                            isScrolled = false;                        } else {                            mTransitionDiatance = mWidth - mHeight;                            invalidate();                            isOpen = true;                            isScrolled = false;                        }                    }                    if (isOpen) {                        if (mTransitionDiatance > (-0.5) * mTransitionLength) {                            mTransitionDiatance = -(mWidth - mHeight);                            invalidate();                            isOpen = true;                            isScrolled = false;                        } else {                            mTransitionDiatance = 0;                            isOpen = false;                            invalidate();                            isScrolled = false;                        }                    }                }                //滑动或单击动画完成之后 背景颜色的改变                backgroundByState(isOpen);                //滑动或单击动画完成之后 监听开关状态                if (onToggleListener != null) {                    onToggleListener.onToggleChanged(isOpen);                }        }        return true;    }    private void backgroundByState(boolean isOpen) {        if(isScrolled||mIsDuringAnimation)            return;        Log.d(TAG, "开关" + isOpen);        if (isOpen) {            mCurrentColor = mOnBackgroundColor;        } else {            mCurrentColor = mOffBackgroundColor;        }        invalidate();    }    /**     * 手指拖动时背景颜色渐变     *     * @param transilation     * @param isOpen     */    private void backgroundByDistance(float transilation, boolean isOpen) {        float diatance;        diatance = Math.abs(transilation);        int[] color = {mOnBackgroundColor, mOffBackgroundColor};        int startColor = isOpen ? color[0] : color[1];        int endColor = isOpen ? color[1] : color[0];        ArgbEvaluator argbEvaluator = new ArgbEvaluator();        BigDecimal bigDistance = BigDecimal.valueOf(diatance);        BigDecimal result = bigDistance.divide(new BigDecimal(mTransitionLength), 8, RoundingMode.HALF_UP);        float fraction = result.floatValue();        nCurrentColor = (Integer) argbEvaluator.evaluate(fraction, startColor, endColor);        mPaint.setColor(nCurrentColor);        invalidate();    }    private void startCloseAnimation() {        mValueAnimator = ValueAnimator.ofFloat(CIRCLE_ANIM_MAX_FRACTION, 0.0f);        mValueAnimator.setDuration(mOffAnimationDuration);        mValueAnimator.addUpdateListener(this);        mValueAnimator.addUpdateListener(this);        mValueAnimator.setInterpolator(mInterpolator);        mValueAnimator.start();        startColorAnimation();    }    private void startOpenAnimation() {        mValueAnimator = ValueAnimator.ofFloat(0.0f, CIRCLE_ANIM_MAX_FRACTION);        mValueAnimator.setDuration(mAnimationOnDuration);        mValueAnimator.addUpdateListener(this);        mValueAnimator.addUpdateListener(this);        mValueAnimator.setInterpolator(mInterpolator);        mValueAnimator.start();        startColorAnimation();    }    private void startColorAnimation() {        int fromColor = isOpen ? mOnBackgroundColor : mOffBackgroundColor;        int toColor = isOpen ? mOffBackgroundColor : mOnBackgroundColor;        long duration = isOpen ? mOffAnimationDuration : mAnimationOnDuration;        ValueAnimator colorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor);        colorAnimator.setDuration(duration);        colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mCurrentColor = (int) animation.getAnimatedValue();            }        });        colorAnimator.start();    }    private void drawForeground(Canvas canvas) {        canvas.save();        canvas.translate(getForegroundTransitionValue(isScrolled), 0);        mPaint.setColor(spotColor);        mPaint.setStyle(Paint.Style.FILL);        canvas.drawPath(circlePath, mPaint);        mPaint.reset();        canvas.restore();    }    private float getForegroundTransitionValue(boolean hasSlide) {        float result = 0;        //单击        if (!hasSlide) {            if (isOpen) {                if (mIsDuringAnimation) {                    result = mAnimationFraction * mTransitionLength;                } else                    result = mTransitionLength;            } else {                if (mIsDuringAnimation) {                    result = mAnimationFraction * mTransitionLength;                } else {                    result = 0;                }            }        } else {            //滑动时偏移的距离            result = isOpen ? mWidth - mHeight + mTransitionDiatance : mTransitionDiatance;        }        return result;    }    private void drawBackground(Canvas canvas, boolean isScrolled) {        if (isScrolled) {            mPaint.setColor(nCurrentColor);        } else {            mPaint.setColor(mCurrentColor);        }        mPaint.setStyle(Paint.Style.FILL);        canvas.drawPath(backgroundPath, mPaint);        mPaint.reset();    }    @Override    public void onAnimationStart(Animator animation) {        mIsDuringAnimation = true;    }    @Override    public void onAnimationEnd(Animator animation) {        mIsDuringAnimation = false;    }    @Override    public void onAnimationCancel(Animator animation) {        mIsDuringAnimation = false;    }    @Override    public void onAnimationRepeat(Animator animation) {        mIsDuringAnimation = true;    }    @Override    public void onAnimationUpdate(ValueAnimator animation) {        mAnimationFraction = (float) animation.getAnimatedValue();        invalidate();    }    /**     * 开关状态监听接口     */    public interface OnToggleChanged {        void onToggleChanged(boolean isOpen);    }    /**     * 设置接口     * @param onToggleChanged     */    public void setOnToggleChanged(OnToggleChanged onToggleChanged) {        onToggleListener = onToggleChanged;    }    //设置开关状态    public void setToggleState(boolean isOpen) {        this.isOpen = isOpen;        backgroundByState(isOpen);    }    public boolean getToogleState() {        return isOpen;    }//自定义View需要保存View的状态//否则 如果开关打开,横屏再切换回来开关会关闭    @Override    protected Parcelable onSaveInstanceState() {        Parcelable superState= super.onSaveInstanceState();        SavedState ss=new SavedState(superState);        ss.mIsOpen=isOpen?1:0;        return ss;    }    @Override    protected void onRestoreInstanceState(Parcelable state) {        Log.e("TEST","onRestore");        SavedState ss = (SavedState) state;        super.onRestoreInstanceState(ss.getSuperState());        boolean result = (ss.mIsOpen == 1);        setToggleState(result);    }    static class SavedState extends BaseSavedState{        int mIsOpen;        public SavedState(Parcel source) {            super(source);            mIsOpen=source.readInt();        }        public SavedState(Parcelable superState) {            super(superState);        }        @Override        public void writeToParcel(Parcel out, int flags) {            super.writeToParcel(out, flags);            out.writeInt(mIsOpen);        }        public static final Parcelable.Creator<SavedState> CREATOR=new Creator<SavedState>() {            @Override            public SavedState createFromParcel(Parcel source) {                return new SavedState(source);            }            @Override            public SavedState[] newArray(int size) {                return new SavedState[size];            }        };    }}
0 0
原创粉丝点击