手表最鸡肋的交互-长按to do something
先上个图吧:

普通状态

长按后会转圈圈

做出来是怎么样子的呢?

理想很美好,显示很残酷!

师爷你来给我翻译翻译!
相关课程
要实现这个东西呢,同学们掌握自定义控件的课程就可以实现了。
如何实现呢?
如果要显示这个图的效果:

我相信,同学们都能想到如何实现。
就一个TextView,然后加个bg就可以了。
比如说代码这样子:
   <TextView
            android:id="@+id/textView9"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginTop="16dp"
            android:background="@drawable/long_press_close_bg"
            android:gravity="center"
            android:onLongClick="@{()->eventHandler.onCloseLongClick()}"
            android:text="@string/long_press_close_text"
            android:textSize="16sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView8" />

bg的代码是这个:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <stroke
        android:width="2dp"
        android:color="@color/white" />
    <size
        android:width="50dp"
        android:height="50dp" />
</shape>

这样子,第一个问题解决了。
那么第二个问题是,长按的时候,怎么样才可以让控件有一个过度的过程。也就是转圈圈的过程。
既然这样可以实现,那么我们是不是可以继承自TextView去二次修改这个View呢?
复写onDraw方法,适当的时候调用spuer,绘制TextView应该绘制的内容,适当的时候绘制我们要求的内容即可。
接口,属性定义
一般来说,我们在设计一个控件的时候,要设计属性,设计接口。
属性是对外开放的,别人控制这个控件的。
接口则是控件通知外部的,谁需要关心,谁就实现。
接口
对于这个控件来说,就两个状态通知外部。
- 用户长按到了特定时长了,也就是完成了(转了一圈)
- 用户的手松开了,没到特定时长,可以理解为取消了。
那我们取个名字:OnLongPressBtnStateChangeListener
里面的方法有:
- onUserCancel();
- onLongPress();//有必要的话,可以加上时长
属性
属性是使用者对控件的设置,比如说大小呀,颜色,时长这些。
对于这个控件来说,你希望哪些是可以设置的呢?可以改变的呢?
比如说,内环的颜色,外环的颜色,外环的宽度,转一周的时长是多少?
- innerCycleColor //内部圈的颜色
- outerPathColor //外部path的颜色
- outerPathDefaultColor //外部path的默认颜色
- duration //long press的时长是多少
至于其他的尺寸我们可以动态地计算。
这些东西都不是非得一定出来的。同学们可以在写的时候,觉得不够了,再定。也可以按产品的要求,以及在效果图中抽象出共同的内容来,哪些是变的,哪些是不变的。
甚至都不用考虑这些,想怎么写怎么写,写完了再去写文档也是可以的。
定义属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="LongPressBtn">
        <attr name="outerPathWidth" format="dimension" />
        <attr name="outerPathColor" format="color" />
        <attr name="innerCircleColor" format="color" />
        <attr name="outerPathDefaultColor" format="color" />
        <attr name="pressDuration" format="integer" />
    </declare-styleable>
