很多跑步软件,暂停后的结束按钮都有一个这样的动画:
长按“结束”按钮,才能结束跑步,长按时候,伴随一个实时的进度动画,实际效果图如下(真机运行流畅,gif中会卡顿):
实现思路:
1、处理点击事件,保证长按时,动画开始;长按结束(手指移动、手指抬起)动画结束
2、进度动画实现
因为是协作开发提供给其他开发者使用,所以用自定义组件的来实现了,还要有结束接口暴露出去。
下面直接贴代码了,实现也比较简单,希望能帮助到有需要的人。
public class LongPressToFinishButton extends RelativeLayout {
private int mLastMotionX, mLastMotionY;
private static final int TOUCH_SLOP = 20;
private final int DURATION = 1000;
int roundWidth;//圆环宽度
private boolean isMoved = false;
private int progress = 0;
private Paint backgroundCirclePaint, progressCirclePaint;
private ValueAnimator valueAnimator;
private boolean isPress;
RelativeLayout buttonLayout;
public LongPressToFinishButton(Context context) {
super(context);
init();
}
public LongPressToFinishButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LongPressToFinishButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View convertView = inflater.inflate(R.layout.long_press_to_finish_button, this, true);
buttonLayout = (RelativeLayout) convertView.findViewById(R.id.button_layout);
roundWidth = DisplayUtils.dip2px(getContext(), 3);
backgroundCirclePaint = new Paint();
backgroundCirclePaint.setStyle(Paint.Style.STROKE);
backgroundCirclePaint.setColor(Color.parseColor("#565351"));
backgroundCirclePaint.setAntiAlias(true);
backgroundCirclePaint.setStrokeWidth(roundWidth);
progressCirclePaint = new Paint();
progressCirclePaint.setStyle(Paint.Style.STROKE);
progressCirclePaint.setColor(Color.parseColor("#E8A387"));
progressCirclePaint.setAntiAlias(true);
progressCirclePaint.setStrokeWidth(roundWidth);
valueAnimator = ValueAnimator.ofInt(0, 100);
valueAnimator.setDuration(DURATION);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
progress = (int) valueAnimator.getAnimatedValue();
postInvalidate();
if (progress == 100) {
postDelayed(new Runnable() {
@Override
public void run() {
isPress = false;
postInvalidate();
if (onFinishListener != null) {
onFinishListener.onFinish();
}
}
}, 50);
}
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isPress = false;
postInvalidate();
}
});
}
private OnFinishListener onFinishListener;
public interface OnFinishListener {
void onFinish();
}
public void setOnFinishListener(OnFinishListener onFinishListener) {
this.onFinishListener = onFinishListener;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
Log.i("nieqi", "dispatchDraw , progress = " + progress);
int center = getWidth() / 2;
int radius = center - roundWidth / 2; //圆环的半径
canvas.save();
RectF oval = new RectF(center - radius, center - radius, center
+ radius, center + radius);
if (isPress) {
canvas.drawArc(oval, 0, 360, false, backgroundCirclePaint);
canvas.drawArc(oval, -90, 360 * progress / 100, false, progressCirclePaint);
}
canvas.restore();
}
private void startAnim() {
Log.i("nieqi", "startAnimation");
if (valueAnimator != null) {
valueAnimator.start();
}
}
private void cancelAnimation() {
isPress = false;
if (valueAnimator != null) {
valueAnimator.cancel();
}
progress = 0;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if (x >= buttonLayout.getLeft() && x <= buttonLayout.getRight()
&& y >= buttonLayout.getTop() && y <= buttonLayout.getBottom()) {//
Log.i("nieqi", "press");
mLastMotionX = x;
mLastMotionY = y;
isMoved = false;
isPress = true;
startAnim();
}
break;
case MotionEvent.ACTION_MOVE:
if (isMoved) {
break;
}
if (Math.abs(mLastMotionX - x) > TOUCH_SLOP
|| Math.abs(mLastMotionY - y) > TOUCH_SLOP) {
isMoved = true;
cancelAnimation();
}
break;
case MotionEvent.ACTION_UP:
cancelAnimation();
break;
}
return true;
}
}
重写dispatchTouchEvent
是为了监听长按,因为系统提供的setOnLongClickListener无法满足要求
代码中,当我们接收到ACTION_DOWN时,判断是否是在圆形按钮范围内,接下来开启长按的状态,开启动画
重写dispatchDraw
一般我们自定义View中,都是重写draw或者onDraw方法,但是这里必须要去重写dispatchDraw,不然postInvalidate是没有办法刷新界面的
当时就犯了这个错误,卡了很久。
因为我们继承的是ViewGroup,网上的说法是:
自定义一个ViewGroup,重写onDraw。
onDraw可能不会被调用,原因是需要先设置一个背景(颜色或图)。
表示这个group有东西需要绘制了,才会触发draw,之后是onDraw。
因此,一般直接重写dispatchDraw来绘制viewGroup
所以就只需要处理好这两点,就能请轻松的实现了。
Good luck!