一、前言
ToggleButton 是常用的Android常用的组件,早期由于有些版本存在差异,很难做出想要的效果,本篇通过自定义方式,来温习一下事件的滑动,同时提供一个骨架,方便后续改造。
二、实现
事件的处理上一定要做到及时消费,这和定义签名View或者手绘View一个道理,由于存在时间差,Move点并不连续,因此不需要担心保存太多的点。
做这类View的定义,需要了解以下特征,基本可以顺手拿来:
-
DOWN事件捕获,拦截
-
起点
-
移动方向
-
MOVE事件
-
UP/CANCEL事件处理
-
volecity
代码实现
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int lineWidthPixies = (int) dpTopx(mLineWidth);
switch (action) {
case MotionEvent.ACTION_DOWN:
//记录起点
startX = event.getX();
startY = event.getY();
getParent().requestDisallowInterceptTouchEvent(true);
return true; //捕获事件
case MotionEvent.ACTION_MOVE:
float currentX = event.getX();
float currentY = event.getY();
float dy = Math.abs(currentY - startY);
float dx = Math.abs(currentX - startX);
if (dy <= dx && dx >= mTouchSlop) {
isTouchMove = true;
}
if (isTouchMove) {
offsetX = (int) (currentX - slideBarRadius);
startX = currentX;
startY = currentY;
if (offsetX < (lineWidthPixies)) {
offsetX = 0; //最左边
} else if (offsetX > contentWidth - slideBarRadius * 2) { //最右边
offsetX = contentWidth - slideBarRadius * 2;
} //消费
postInvalidateOnAnimation();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
getParent().requestDisallowInterceptTouchEvent(false);
startX = event.getX();
if (startX <= getWidth() / 2) {
if(isTouchMove){
offsetX = 0;
mState = STATUS_LEFT;
}else{
startSlideBarAnimation(offsetX,0,STATUS_LEFT);
}
} else {
if(isTouchMove){
mState = STATUS_RIGHT;
offsetX = contentWidth - 2 * slideBarRadius;
}else{
startSlideBarAnimation(offsetX,contentWidth - 2 * slideBarRadius,STATUS_RIGHT);
}
}
isTouchMove = false;
postInvalidateOnAnimation();
break;
}
return super.onTouchEvent(event);
}
全部代码
public class ToggleButton extends View {
private int mLineWidth = 3;
private int mTextSize = 16;
private TextPaint mTextPaint = null;
private int mTouchSlop = 0;
private boolean isTouchMove = false;
private float offsetX = 0;
private float contentWidth = 0;
private float contentHeight = 0;
private float slideBarRadius;
float startX = 0f;
float startY = 0f;
private int STATUS_LEFT = 0;
private int STATUS_RIGHT = 1;
private RectF outlineRectF = new RectF();
private RectF innerRectF = new RectF();
private RectF middleRect = new RectF();
private RectF circleRectF = new RectF();
private int mState = STATUS_LEFT;
private ValueAnimator mSlideAnimator = null;
public ToggleButton(Context context) {
this(context, null);
}
public ToggleButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//设置此项true,否则无法滑动
mTextPaint = initPaint();
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop() / 2;
setClickable(true);
setFocusable(true);
setFocusableInTouchMode(true);
}
private TextPaint initPaint() {
// 实例化画笔并打开抗锯齿
TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
paint.setAntiAlias(true);
paint.setStrokeWidth(dpTopx(mLineWidth));
paint.setTextSize(dpTopx(mTextSize));
return paint;
}
private float dpTopx(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = getPaddingTop() + getPaddingBottom() + 100;
} else if (heightMode == MeasureSpec.AT_MOST) {
int disire = getPaddingTop() + getPaddingBottom() + 100;
heightSize = Math.min(disire, heightSize);
}
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = getPaddingTop() + getPaddingBottom() + 280;
} else if (widthMode == MeasureSpec.AT_MOST) {
int disire = getPaddingTop() + getPaddingBottom() + 280;
widthSize = Math.min(disire, widthSize);
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w = getWidth();
int h = getHeight();
float lineWidthPixel = mTextPaint.getStrokeWidth();
contentWidth = w - 2 * lineWidthPixel;
contentHeight = h - 2 * lineWidthPixel;
float radius = Math.min(w, h) / 2F - lineWidthPixel / 2;
if (radius < 0) return;
mTextPaint.setColor(Color.LTGRAY);
mTextPaint.setStyle(Paint.Style.STROKE);
outlineRectF.set(lineWidthPixel / 2F,
lineWidthPixel / 2F,
lineWidthPixel + contentWidth + lineWidthPixel / 2F,
lineWidthPixel + contentHeight + lineWidthPixel / 2F);
canvas.drawRoundRect(outlineRectF, radius, radius, mTextPaint);
innerRectF.set(lineWidthPixel,
lineWidthPixel,
lineWidthPixel + contentWidth,
lineWidthPixel + contentHeight);
mTextPaint.setColor(Color.TRANSPARENT);
mTextPaint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(innerRectF, radius, radius, mTextPaint);
mTextPaint.setColor(Color.LTGRAY);
middleRect.set(w / 2F - lineWidthPixel / 2F,
lineWidthPixel * 3,
w / 2F + lineWidthPixel / 2F,
h - lineWidthPixel * 3);
mTextPaint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(middleRect, radius, radius, mTextPaint);
slideBarRadius = contentHeight / 2F;
circleRectF.set(lineWidthPixel + lineWidthPixel + offsetX,
0,
slideBarRadius * 2 + offsetX,
h);
mTextPaint.setColor(Color.GRAY);
canvas.drawCircle(circleRectF.centerX(), circleRectF.centerY(), slideBarRadius, mTextPaint);
if (mState == STATUS_LEFT) {
mTextPaint.setColor(Color.CYAN);
} else {
mTextPaint.setColor(Color.RED);
}
canvas.drawCircle(circleRectF.centerX(), circleRectF.centerY(), slideBarRadius / 4, mTextPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int lineWidthPixies = (int) dpTopx(mLineWidth);
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
getParent().requestDisallowInterceptTouchEvent(true);
return true;
case MotionEvent.ACTION_MOVE:
float currentX = event.getX();
float currentY = event.getY();
float dy = Math.abs(currentY - startY);
float dx = Math.abs(currentX - startX);
if (dy <= dx && dx >= mTouchSlop) {
isTouchMove = true;
}
if (isTouchMove) {
offsetX = (int) (currentX - slideBarRadius);
startX = currentX;
startY = currentY;
if (offsetX < (lineWidthPixies)) {
offsetX = 0; //最左边
} else if (offsetX > contentWidth - slideBarRadius * 2) { //最右边
offsetX = contentWidth - slideBarRadius * 2;
}
postInvalidateOnAnimation();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
getParent().requestDisallowInterceptTouchEvent(false);
startX = event.getX();
if (startX <= getWidth() / 2F) {
if (isTouchMove) {
offsetX = 0;
mState = STATUS_LEFT;
} else {
startSlideBarAnimation(offsetX, 0, STATUS_LEFT);
}
} else {
if (isTouchMove) {
mState = STATUS_RIGHT;
offsetX = contentWidth - 2 * slideBarRadius;
} else {
startSlideBarAnimation(offsetX, contentWidth - 2 * slideBarRadius, STATUS_RIGHT);
}
}
isTouchMove = false;
postInvalidateOnAnimation();
break;
}
return super.onTouchEvent(event);
}
public void postInvalidateOnAnimation() {
if (Build.VERSION.SDK_INT >= 16) {
super.postInvalidateOnAnimation();
} else {
postInvalidate();
}
}
public void setState(int state) {
this.mState = state;
if (state == STATUS_LEFT) {
offsetX = 0;
} else {
offsetX = contentWidth - 2 * slideBarRadius;
}
postInvalidate();
}
public void startSlideBarAnimation(float from, float to, final int state) {
if (mSlideAnimator != null) {
mSlideAnimator.cancel();
}
mSlideAnimator = ValueAnimator.ofFloat(from, to).setDuration(300);
mSlideAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mSlideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
offsetX = (float) animation.getAnimatedValue();
float fraction = animation.getAnimatedFraction();
if (fraction >= 0.9) {
mState = state;
}
postInvalidate();
}
});
mSlideAnimator.start();
}
}
三、总结
总体上不是很难,但是需要了解只是有Android的事件传递机制和捕获机制,requestDisallowParentInterceptTouchEvent,另外就是联动机制,滑动方向判断等,还有事件相对View的坐标,要知道的是TouchEvent无法像Canvas那样平移,但是你可以转换。