SeekBar概述
SeekBar是拖动条,与进度条ProgressBar类似,但是该控件支持与用户交互,用户可以通过拖动来调节SeekBar,以此来控制音量、亮度等功能。
由于SeekBar可以与用户交互,自然需要对用户的操作进行事件监听需要实现SeekBar.onSeekBarChangeListener接口,监听3个事件:
① 数值的改变(onProgressChanged);
② 开始拖动(onStartTrackingTouch);
③ 停止拖动(onStopTrackingTouch)。
SeekBar集成了ProgressBar,ProgressBar的xml属性SeekBar都可以用
自定义SeekBar
简单自定义SeekBar
- 简单自定义SeekBar样式
SeekBar由进度条、进度条背景和滑块三部分组成,由于SeekBar继承自ProgressBar,属性也被继承过来了,所以SeekBar的进度条自定义完全可以复用ProgressBar的简单自定义样式;
- SeekBar进度条样式 <drawable/horizontal_style.xml>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 进度条的背景色-->
<item android:id="@android:id/background"
android:gravity="center_vertical|fill_horizontal">
<shape android:shape="rectangle">
<size android:height="10dp" />
<corners android:radius="5dp" />
<solid android:color="#AAA" />
</shape>
</item>
<!-- 缓冲进度条的背景色-->
<item android:id="@android:id/secondaryProgress"
android:gravity="center_vertical|fill_horizontal">
<scale android:scaleWidth="100%">
<shape android:shape="rectangle">
<size android:height="10dp" />
<corners android:radius="5dp" />
<solid android:color="@color/teal_200" />
</shape>
</scale>
</item>
<!-- 进度条的背景色-->
<item android:id="@android:id/progress"
android:gravity="center_vertical|fill_horizontal">
<scale android:scaleWidth="100%">
<shape android:shape="rectangle">
<size android:height="10dp" />
<corners android:radius="5dp" />
<solid android:color="@color/teal_700"/>
</shape>
</scale>
</item>
</layer-list>
- SeekBar滑块样式 <drawable/thumb_style.xml>
滑块根据状态设置了两种样式,正常状态下是蓝色环型圆圈,点击滑动的时候为红色环型圆圈;
<drawable/thumb_style.xml>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/thumb_pressed"/>
<item android:state_pressed="false" android:drawable="@drawable/thumb_normal"/>
</selector>
<drawable/thumb_normal.xml>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="#00f" />
<size android:width="20dp" android:height="20dp"/>
</shape>
</item>
<item android:left="5dp" android:right="5dp" android:top="5dp" android:bottom="5dp">
<shape android:shape="oval">
<solid android:color="#fff" />
<size android:width="10dp" android:height="10dp"/>
<corners android:radius="5dp"/>
</shape>
</item>
</layer-list>
<drawable/thumb_pressed.xml>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="#f00" />
<size android:width="20dp" android:height="20dp"/>
</shape>
</item>
<item android:left="5dp" android:right="5dp" android:top="5dp" android:bottom="5dp">
<shape android:shape="oval">
<solid android:color="#fff" />
<size android:width="10dp" android:height="10dp"/>
<corners android:radius="5dp"/>
</shape>
</item>
</layer-list>
- 自定义SeekBar控件 <activity_seek_bar.xml>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SeekBarActivity">
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:progress="66"
android:secondaryProgress="88"
android:progressDrawable="@drawable/horizontal_style"
android:thumb="@drawable/thumb_style"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- 监听SeekBar事件 <SeekBarActivity.kt>
class SeekBarActivity : AppCompatActivity() ,SeekBar.OnSeekBarChangeListener{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_seek_bar)
initView()
}
private fun initView(){
seek_bar.setOnSeekBarChangeListener(this)
}
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
Log.d("=========", " progress = $progress =========== ")
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
Log.d("=========", " onStartTrackingTouch =========== ")
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
Log.d("=========", " onStopTrackingTouch =========== ")
}
}
- 当点到SeekBar的滑块时会监听的 onStartTrackingTouch 事件;
- 滑动滑块时 onProgressChanged 监听事件中的 progress 参数可以实时获取进度条的数值;
- 当用户手指离开SeekBar滑块时会监听到 onStopTrackingTouch事件。
自定义亮度调节CustomSeekBarItem
亮度、音量等功能调节经常会用到SeekBar可拖动控件,接下来将自定义一个亮度调节控件SeekBarItem,效果如下图。
自定义CustomSeekBarItem的步骤如下:
-
首先绘制SeekBar进度条和滑块的样式,这里自定义的SeekBar不显示滑块,只需要修改进度条的样式;
-
根据需求自定义CustomSeekBarItem的布局,将需要的多种控件组合排布,构成一个通用的自定义功能控件;
-
编写自定义控件CustomSeekBarItem类,在CustomSeekBarItem类中绑定CustomSeekBarItem的布局以及CustomSeekBarItem属性,编写接口和SeekBar监听器,相当于在SeekBar控件外面包裹了一层,CustomSeekBarItem类的作用就是使开发人员在使用封装的自定义控件CustomSeekBarItem时可以直接设置内部控件的属性;
-
自定义控件CustomSeekBarItem的使用。
1. 绘制自定义SeekBar进度条的样式 <seekbar_style.xml>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 进度条的背景色-->
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<size android:height="100dp"/>
<corners android:radius="16dp" />
<solid android:color="#8005051C" />
</shape>
</item>
<!-- 进度条的背景色-->
<item android:id="@android:id/progress">
<inset
android:insetTop="10dp"
android:insetRight="10dp"
android:insetBottom="10dp"
android:insetLeft="10dp">
<clip>
<shape>
<corners android:radius="13dp" />
<solid android:color="#B3EBEEFF" />
</shape>
</clip>
</inset>
</item>
</layer-list>
与简单自定义SeekBar进度条方法一样,只不过这里的进度条嵌套在进度条背景里面,四周留边,然后改用高级一点的颜色。
2. 自定义CustomSeekBarItem控件布局 <activity_custom_seek_bar_item.xml>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/seekbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<!-- 拖动条 -->
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:progress="66"
android:thumb="@null"
android:background="@android:color/transparent"
android:progressDrawable="@drawable/seekbar_style"
app:layout_constraintTop_toTopOf="parent"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingEnd="0dp"
android:paddingRight="0dp" />
<!-- 左侧功能图标 -->
<View
android:id="@+id/seekbar_icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/brightness_icon"
android:layout_marginLeft="45dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
</View>
<!-- 右侧显示进度值 -->
<TextView
android:id="@+id/seekbar_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="66%"
android:textSize="30sp"
android:textColor="@color/white"
android:layout_marginRight="40dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
① 拖动条上面显示图标和百分比是通过图层叠加上去的;
② 拖动条上的滑块可以通过设置 android:thumb=”@null” 去掉;
③ SeekBar获取焦点时会在进度条中间固定位置存在一个焦点阴影,设置SeekBar背景为透明即可去除;
④ 亮度的图标是在阿里矢量图标库中下载导入的。
- AS导入阿里矢量图标
- 在官方阿里矢量图标库中找到需要的图标点击下载,在弹窗中选好图标的颜色,点击SVG下载图标到本地;
- 打开AS,右击drawable文件夹 —> New —> Vector Asset;
在弹窗中选择本地文件Local file,命名icon图标,Path选择下载到本地的SVG图标,Opacity可以调节图标的透明度,设置好后点击完成,AS就将SVG格式的图标转换为我们可以使用的xml格式了
用上述的方法可以再添加一个音量的图标volume_icon.xml,方便后面测试自定义SeekBar控件。
3. 编写自定义控件CustomSeekBarItem类
说白了,CustomSeekBarItem类主要起一个传递作用,如果直接使用自定义控件CustomSeekBarItem是无法控制内部添加的控件的,CustomSeekBarItem就是为了在自定义控件上设置的属性值(如SeekBar的progress、TextView的text)可以传递到内部控件上生效。
普通控件属性的设置方式有两种:① xml中直接设置控件属性值;② 代码中通过方法设置控件属性值。自定义控件也一样的。
首先定义CustomSeekBarItem在xml中的属性值 <values/attrs.xml>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--设置界面的item属性 左边图标 右边文字-->
<declare-styleable name="CustomSeekBarItem">
<attr name="left_icon" selectOne="dimension"/>
<attr name="is_show_right_text" selectOne="boolean" />
<attr name="right_text" selectOne="string" />
<attr name="right_text_color" selectOne="color" />
</declare-styleable>
</resources>
然后在CustomSeekBarItem类中要绑定CustomSeekBarItem.xml布局,绑定并初始化CustomSeekBarItem设置的属性attr,还要写一些接口用来传递控件的属性值 <CustomSeekBarItem.kt>
class CustomSeekBarItem(context : Context, attrs : AttributeSet) : ConstraintLayout(context, attrs) {
private var mLeftIcon : Drawable? = null
private var mIsShowRightText : Boolean? = null
private var mRightText : String? = null
private var mRightTextColor = 0
init {
initAttrs(context, attrs)
// inflate的作用是将xml文件转换成View对象,加载到当前类所继承的 ViewGroup 中,以便在界面上显示
inflate(context, R.layout.activity_custom_seek_bar_item, this)
// 初始化xml中设置的属性
mLeftIcon?.let {
seekbar_icon?.background = mLeftIcon
}
mIsShowRightText?.let {
if (it) {
seekbar_progress?.visibility = View.VISIBLE
} else {
seekbar_progress?.visibility = View.GONE
}
}
mRightText?.let{
seekbar_progress.text = "$it%"
seek_bar.progress = it.toInt()
}
if (mRightTextColor != 0){
seekbar_progress.setTextColor(mRightTextColor)
}
}
@SuppressLint("Recycle")
private fun initAttrs(context: Context, attrs: AttributeSet) {
// 获取并绑定 attr 属性
val typeArray = context.obtainStyledAttributes(attrs, R.styleable.CustomSeekBarItem)
// 左侧View属性
mLeftIcon = typeArray.getDrawable(R.styleable.CustomSeekBarItem_left_icon)
// 右侧TextView属性
mIsShowRightText = typeArray.getBoolean(R.styleable.CustomSeekBarItem_is_show_right_text, true)
mRightText = typeArray.getString(R.styleable.CustomSeekBarItem_right_text)
mRightTextColor = typeArray.getColor(R.styleable.CustomSeekBarItem_right_text_color, 0)
}
// 通过代码设置属性的接口
// progress 数值
fun getProgress(): Int {
return seek_bar?.progress ?: 0
}
fun setProgress(progress: Int) {
seek_bar?.progress = progress
}
// 左侧功能图标
fun setLeftIcon(icon : Int){
seekbar_icon?.setBackgroundResource(icon)
}
// 右侧显示的进度值
fun setRightTextVisible(isShow: Boolean) {
if (isShow) {
seekbar_progress?.visibility = View.VISIBLE
} else {
seekbar_progress?.visibility = View.GONE
}
}
fun setRightText(progress: Int) {
seekbar_progress?.text = "$progress%"
seek_bar.progress = progress
}
fun setRightTextColor(color: Int) {
seekbar_progress?.setTextColor(color)
}
// SeekBar监听器
fun setCustomOnSeekBarChangListener(listener: SeekBar.OnSeekBarChangeListener?){
seek_bar?.setOnSeekBarChangeListener(listener)
}
}
- CustomSeekBarItem只是将多个控件包裹在一起,并不是控件使用者, 这里设置监听的作用也是起一个传递,SeekBar监听需要在使用自定义控件CustomSeekBarItem的地方设置监听,在使用的Activity中继承SeeKbar的监听事件,将事件传到自定义CustomSeekBarItem类中,在这个包裹中将监听设置到内部SeekBar控件上,然后在使用的Activity中重写SeekBar的三个事件监听方法,根据需求在监听到指定事件时做逻辑处理。
4. 自定义控件CustomSeekBarItem的使用
在Activity的xml中添加自定义控件CustomSeekBarItem <activity_seek_bar.xml>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SeekBarActivity">
<com.example.bardemo.CustomSeekBarItem
android:id="@+id/custom_seek_bar_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
// 自定义控件的属性设置
app:left_icon="@drawable/volume_icon"
app:is_show_right_text="true"
app:right_text_color="#fff"
app:right_text="88"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
在Activity中使用CustomSeekBarItem自定义控件 <SeekBarActivity.kt>
class SeekBarActivity : AppCompatActivity() , SeekBar.OnSeekBarChangeListener{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_seek_bar)
initView()
}
private fun initView(){
custom_seek_bar_item.setCustomOnSeekBarChangListener(this)
// 代码设置CustomSeekBarItem属性
custom_seek_bar_item.setLeftIcon(R.drawable.brightness_icon)
custom_seek_bar_item.setRightTextVisible(true)
custom_seek_bar_item.setRightText(33)
custom_seek_bar_item.setRightTextColor(Color.rgb(255,0,0))
// custom_seek_bar_item.setRightTextColor(Color.parseColor("#00ff00"))
}
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
Log.d("====", " onProgressChanged progress = $progress ====")
custom_seek_bar_item.setRightText(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
Log.d("====", " onStartTrackingTouch ====")
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
Log.d("====", " onStopTrackingTouch ====")
}
}
右侧进度条数值显示的TextView的字体颜色设置也可以传入 Color.parseColor(“#00ff00”) ,需要注意的是,这里传入的颜色代码需要6位数值,如果传3位 (“#0f0”) 程序会崩溃!
同一个Activity中有多个SeekBar控件的事件监听
如果同一个Activity中要添加多个CustomSeekBarItem,如果在Acticity中继承SeekBar的OnSeekBarChangeListener会导致一个问题,拖动一个SeekBar的进度,其他的SeekBar也会跟着动,如果要实现每个SeekBar的事件监听分离的话就不能在Activity中继承SeekBar的监听了,需要创建多个SeekBar监听器;
修改Activity代码 <SeekBarActivity.kt>
class SeekBarActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_seek_bar)
initView()
}
private fun initView(){
custom_seek_bar_item.setCustomOnSeekBarChangListener(object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
Log.d("====", " onProgressChanged progress = $progress ==== ")
custom_seek_bar_item.setRightText(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
Log.d("====", " onStartTrackingTouch ====")
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
Log.d("====", " onStopTrackingTouch ====")
}
})
custom_seek_bar_item2.setCustomOnSeekBarChangListener(object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
Log.d("====", " onProgressChanged progress = $progress ==== ")
custom_seek_bar_item2.setRightText(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
Log.d("====", " onStartTrackingTouch ====")
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
Log.d("====", " onStopTrackingTouch ====")
}
})
// 代码设置CustomSeekBarItem属性
custom_seek_bar_item.setLeftIcon(R.drawable.brightness_icon)
custom_seek_bar_item.setRightTextVisible(true)
custom_seek_bar_item.setRightText(33)
custom_seek_bar_item.setRightTextColor(Color.parseColor("#00ff00"))
}