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 自定义控件之音频条,希望对大家有所帮助
上一篇: array- Median of Two Sorted Arrays
下一篇: 小白鼠试毒模拟