Andriod 自定义控件之音频条
今天我们实现一个直接继承于View的全新控件。大家都知道音乐播放器吧,在点击一首歌进行播放时,通常会有一块区域用于显示音频条,我们今天就来学习下,播放器音频条的实现。
首先我们还是先定义一个类,直接继承于View,并重写它的构造方法,并初始化一个画笔,这和上一节是同样的道理。直接贴出代码:
public class AudioBar extends View{
private Paint mTextPaint;
public AudioBar(Context context) {
this(context,null);
}
public AudioBar(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public AudioBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mTextPaint = new TextPaint();
mTextPaint.setColor(Color.RED);
}
}
然后同样的道理,想要定义我们自己的View控件,我们需要重写View的onDraw()方法。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
有听过或是播放音乐的伙伴大都知道音频条是什么样子的,无非就是来回跳动的不同竖形图,在这里我们稍微转换下思想就知道,在我们android中可以以竖形矩形来实现,各个矩形之间以固定的间距分割开来就能模仿实现我们的目标控件-音频条。先贴出代码,稍候看代码解释:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
width = getMeasuredWidth() ;
height = getMeasuredHeight();
int mRectCount = 0;
for(int count = 5 ; count < width ;count += mRectWidth){
mRectCount ++ ;
}
for(int i = 0 ; i < mRectCount ; i ++){
double mRandom = Math.random();
mRectHeight = (float) (height * mRandom);
canvas.drawRect(offset + mRectWidth * i,
mRectHeight,
mRectWidth * (i+1),
height,
mTextPaint);
}
}
好,来看下这段代码,首先是我们先获取手机频幕的尺寸大小,然后我会根据手机频幕尺寸和预先定义出的矩形宽度(这里使用mRectWidth变量)来计算出当前手机频幕可以容纳多少个矩形(使用mRectCount 来计数)。然后通过循环创建矩形的方式,让系统给我们画出我们所定义的视图。当然这里我还随机产生了一个随机数,用于控制矩形的高度。
ok,把它加入到我们的布局文件中,并在Activity中显示出来看看是什么效果吧:
activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.sanhuimusic.mycustomview.MainActivity">
<com.sanhuimusic.mycustomview.view.AudioBar
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
然后MainActivity类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
现在运行程序看看效果吧。
是不是很酷呢?但是有伙伴该有疑问了,音频条不都是动态的吗?现在我们实现的只是静态的矩形条呀,别急,我们现在让它动起来,但是该怎么实现呢?
有经验的伙伴都知道,我们所使用或定义的UI视图都是在onDraw()绘制完成之后在Activity中显示出来的,那么我们要实现动态的视图是不是可以不停的调用该方法呢?又有什么方法可以不停的调用它使它不停的绘制呢?答案显而易见,使用invalidate();方法,它可以不停的重新绘制View。因为使用invalidate();间隔太短,速度太快,所以根据我们的需求,我们可以使用延迟的方法重绘View,在这里我们使用postInvalidateDelayed(500);让它500毫秒重画一次,这样就可以体现了动态的音频条。大家可以试下,动态图不太会搞,我就不贴图了,你可以跑下程序了。
ok,现在已基本符合我们的要求了,是不是送了一口气呢,还没有,你有没有试试在layout文件中为我们自定义的控件添加padding属性呢,试试吧。哈哈,是不是也木有任何改变呢?
那是因为我们在onDraw()方法中没有考虑到这一情况的发生。在自定义控件中,直接继承View时,必须要考虑到padding属性对控件的影响,所以接下来,让我们的控件贴近原生控件吧。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int leftPadding = getPaddingLeft();
int topPadding = getPaddingTop();
int rightPadding = getPaddingRight();
int bottomPadding = getPaddingBottom();
width = getMeasuredWidth() - leftPadding - rightPadding;
height = getMeasuredHeight()- topPadding - bottomPadding;
int mRectCount = 0;
for(int count = 5 ; count < width ;count += mRectWidth){
mRectCount ++ ;
}
for(int i = 0 ; i < mRectCount ; i ++){
double mRandom = Math.random();
mRectHeight = (float) (height * mRandom);
canvas.drawRect(offset + mRectWidth * i,
mRectHeight,
mRectWidth * (i+1),
height,
mTextPaint);
}
postInvalidateDelayed(500);
}
也相当的好理解,根据当前情景对padding属性进行控制一下就ok了,小伙伴们现在赶紧在运行试试吧。
到这里整个自定义控件已差不多完成,但是细心的伙伴可能会发现:我们制作的音频条不可能占据整个频幕呀,嘿嘿,这个比较简单,我们通常的做法是修改一下布局文件不就行喽,好,修改如下:
activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.sanhuimusic.mycustomview.MainActivity">
<com.sanhuimusic.mycustomview.view.AudioBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
ok,大功告成,在运行下,试试。
我倒,怎么完全没有木有变化啊,检查检查,还是木有问题,到底是哪个出问题了呢,我想你该蒙了。
这时候你该通过搜索或是书籍查询了,(10秒钟以后,哈哈)通过了解你大概明白了问题所在,View的工作流程是在onDraw绘制之前,是需要先测量布局的,这里引入了两个名词,测量,和布局。后面我想针对View的工作流程专门做一节学习,所以,我们现在只需要先了解下View测量的工作是在哪进行的。
好,经过查询资料,我们了解到,View的测量工作是在onMeasure()方法中进行的。接下来让我们看看它到底是怎么测量的,而我们在当前场景下使用wrap_content为什么没有效果?带着问题,我们先重写View的onMeasure()方法,如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
然后跟到super.onMeasure(widthMeasureSpec, heightMeasureSpec);源码中,我们所看到的源码很简单,如下,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在方法体中只是调用了setMeasuredDimension();方法来决定View尺寸的,再看它里面的参数是通过getDefaultSize()方法获取大小,再次跟进getDefaultSize()方法中。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
ok,很简单的源码,主要通过MeasureSpec类(后面会详细讲解)获取测量的模式和测量的大小,然后通过测量的模式来决定测量的大小,但是有一点是不是很奇怪呢,当测量模式为AT_MOST(最大值模式,对应的是layout宽高属性是wrap_content)时它的测量大小和模式为EXACTLY(精确值模式,对应的是layout宽高属性是match_parent)的测量大小一样呢,因为我们恍然大悟,系统默认的测量大小不管是layout宽高属性是wrap_content还是match_parent它的取值都是match_parent是的默认值。
由此可以明白,我们在修改了layout宽高属性值时,并没有达到我们预期的希望。那该怎么解决呢?其实也很简单,因为,View测量大小的取值取决于setMeasuredDimension()这个方法,因此只要我们重写了setMeasuredDimension()方法,就可以完成我们的需求。因此,我们可以进行如下操作:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(getMeasuredWidth()/2 , getMeasuredHeight()/2);
} else if(widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(getMeasuredWidth()/2 ,heightSpecSize);
} else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize ,getMeasuredHeight()/2);
}
}
代码解释:首先我们分别先得到控件测量的模式和大小,然后根据情况分别识别当前View属性属于哪种情景,再根据具体的情景进行重写了setMeasuredDimension()方法。这里我是让它各显示屏幕的一半。好,来看看现在是否符合了我们的需求。
好了,完全符合需求,可以开心下了。
总结下:当我们直接继承View实现自定义控件时,主要困难点就在于坐标系的计算,计算出正确的坐标,自定义的控件也就完成大半了,另外还有需要针对padding属性和layout_width 和 layout_height属性值为wrap_content的情况进行必要的考虑。好了,今天就说到这里吧。
更多资讯请关注微信平台,有博客更新会及时通知。爱学习爱技术。
Andriod 自定义控件之音频条的更多相关文章
- Andriod 自定义控件之创建可以复用的组合控件
前面已学习了一种自定义控件的实现,是Andriod 自定义控件之音频条,还没学习的同学可以学习下,学习了的同学也要去温习下,一定要自己完全的掌握了,再继续学习,贪多嚼不烂可不是好的学习方法,我们争取学 ...
- Android自定义控件:进度条的四种实现方式(Progress Wheel的解析)
最近一直在学习自定义控件,搜了许多大牛们Blog里分享的小教程,也上GitHub找了一些类似的控件进行学习.发现读起来都不太好懂,就想写这么一篇东西作为学习笔记吧. 一.控件介绍: 进度条在App中非 ...
- Qt编写自定义控件2-进度条标尺
前言 进度条标尺控件的应用场景一般是需要手动拉动进度,上面有标尺可以看到当前进度,类似于qslider控件,其实就是qslider+qprogressbar的杂交版本,不过我才用的是纯qpainter ...
- (三十八)c#Winform自定义控件-圆形进度条-HZHControls
官网 http://www.hzhcontrols.com 前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kww ...
- [转]Android自定义控件:进度条的四种实现方式(Progress Wheel的解析)
最近一直在学习自定义控件,搜了许多大牛们Blog里分享的小教程,也上GitHub找了一些类似的控件进行学习.发现读起来都不太好懂,就想写这么一篇东西作为学习笔记吧. 一.控件介绍: 进度条在App中非 ...
- WPF自定义控件第一 - 进度条控件
本文主要针对WPF新手,高手可以直接忽略,更希望高手们能给出一些更好的实现思路. 前期一个小任务需要实现一个类似含步骤进度条的控件.虽然对于XAML的了解还不是足够深入,还是摸索着做了一个.这篇文章介 ...
- 自定义控件 环形进度条 ProgressBar
使用 public class MainActivity extends Activity implements OnComompleteListener { private int num ...
- (四十六)c#Winform自定义控件-水波进度条-HZHControls
官网 http://www.hzhcontrols.com 前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kww ...
- Android 自定义控件之继承ViewGroup创建新容器
欢迎大家来学习本节内容,前几节我们已经学习了其他几种自定义控件,分别是Andriod 自定义控件之音频条及 Andriod 自定义控件之创建可以复用的组合控件还没有学习的同学请先去学习下,因为本节将使 ...
随机推荐
- js月份,日期加一天
js没有直接可以用的函数,所以只能自己写,其中需要涉及到每个月天数的判断,如果是2月份的话,还要涉及到闰年的判断 var addDate = { //日期,在原有日期基础上,增加days天数,默认增加 ...
- .NET基础拾遗(7)Web Service的开发与应用基础
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基 ...
- 镜像切换Logreader Agent报错:分发数据库中可能存在不一致的状态(续)
报错: 分发数据库中可能存在不一致的状态: dist_backup_lsn {00000030:000001ba:0004},dist_last_lsn {00000030:000001cd:0004 ...
- HTML (超文本标记语言)
<html> --开始标签 <head> 网页上的控制信息 <title>页面标题</title> </head> <body> ...
- ElasticSearch 5学习(7)——分布式集群学习分享2
前面主要学习了ElasticSearch分布式集群的存储过程中集群.节点和分片的知识(ElasticSearch 5学习(6)--分布式集群学习分享1),下面主要分享应对故障的一些实践. 应对故障 前 ...
- mysql大小写敏感与校对规则
大家在使用mysql过程中,可能会遇到类似一下的问题: root@chuck 07:42:00>select * from test where c1 like 'ab%'; +-----+ ...
- 基于HTML5的WebGL应用内存泄露分析
上篇(http://www.hightopo.com/blog/194.html)我们通过定制了CPU和内存展示界面,体验了HT for Web通过定义矢量实现图形绘制与业务数据的代码解耦及绑定联动, ...
- .Net语言 APP开发平台——Smobiler学习日志:如何快速实现地图定位时的地点微调功能
Smobiler是一个在VS环境中使用.Net语言来开发APP的开发平台,也许比Xamarin更方便 样式一 一.目标样式 我们要实现上图中的效果,需要如下的操作: 二.地点微调代码 VB: Dim ...
- C语言中的结构体
用户自己建立自己的结构体类型 1. 定义和使用结构体变量 (1).结构体的定义 C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它称为结构体. (2).声明一个结构体类型的一般形式为: ...
- [Q&A] C1DataGrid 奇葩的 BeginNewRow() 方法
一.前言 用户千千万,自然需求就千奇百怪都有,某天有人提了这样一个需求: 某个 C1DataGrid 在 ScrollViewer 的底部(使纵向滚动条显示出来),然后当该 C1DataGrid 增加 ...