手表最鸡肋的交互-长按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...");
}
});