源码整理
This commit is contained in:
@@ -6,91 +6,142 @@ import android.util.AttributeSet;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
|
|
||||||
public class VerticalSeekBar extends SeekBar {
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
public static final String TAG = VerticalSeekBar.class.getSimpleName();
|
|
||||||
|
|
||||||
public volatile int _mnProgress = -1;
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/12/17 14:11
|
||||||
|
* @Describe 垂直进度条控件,适配 API30,支持逆时针旋转(0在下,100在上),修复滑块同步bug
|
||||||
|
*/
|
||||||
|
public class VerticalSeekBar extends SeekBar {
|
||||||
|
// ======================== 静态常量(置顶,唯一标识)========================
|
||||||
|
private static final String TAG = VerticalSeekBar.class.getSimpleName();
|
||||||
|
|
||||||
|
// ======================== 成员变量(私有优先, volatile 关键字保留,确保线程可见性)========================
|
||||||
|
private volatile int mProgress = -1; // 当前进度缓存,修复滑块同步问题
|
||||||
|
|
||||||
|
// ======================== 构造方法(按参数个数升序排列,适配 Java7 语法)========================
|
||||||
public VerticalSeekBar(Context context) {
|
public VerticalSeekBar(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
initView();
|
||||||
|
LogUtils.d(TAG, "VerticalSeekBar: 单参数构造方法初始化完成");
|
||||||
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public VerticalSeekBar(Context context, AttributeSet attrs) {
|
public VerticalSeekBar(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
// 去除冗余的水平阴影
|
initView();
|
||||||
|
LogUtils.d(TAG, "VerticalSeekBar: 双参数构造方法初始化完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
initView();
|
||||||
|
LogUtils.d(TAG, "VerticalSeekBar: 三参数构造方法初始化完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 初始化方法(封装通用逻辑,避免构造方法冗余)========================
|
||||||
|
/**
|
||||||
|
* 初始化视图配置,适配 API30 资源管控
|
||||||
|
*/
|
||||||
|
private void initView() {
|
||||||
|
// 移除水平默认阴影,优化垂直显示效果,减少 API30 不必要的绘制开销
|
||||||
setBackgroundDrawable(null);
|
setBackgroundDrawable(null);
|
||||||
|
LogUtils.d(TAG, "initView: 视图初始化完成,移除默认背景阴影");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
// ======================== 重写测量/布局方法(按执行顺序排列:测量→尺寸变化→绘制)========================
|
||||||
super.onSizeChanged(h, w, oldh, oldw);
|
/**
|
||||||
}
|
* 重写测量方法,交换宽高适配垂直显示,兼容 API30 测量机制
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
|
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
|
||||||
|
// 交换测量结果,将原高度作为宽度、原宽度作为高度,实现垂直布局
|
||||||
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
|
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
|
||||||
|
LogUtils.v(TAG, "onMeasure: 垂直测量完成,宽=" + getMeasuredHeight() + ", 高=" + getMeasuredWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onDraw(Canvas c) {
|
/**
|
||||||
// 0--------100,顺时针旋转,小在上
|
* 重写尺寸变化方法,确保进度变化时视图同步刷新,适配 API30 布局刷新机制
|
||||||
// c.rotate(+90);
|
*/
|
||||||
// c.translate(0, -getWidth());
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
// 0--------100,逆时针旋转,小在下
|
super.onSizeChanged(h, w, oldh, oldw);
|
||||||
c.rotate(-90);
|
LogUtils.v(TAG, "onSizeChanged: 尺寸变化,新宽=" + h + ", 新高=" + w + ", 旧宽=" + oldh + ", 旧高=" + oldw);
|
||||||
c.translate(-getHeight(), 0);
|
|
||||||
|
|
||||||
super.onDraw(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写绘制方法,逆时针旋转90度实现垂直显示(0在下,100在上),适配 API30 画布渲染
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
// 逆时针旋转90度,平移画布避免绘制偏移(核心垂直显示逻辑)
|
||||||
|
canvas.rotate(-90);
|
||||||
|
canvas.translate(-getHeight(), 0);
|
||||||
|
super.onDraw(canvas);
|
||||||
|
LogUtils.v(TAG, "onDraw: 垂直绘制完成,旋转角度=-90°");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 重写触摸事件方法(核心交互逻辑,适配 API30 事件分发)========================
|
||||||
|
/**
|
||||||
|
* 重写触摸事件,转换坐标计算垂直进度,确保 OnSeekBarChangeListener 正常回调
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
// 调用基类的处理函数
|
// 先调用父类方法,保证 OnSeekBarChangeListener 的 onStart/onStopTrackingTouch 正常触发(关键!)
|
||||||
// 该方法可以使得
|
|
||||||
// SeekBar.OnSeekBarChangeListener
|
|
||||||
// 的 onStopTrackingTouch 和 onStartTrackingTouch 等函数有效。
|
|
||||||
boolean handled = super.onTouchEvent(event);
|
boolean handled = super.onTouchEvent(event);
|
||||||
|
LogUtils.d(TAG, "onTouchEvent: 触摸事件触发,action=" + event.getAction() + ", 父类处理结果=" + handled);
|
||||||
|
|
||||||
if (handled) {
|
if (handled) {
|
||||||
int action = event.getAction();
|
switch (event.getAction()) {
|
||||||
switch (action) {
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
LogUtils.d(TAG, "onTouchEvent: 触摸按下,坐标Y=" + event.getY());
|
||||||
|
break;
|
||||||
case MotionEvent.ACTION_MOVE:
|
case MotionEvent.ACTION_MOVE:
|
||||||
// 0--------100,顺时针旋转,小在上
|
// 计算垂直进度(逆时针旋转:Y越小进度越大,0在下,100在上)
|
||||||
//_mnProgress = (int)(getMax() * event.getY() / getHeight());
|
calculateProgress(event.getY());
|
||||||
// // 0--------100,逆时针旋转,小在下
|
setProgress(mProgress);
|
||||||
_mnProgress = getMax() - (int) (getMax() * event.getY() / getHeight());
|
LogUtils.v(TAG, "onTouchEvent: 触摸滑动,进度更新为=" + mProgress);
|
||||||
_mnProgress = _mnProgress > 100 ? 100 : _mnProgress ;
|
break;
|
||||||
//LogUtils.d(TAG, "_mnProgress is " + Integer.toString(_mnProgress));
|
case MotionEvent.ACTION_UP:
|
||||||
setProgress(_mnProgress);
|
// 滑动结束,最终更新进度
|
||||||
//onSizeChanged(getWidth(), getHeight(), 0, 0);
|
calculateProgress(event.getY());
|
||||||
|
setProgress(mProgress);
|
||||||
|
LogUtils.d(TAG, "onTouchEvent: 触摸抬起,最终进度=" + mProgress);
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_CANCEL:
|
case MotionEvent.ACTION_CANCEL:
|
||||||
break;
|
LogUtils.d(TAG, "onTouchEvent: 触摸取消,进度保持=" + getProgress());
|
||||||
default :
|
|
||||||
//LogUtils.d(TAG, "event.getAction() is " + event.getAction());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 返回父类处理结果,确保事件分发完整,适配 API30 事件机制
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解决调用setProgress()方法时滑块不跟随的bug
|
// ======================== 重写进度设置方法(修复滑块同步bug,适配 API30 进度更新)========================
|
||||||
|
/**
|
||||||
|
* 重写进度设置,调用尺寸变化方法强制刷新,解决 setProgress 滑块不跟随问题
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized void setProgress(int progress) {
|
public synchronized void setProgress(int progress) {
|
||||||
super.setProgress(progress);
|
super.setProgress(progress);
|
||||||
|
// 强制触发尺寸变化,同步刷新滑块位置(核心bug修复逻辑)
|
||||||
onSizeChanged(getWidth(), getHeight(), 0, 0);
|
onSizeChanged(getWidth(), getHeight(), 0, 0);
|
||||||
|
mProgress = progress;
|
||||||
|
LogUtils.d(TAG, "setProgress: 进度设置完成,进度=" + progress + ", 滑块同步刷新");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 内部工具方法(封装重复逻辑,提升复用性)========================
|
||||||
|
/**
|
||||||
|
* 计算垂直进度,校准范围 0~100,避免异常值
|
||||||
|
* @param touchY 触摸点Y坐标
|
||||||
|
*/
|
||||||
|
private void calculateProgress(float touchY) {
|
||||||
|
// 核心进度计算公式(逆时针旋转适配)
|
||||||
|
mProgress = getMax() - (int) (getMax() * touchY / getHeight());
|
||||||
|
// 校准进度范围,防止超出 0~100(兼容 API30 进度边界校验)
|
||||||
|
mProgress = Math.max(0, Math.min(mProgress, getMax()));
|
||||||
|
LogUtils.v(TAG, "calculateProgress: 进度计算完成,触摸Y=" + touchY + ", 计算进度=" + mProgress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user