服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android自定义控件实现带文字提示的SeekBar

Android自定义控件实现带文字提示的SeekBar

2022-09-05 15:28容华谢后 Android

这篇文章主要给大家介绍了关于Android自定义控件实现带文字提示的SeekBar的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1.写在前面

SeekBar控件在开发中还是比较常见的,比如音视频进度、音量调节等,但是原生控件有时还不能满足我们的需求,今天就来学习一下如何自定义SeekBar控件,本文主要实现了一个带文字指示器效果的SeekBar控件

看下最终效果:

Android自定义控件实现带文字提示的SeekBar

IndicatorSeekBar

2.实现

IndicatorSeekBar

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public class IndicatorSeekBar extends AppCompatSeekBar {
 
 // 画笔
 private Paint mPaint;
 // 进度文字位置信息
 private Rect mProgressTextRect = new Rect();
 // 滑块按钮宽度
 private int mThumbWidth = dp2px(50);
 // 进度指示器宽度
 private int mIndicatorWidth = dp2px(50);
 // 进度监听
 private OnIndicatorSeekBarChangeListener mIndicatorSeekBarChangeListener;
 
 public IndicatorSeekBar(Context context) {
 this(context, null);
 }
 
 public IndicatorSeekBar(Context context, AttributeSet attrs) {
 this(context, attrs, R.attr.seekBarStyle);
 }
 
 public IndicatorSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 init();
 }
 
 private void init() {
 mPaint = new TextPaint();
 mPaint.setAntiAlias(true);
 mPaint.setColor(Color.parseColor("#00574B"));
 mPaint.setTextSize(sp2px(16));
 
 // 如果不设置padding,当滑动到最左边或最右边时,滑块会显示不全
 setPadding(mThumbWidth / 2, 0, mThumbWidth / 2, 0);
 
 // 设置滑动监听
 this.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
 @Override
 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
 // NO OP
 }
 
 @Override
 public void onStartTrackingTouch(SeekBar seekBar) {
 if (mIndicatorSeekBarChangeListener != null) {
  mIndicatorSeekBarChangeListener.onStartTrackingTouch(seekBar);
 }
 }
 
 @Override
 public void onStopTrackingTouch(SeekBar seekBar) {
 if (mIndicatorSeekBarChangeListener != null) {
  mIndicatorSeekBarChangeListener.onStopTrackingTouch(seekBar);
 }
 }
 });
 }
 
 @Override
 protected synchronized void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 String progressText = getProgress() + "%";
 mPaint.getTextBounds(progressText, 0, progressText.length(), mProgressTextRect);
 
 // 进度百分比
 float progressRatio = (float) getProgress() / getMax();
 // thumb偏移量
 float thumbOffset = (mThumbWidth - mProgressTextRect.width()) / 2 - mThumbWidth * progressRatio;
 float thumbX = getWidth() * progressRatio + thumbOffset;
 float thumbY = getHeight() / 2f + mProgressTextRect.height() / 2f;
 canvas.drawText(progressText, thumbX, thumbY, mPaint);
 
 if (mIndicatorSeekBarChangeListener != null) {
 float indicatorOffset = getWidth() * progressRatio - (mIndicatorWidth - mThumbWidth) / 2 - mThumbWidth * progressRatio;
 mIndicatorSeekBarChangeListener.onProgressChanged(this, getProgress(), indicatorOffset);
 }
 }
 
 /**
 * 设置进度监听
 *
 * @param listener OnIndicatorSeekBarChangeListener
 */
 public void setOnSeekBarChangeListener(OnIndicatorSeekBarChangeListener listener) {
 this.mIndicatorSeekBarChangeListener = listener;
 }
 
 /**
 * 进度监听
 */
 public interface OnIndicatorSeekBarChangeListener {
 /**
 * 进度监听回调
 *
 * @param seekBar SeekBar
 * @param progress 进度
 * @param indicatorOffset 指示器偏移量
 */
 public void onProgressChanged(SeekBar seekBar, int progress, float indicatorOffset);
 
 /**
 * 开始拖动
 *
 * @param seekBar SeekBar
 */
 public void onStartTrackingTouch(SeekBar seekBar);
 
 /**
 * 停止拖动
 *
 * @param seekBar SeekBar
 */
 public void onStopTrackingTouch(SeekBar seekBar);
 }
 
 /**
 * dp转px
 *
 * @param dp dp值
 * @return px值
 */
 public int dp2px(float dp) {
 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
 getResources().getDisplayMetrics());
 }
 
 /**
 * sp转px
 *
 * @param sp sp值
 * @return px值
 */
 private int sp2px(float sp) {
 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
 getResources().getDisplayMetrics());
 }
}