</resources>
代码实现
package net.sunofbeach.longpressbtn.view;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import net.sunofbeach.longpressbtn.R;
import net.sunofbeach.longpressbtn.utils.SizeUtils;
public class LongPressBtn extends AppCompatTextView {
    private static final String TAG = "LongPressButton";
    private Paint innerPaint;
    private Paint outerPaint;
    private Paint pathPaint;
    private int outerR;
    private Paint circlePaint;
    //颜色
    private final int outerPathColor;
    private final int outerPathDefaultColor;
    private final int innerCircleColor;
    private final int pressDuration;
    private final int outerPathWidth;
    //
    public final int CIRCLE_VALUE = 360;
    private OnLongPressStateChangeListener mListener = null;
    public LongPressBtn(Context context) {
        this(context, null);
    }
    public LongPressBtn(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }
    public LongPressBtn(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //设置文字内容
        setText(R.string.long_press_close_text);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LongPressBtn);
        //获取相关的属性
        outerPathColor = a.getColor(R.styleable.LongPressBtn_outerPathColor, context.getColor(R.color.white));
        outerPathDefaultColor = a.getColor(R.styleable.LongPressBtn_outerPathDefaultColor, context.getColor(R.color.ring_default_color));
        innerCircleColor = a.getColor(R.styleable.LongPressBtn_innerCircleColor, context.getColor(R.color.white));
        pressDuration = a.getInt(R.styleable.LongPressBtn_pressDuration, DEFAULT_TIME_DURATION);
        outerPathWidth = (int) a.getDimension(R.styleable.LongPressBtn_outerPathWidth, SizeUtils.dip2px(context, 4));
        a.recycle();
        //准备画笔
        initPaint();
        mRecordAnim.addUpdateListener(animation -> {
            progress = (int) animation.getAnimatedValue();
            invalidate();
            //如果值到了,那就结束了呀
            if (progress == CIRCLE_VALUE && mListener != null) {
                mListener.onLongPressFinished();
            }
        });
    }
    public void setOnLongPressStateChangeListener(OnLongPressStateChangeListener listener) {
        this.mListener = listener;
    }
    public interface OnLongPressStateChangeListener {
        void onLongPressFinished();
        void onUserCancel();
    }
    /**
     * 设置相关的画笔
     */
    private void initPaint() {
        innerPaint = new Paint();
        innerPaint.setStyle(Paint.Style.FILL);
        innerPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        outerPaint = new Paint();
        outerPaint.setStyle(Paint.Style.STROKE);
        outerPaint.setStrokeWidth(outerPathWidth);
        outerPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        pathPaint = new Paint();
        pathPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        pathPaint.setStyle(Paint.Style.STROKE);
        //补偿一下宽度
        pathPaint.setStrokeWidth(outerPathWidth + 0.5f);
        circlePaint = new Paint();
        circlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setStrokeWidth(outerPathWidth);
        //设置颜色
        innerPaint.setColor(innerCircleColor);
        outerPaint.setColor(outerPathColor);
        pathPaint.setColor(outerPathDefaultColor);
        circlePaint.setColor(outerPathColor);
    }
    //按压状态
    enum PressState {
        NONE, PRESS, UP
    }
    //中心点
    private int centerX = 0;
    private int centerY = 0;
    //内圆半径
    private int innerR = 0;
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        centerX = getWidth() / 2;
        centerY = getHeight() / 2;
        Log.d(TAG, "center x == > " + centerX);
        Log.d(TAG, "center y == > " + centerY);
        Log.d(TAG, "outerRingWidth == > " + outerPathWidth);
        //内圈半径
        innerR = getWidth() / 2 - outerPathWidth * 3;
        outerR = getWidth() / 2;
    }
    private PressState mCurrentPressState = PressState.NONE;
    private final ValueAnimator mRecordAnim = ValueAnimator.ofInt(0, CIRCLE_VALUE);
    //转过的角度
    private int progress = 0;
    @Override
    protected void onDraw(Canvas canvas) {
        //根据状态判断是否需要super
        //触摸的时候不super,手松开的时候super
        if (mCurrentPressState != PressState.PRESS) {
            super.onDraw(canvas);
            canvas.drawCircle(centerX, centerY, outerR - outerPathWidth, circlePaint);
        } else {
            //画圈圈
            canvas.drawCircle(centerX, centerY, innerR, innerPaint);
            //外圈
            canvas.drawCircle(centerX, centerY, outerR - outerPathWidth, outerPaint);
            canvas.drawArc(centerX - (outerR - outerPathWidth),
                    centerY - (outerR - outerPathWidth),
                    centerX + (outerR - outerPathWidth),
                    centerY + (outerR - outerPathWidth),
                    -90,
                    progress,
                    false,
                    pathPaint);
        }
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "ACTION_DOWN...");
                mCurrentPressState = PressState.PRESS;
                invalidate();
                //找按就开始
                startProgress();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                Log.d(TAG, "ACTION_UP...");
                mCurrentPressState = PressState.UP;
                invalidate();
                //判断是否结束,如果没有结束则重置动画相关的属性
                if (mListener != null
                        && progress != CIRCLE_VALUE) {
                    mListener.onUserCancel();
                }
                break;
        }
        return true;
    }
    public static final int DEFAULT_TIME_DURATION = 3000;
    private void startProgress() {
        mRecordAnim.setDuration(pressDuration);
        mRecordAnim.setInterpolator(new LinearInterpolator());
        mRecordAnim.start();
    }
}
事件通知
定义接口
    public interface OnLongPressStateChangeListener {
        void onLongPressFinished();
        void onUserCancel();
    }
暴露设置接口的地方
    public void setOnLongPressStateChangeListener(OnLongPressStateChangeListener listener) {
        this.mListener = listener;
    }
回调的时机
- 用户手离开的时候
- 长按时长到时间的时候
用户手离开的时候
 Log.d(TAG, "ACTION_UP...");
                mCurrentPressState = PressState.UP;
                invalidate();
                //判断是否结束,如果没有结束则重置动画相关的属性
                if (mListener != null
                        && progress != CIRCLE_VALUE) {
                    mListener.onUserCancel();
                }
长按时间到的时候
 mRecordAnim.addUpdateListener(animation -> {
            progress = (int) animation.getAnimatedValue();
            invalidate();
            //如果值到了,那就结束了呀
            if (progress == CIRCLE_VALUE && mListener != null) {
                mListener.onLongPressFinished();
            }
        });
使用
    <net.sunofbeach.longpressbtn.view.LongPressBtn
        android:id="@+id/long_press_btn"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:gravity="center"
        android:text="@string/long_press_close_text"
        android:textColor="@color/white"
        android:textSize="14sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
结果就如前面所示啦!
设置一下回调接口
        LongPressBtn longPressBtn = findViewById(R.id.long_press_btn);
        longPressBtn.setOnLongPressStateChangeListener(new LongPressBtn.OnLongPressStateChangeListener() {
            @Override
            public void onLongPressFinished() {
                Log.d(TAG, "onLongPressFinished...");
            }
            @Override
            public void onUserCancel() {
                Log.d(TAG, "onUserCancel...");
            }
        });

































