今天我们实现一个直接继承于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 自定义控件之音频条的更多相关文章

  1. Andriod 自定义控件之创建可以复用的组合控件

    前面已学习了一种自定义控件的实现,是Andriod 自定义控件之音频条,还没学习的同学可以学习下,学习了的同学也要去温习下,一定要自己完全的掌握了,再继续学习,贪多嚼不烂可不是好的学习方法,我们争取学 ...

  2. Android自定义控件:进度条的四种实现方式(Progress Wheel的解析)

    最近一直在学习自定义控件,搜了许多大牛们Blog里分享的小教程,也上GitHub找了一些类似的控件进行学习.发现读起来都不太好懂,就想写这么一篇东西作为学习笔记吧. 一.控件介绍: 进度条在App中非 ...

  3. Qt编写自定义控件2-进度条标尺

    前言 进度条标尺控件的应用场景一般是需要手动拉动进度,上面有标尺可以看到当前进度,类似于qslider控件,其实就是qslider+qprogressbar的杂交版本,不过我才用的是纯qpainter ...

  4. (三十八)c#Winform自定义控件-圆形进度条-HZHControls

    官网 http://www.hzhcontrols.com 前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kww ...

  5. [转]Android自定义控件:进度条的四种实现方式(Progress Wheel的解析)

    最近一直在学习自定义控件,搜了许多大牛们Blog里分享的小教程,也上GitHub找了一些类似的控件进行学习.发现读起来都不太好懂,就想写这么一篇东西作为学习笔记吧. 一.控件介绍: 进度条在App中非 ...

  6. WPF自定义控件第一 - 进度条控件

    本文主要针对WPF新手,高手可以直接忽略,更希望高手们能给出一些更好的实现思路. 前期一个小任务需要实现一个类似含步骤进度条的控件.虽然对于XAML的了解还不是足够深入,还是摸索着做了一个.这篇文章介 ...

  7. 自定义控件 环形进度条 ProgressBar

    使用 public class MainActivity extends Activity implements OnComompleteListener {     private int num  ...

  8. (四十六)c#Winform自定义控件-水波进度条-HZHControls

    官网 http://www.hzhcontrols.com 前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kww ...

  9. Android 自定义控件之继承ViewGroup创建新容器

    欢迎大家来学习本节内容,前几节我们已经学习了其他几种自定义控件,分别是Andriod 自定义控件之音频条及 Andriod 自定义控件之创建可以复用的组合控件还没有学习的同学请先去学习下,因为本节将使 ...

随机推荐

  1. 性能测试工具 wrk 安装与使用

    介绍 今天给大家介绍一款开源的性能测试工具 wrk,简单易用,没有Load Runner那么复杂,他和 apache benchmark(ab)同属于性能测试工具,但是比 ab 功能更加强大,并且可以 ...

  2. 跨平台的 .NET 运行环境 Mono 3.2 新特性

    Mono 3.2 发布了,对 Mono 3.0 和 2.10 版本的支持不再继续,而且这两个分支也不再提供 bug 修复更新. Mono 3.2 主要新特性: LLVM 更新到 3.2 版本,带来更多 ...

  3. 探索c#之Async、Await剖析

    阅读目录: 基本介绍 基本原理剖析 内部实现剖析 重点注意的地方 总结 基本介绍 Async.Await是net4.x新增的异步编程方式,其目的是为了简化异步程序编写,和之前APM方式简单对比如下. ...

  4. Visual Studio Code预览版Ver 0.3.0试用体验

    当你开始阅读这篇文章时,请先不要把Visual Studio Code和.net.Windows联想到一起,因为VS Code是一个跨平台,支持30多种语言的开箱代码编辑器.不管你是.Net.Java ...

  5. C#对WebApi数据操作

    目标 简化并统一程序获取WebApi对应实体数据的过程,方便对实体进行扩充.原理就是数据服务使用反射发现数据提供者,处理好泛型就行. 相关传送门:Restful WebApi开发实践 先来看下最后的请 ...

  6. Git异常:fatal: could not create work tree dir 'XXX': No such file or directory

    GitHub实战系列汇总:http://www.cnblogs.com/dunitian/p/5038719.html ———————————————————————————————————————— ...

  7. Laravel - 安装与配置

    有多重途径可以安装Laravel,下面是通过composer安装laravel的方法.Composer 是 PHP 的一个依赖管理工具.它允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们.c ...

  8. Chrome浏览器必装的扩展工具

    名称 作用 下载地址 Postman 模拟各种http请求 点击下载 JSON Viewer 访问json结果自动视图化 点击下载 Axure RP Extension for Chrome 查看Ax ...

  9. 利用C#开发移动跨平台Hybrid App(一):从Native端聊Hybrid的实现

    0x00 前言 前一段时间分别读了两篇博客,分别是叶小钗兄的<浅谈Hybrid技术的设计与实现>以及徐磊哥的<从技术经理的角度算一算,如何可以多快好省的做个app>.受到了很多 ...

  10. 关于Quartz.NET作业调度框架的一点小小的封装,实现伪AOP写LOG功能

    Quartz.NET是一个非常强大的作业调度框架,适用于各种定时执行的业务处理等,类似于WINDOWS自带的任务计划程序,其中运用Cron表达式来实现各种定时触发条件是我认为最为惊喜的地方. Quar ...