欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-01-15 15:51:22
1.写在前面 seekbar控件在开发中还是比较常见的,比如音视频进度、音量调节等,但是原生控件有时还不能满足我们的需求,今天就来学习一下如何自定义seekbar控件...

1.写在前面

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

看下最终效果:

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

indicatorseekbar

2.实现

indicatorseekbar

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方法:

@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坐标,计算方式与上文中进度百分比文字的计算方式一致:

// 【总宽度 * 进度百分比 -(指示器宽度 - 滑块宽度)/ 2 - 滑块宽度 * 进度百分比】
float indicatoroffset = getwidth() * progressratio - (mindicatorwidth - mthumbwidth) / 2 - mthumbwidth * progressratio;
mindicatorseekbarchangelistener.onprogresschanged(this, getprogress(), indicatoroffset);

看下如何使用:

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);
  }
 });
 }
}

布局文件:

<?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

总结

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