自定义可滑动、可点击的开关
2016-11-04 15:43
281 查看
单击:动画效果改变开关的状态
滑动:根据拖动距离设置颜色渐变,拖动距离小于某一值返回原状态,否则返回另一状态
主要步骤:
1onMeasure()
defaultWidth=200dp,defaultHeight=defaultWidth*0.55
2onDraw()
3onTouchEven@Override
滑动:根据拖动距离设置颜色渐变,拖动距离小于某一值返回原状态,否则返回另一状态
主要步骤:
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]; } }; } }
相关文章推荐
- android三档自定义滑动开关,禁止点击功能的实现,用默认的seekbar组件实现
- 自定义开关(不带有滑动,只具有点击切换开关功能)
- android三档自定义滑动开关,禁止点击功能的实现,用默认的seekbar组件实现
- 自定义开关控件 点击和滑动效果
- Android自定义开关(可滑动,点击)
- Android 三档自定义滑动开关,禁止点击功能的实现,用默认的seekbar组件实现
- Android:android自定义滑动开关控件
- Android自定义View-------IOS风格的滑动开关
- 自定义tab,下边是viewpager。滑动viewpager,tab跟着动,点击tab,有动画效果并且viewpager也动。
- android自定义滑动开关
- android自定义滑动开关组件【转】
- 自定义view-滑动开关
- 可滑动可点击,不占全屏的自定义控件
- android自定义View之滑动开关SlideButton
- 自定义滑动开关SwitchButton
- Android自定义控件之实现滑动选择开关
- 自定义滑动开关按钮-SwitchButton-进阶
- Android 自定义简单的滑动效果switch开关
- 自定义的可滑动的开关按钮
- 自定义ViewGroup (支持margin,gravity以及水平,垂直排列,滑动和点击事件)