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

单击:动画效果改变开关的状态
滑动:根据拖动距离设置颜色渐变,拖动距离小于某一值返回原状态,否则返回另一状态最下面的绿色开关,没有动态图
主要步骤:
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];
            }
        };
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值