重点看下onDraw方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected synchronized void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 String progressText = getProgress() + "%";
 mPaint.getTextBounds(progressText, 0, progressText.length(), mProgressTextRect);
 
 // 进度百分比
 float progressRatio = (float) getProgress() / getMax();
 // thumb偏移量
 float thumbOffset = (mThumbWidth - mProgressTextRect.width()) / 2 - mThumbWidth * progressRatio;
 float thumbX = getWidth() * progressRatio + thumbOffset;
 float thumbY = getHeight() / 2f + mProgressTextRect.height() / 2f;
 canvas.drawText(progressText, thumbX, thumbY, mPaint);
 
 if (mIndicatorSeekBarChangeListener != null) {
 float indicatorOffset = getWidth() * progressRatio - (mIndicatorWidth - mThumbWidth) / 2 - mThumbWidth * progressRatio;
 mIndicatorSeekBarChangeListener.onProgressChanged(this, getProgress(), indicatorOffset);
 }
}

再看一遍效果图:

Android自定义控件实现带文字提示的SeekBar

IndicatorSeekBar

可以看到,进度百分比文字是跟着进度变化在平移的,所以X轴坐标根据进度动态计算就可以了【总宽度 * 进度百分比】(getWidth() * progressRatio),文字需要居中显示,所以需要向右平移【(滑块宽度 - 文字宽度)/ 2】( (mThumbWidth - mProgressTextRect.width()) / 2)。

为了避免滑块滑动到终点时布局被隐藏,需要为SeekBar设置左右padding,距离分别为滑块宽度的一半,,所以【控件总长度 = 控件实际长度 + 滑块宽度】,向右平移的过程中就要动态减去滑块宽度【滑块宽度 * 进度百分比】(mThumbWidth * progressRatio),到这里文字的X轴坐标就计算完成了。

文字在平移的过程中始终是垂直居中的,所以Y轴坐标可以这样计算【控件高度 / 2 + 文字高度 / 2】(getHeight() / 2f + mProgressTextRect.height() / 2f),注意drawText方法默认是从左下角开始绘制文字的,如果对绘制文字还不太了解,可以看下这篇文章《Android 图解Canvas drawText文字居中的那些事

指示器跟随滑块移动

在IndicatorSeekBar中,向外提供了一个setOnSeekBarChangeListener方法用来回调SeekBar的状态,其中onProgressChanged方法中的indicatorOffset参数就是指示器控件的X坐标,计算方式与上文中进度百分比文字的计算方式一致:

?
1
2
3
// 【总宽度 * 进度百分比 -(指示器宽度 - 滑块宽度)/ 2 - 滑块宽度 * 进度百分比】
float indicatorOffset = getWidth() * progressRatio - (mIndicatorWidth - mThumbWidth) / 2 - mThumbWidth * progressRatio;
mIndicatorSeekBarChangeListener.onProgressChanged(this, getProgress(), indicatorOffset);

看下如何使用:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class MainActivity extends AppCompatActivity {
 
 private TextView tvIndicator;
 private IndicatorSeekBar indicatorSeekBar;
 private Paint mPaint = new Paint();
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 tvIndicator = findViewById(R.id.tv_indicator);
 indicatorSeekBar = findViewById(R.id.indicator_seek_bar);
 
 initData();
 }
 
 private void initData() {
 final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tvIndicator.getLayoutParams();
 indicatorSeekBar.setOnSeekBarChangeListener(new IndicatorSeekBar.OnIndicatorSeekBarChangeListener() {
  @Override
  public void onProgressChanged(SeekBar seekBar, int progress, float indicatorOffset) {
  String indicatorText = progress + "%";
  tvIndicator.setText(indicatorText);
  params.leftMargin = (int) indicatorOffset;
  tvIndicator.setLayoutParams(params);
  }
 
  @Override
  public void onStartTrackingTouch(SeekBar seekBar) {
  tvIndicator.setVisibility(View.VISIBLE);
  }
 
  @Override
  public void onStopTrackingTouch(SeekBar seekBar) {
  tvIndicator.setVisibility(View.INVISIBLE);
  }
 });
 }
}

布局文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 
 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_centerInParent="true"
 android:layout_marginStart="20dp"
 android:layout_marginEnd="20dp"
 android:orientation="vertical">
 
 <TextView
  android:id="@+id/tv_indicator"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/bg_indicator"
  android:gravity="center"
  android:textColor="#FFFFFF"
  android:textSize="16sp"
  android:visibility="invisible" />
 
 <com.yl.indicatorseekbar.IndicatorSeekBar
  android:id="@+id/indicator_seek_bar"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_marginTop="5dp"
  android:background="@null"
  android:max="100"
  android:maxHeight="2dp"
  android:minHeight="2dp"
  android:progress="50"
  android:progressDrawable="@drawable/seekbar_progress_drawable"
  android:thumb="@drawable/seekbar_thumb" />
 
 </LinearLayout>
 
</RelativeLayout>

3.写在最后

代码已上传至GitHub,欢迎Star、Fork!

GitHub地址:https://github.com/alidili/Demos/tree/master/IndicatorSeekBarDemo

本文Demo的Apk下载地址:

https://github.com/alidili/Demos/raw/master/IndicatorSeekBarDemo/IndicatorSeekBarDemo.apk

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:https://www.jianshu.com/p/b753c4a9ddfa

延伸 · 阅读

精彩推荐