您的位置:首页 > 其它

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

2016-11-04 15:43 281 查看
单击:动画效果改变开关的状态

滑动:根据拖动距离设置颜色渐变,拖动距离小于某一值返回原状态,否则返回另一状态


主要步骤:

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];
}
};
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: