转载请注明出处: http://blog.csdn.net/like_program/article/details/53352899
最终效果图
我们先看下最终效果图:
分析
通过上面的效果图,我们可以分析出:音频条形图,是由一个个矩形组成的,每个矩形的宽度相同,高度不同。每个矩形之间的距离相同。知道了这些,也就好办了。
绘制音频条形图
打开 Android Studio,新建 VolumeViewTest 项目。
新建 VolumeView.java ,继承自 View,代码如下:
package com.example.volumeviewtest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/**
* 自定义 View 音频条形图
*/
public class VolumeView extends View {
/**
* 屏幕宽度
*/
private int mScreenWidth;
/**
* 屏幕的高度
*/
private int mScreenHeight;
/**
* mRectWidth 实际上是相邻矩形左边缘的距离
* 但是把它当作每个矩形的宽度,后面会好理解一些
*/
private int mRectWidth;
/**
* 绘制矩形的画笔
*/
private Paint mPaint;
/**
* 矩形的数量
*/
private int mRectCount = 12;
/**
* 第一个矩形右边缘和第二个矩形左边缘之间的距离
*/
private int offset = 5;
/**
* 随机数
*/
private double mRandom;
public VolumeView(Context context) {
super(context);
}
public VolumeView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public VolumeView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 初始化画笔
*/
private void initPaint() {
// 创建绘制矩形的画笔
mPaint = new Paint();
// 蓝色
mPaint.setColor(Color.BLUE);
}
/**
* 系统计算出控件的大小后,就会回调此方法
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 获取屏幕宽度
mScreenWidth = w;
// 获取屏幕高度
mScreenHeight = h;
// 每个矩形的宽度
mRectWidth = (int) (mScreenWidth * 0.6 / mRectCount);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mRectCount; i++) {
// 生成一个 0 到 1 之间的随机数
mRandom = Math.random();
// 随机生成矩形的高度
float RectHeight = (float) (mScreenHeight * mRandom);
// 绘制矩形
canvas.drawRect(
(float) (mScreenWidth * 0.4 / 2 + mRectWidth * i + offset),
RectHeight,
(float) (mScreenWidth * 0.4 / 2 + mRectWidth * (i + 1)),
mScreenHeight,
mPaint);
}
}
}
我们先创建了画笔,并给画笔设置了颜色。然后我们获取了屏幕宽度和高度,利用屏幕高度和宽度计算出了每个矩形的宽度和高度,最后调用 canvas.drawRect()
方法绘制出了矩形。
关于 canvas.drawRect()
各个参数的含义,如果有不懂的同学,可以看下我的这篇博客:
《Android 群英传》读书笔记:自定义 View – 比例图
里面详细讲解了 canvas.drawRect()
方法各个参数的含义。
接下来,我们把自定义的 VolumeView 写入布局文件 activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/com.example.customview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.customview.MainActivity" >
<!-- 音频条形图 -->
<com.example.customview.VolumeView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
好了,我们运行一下程序:
可以看到,矩形已经绘制出来了。
注意
因为矩形的高度是随机生成的,所以大家运行出来的效果肯定和我运行出来的效果是不一样的。这点要注意下。
加上渐变效果
嗯,矩形已经绘制出来了,但是颜色看上去有点单调,全是蓝色。
刚刚我们看到的最终效果图上的矩形的颜色并不是单调的蓝色,而是从上至下,从黄色过渡到蓝色。
所以接下来我们要给矩形加上渐变效果。使用渐变效果需要使用 LinearGradient。这个类的构造方法如下:
LinearGradient 各个参数含义
/**
* 线性渐变
*
* @param x0 渐变起始点的 x 坐标
* @param y0 渐变起始点的 y 坐标
* @param x1 渐变终点的 x 坐标
* @param y1 渐变终点的 y 坐标
* @param color0 渐变开始颜色
* @param color1 渐变结束颜色
* @param tile 渲染器平铺模式
*/
public LinearGradient (
float x0,
float y0,
float x1,
float y1,
int color0,
int color1,
Shader.TileMode tile)
最后一个参数 tile
前面几个参数都比较好懂,但是最后一个参数 渲染器平铺模式 是个什么鬼?
我们先来看下这段代码,这段代码表示:我们告诉系统从 (0, 0) 到 (100, 300) 之间要渐变,渐变效果是从黄色过渡到蓝色:
LinearGradient linearGradient = new LinearGradient(
0,
0,
100,
300,
Color.YELLOW,
Color.BLUE,
Shader.TileMode.CLAMP);
如果你实际画出的矩形 y 坐标也是 0 到 300,那好说,就按你说的渐变。
但是如果你实际画出的矩形 y 坐标是 0 到 400 呢?
0 到 300 之间好说,就按之前说的从黄色到蓝色渐变,但是 300 到 400 之间是什么颜色呢,你没告诉系统啊。所以为了防止这种情况的发生,就要指定渲染器平铺模式了。
渲染器平铺模式指定为 Shader.TileMode.CLAMP
,系统会将边缘(y 坐标 300)颜色(蓝色)进行拉伸,扩展,从而把 300 到 400 之间用蓝色填充满。
渲染器平铺模式有 3 种参数可供选择,分别为
- Shader.TileMode.CLAMP
如果渲染器超出原始边界范围,则会复制边缘颜色对超出范围的区域进行着色
- Shader.TileMode.REPEAT
在横向和纵向上以平铺的形式重复渲染位图
- Shader.TileMode.MIRROR
横向和纵向上以镜像的方式重复渲染位图
关于这三个参数的区别大家可以看下下面的三个链接,这里就不多说了。
详解Paint的setShader(Shader shader)
明白了线性渐变的知识后,我们就可以对音频条形图进行改造了,给矩形加上渐变效果。
修改 VolumeView.java 中的 onSizeChanged()
方法,代码如下:
/**
* 线性渐变
*/
private LinearGradient mLinearGradient;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
......
// 从坐标(0,0)到坐标(mRectWidth,mScreenHeight)线性渐变,
// 从黄色渐变到蓝色
mLinearGradient = new LinearGradient(
0,
0,
mRectWidth,
mScreenHeight,
Color.YELLOW,
Color.BLUE,
Shader.TileMode.CLAMP);
// 给画笔设置渐变
mPaint.setShader(mLinearGradient);
}
好了,再运行下程序:
可以看到,音频条形图已经有渐变效果了。
音频条形图动起来
音频条形图已经成功绘制出来了,但是它现在是静态的,所以,接下来,我们要让音频条形图动起来!
想让音频条形图动起来,嗯,这个简单,把音频条形图不停重绘就行了,因为每次绘制的音频条形图的矩形高度是随机的,所以每次绘制的音频条形图都不一样,不停的重绘,这样就达到了动态的效果。
要重绘音频条形图,我们需要调用 invalidate()
方法,调用这个方法之后,系统会回调 onDraw() 方法,这样就达到了重绘的目的。
修改 VolumeView.java 的 onDraw()
方法,代码如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
// 重绘
invalidate();
}
嗯,运行一下看下效果:
我擦,闪的太快了,不行,得让它重绘的慢点。嗯,正巧,有个方法 postInvalidateDelayed()
可以延迟重绘:
public void postInvalidateDelayed (long delayMilliseconds)
delayMilliseconds
表示我们想让 View 延迟多久重绘一次,单位是毫秒。这里我们传入 300,表示每隔 300 毫秒,也就是 0.3 秒重绘一次。
这样每隔 0.3 秒回调一下 onDraw()
方法,就不会闪的太快了。
修改 VolumeView.java 的 onDraw()
方法,代码如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
// 每隔 0.3 秒重绘一次
postInvalidateDelayed(300);
// 重绘
// invalidate();
}
运行一下程序:
好了,音频条形图我们已经成功的绘制出来了。
整个流程
我们来梳理一下整个流程:
应用一启动,就会开始加载自定义控件 VolumeView ,这时候就会执行 public VolumeView(Context context, AttributeSet attrs)
这个构造方法,在这个构造方法中,我们初始化了画笔。
接着,既然要加载自定义控件 VolumeView,那么首先要计算自定义控件 VolumeView 的尺寸。
因为 VolumeView 的 layout_width
和 layout_height
都是 match_parent
,而父控件 LinearLayout 的 layout_width
和 layout_height
也是 match_parent
,所以 VolumeView 就会占满全屏。那么 VolumeView 的尺寸也就计算出来了。
尺寸计算出来了之后,系统就会回调 onSizeChanged()
方法,并将刚刚计算出来的尺寸传给 onSizeChanged()
方法,这样我们就可以在 onSizeChanged()
方法中获取到 VolumeView 的宽度和高度了。
获取到 VolumeView 的宽度和高度之后,我们给画笔设置了线性渐变。这样,等会画笔绘制出的矩形才能有渐变效果。
接着,就要执行 onDraw()
方法,开始我们的绘制工作。
在绘制之前,还要注意一个问题,因为音频条形图的每段音频都不一样,所以矩形的高度也都是随机的,既然是随机的,就需要用到 Math.random()
方法随机生成一个 0 到 1 之间的随机数,用这个随机数去乘以屏幕高度,得到的就是每个矩形不一样的高度。
绘制出了矩形之后,我们就完成了静态音频条形图的效果。
接着,我们要让音频条形图动起来,于是调用 postInvalidateDelayed(300)
方法让 VolumeView 每隔 0.3 秒重绘一次,这样音频条形图就可以动起来了。
最后,贴上 VolumeView 的全部源码:
package com.example.volumeviewtest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
/**
* 自定义 View 音频条形图
*/
public class VolumeView extends View {
/**
* 屏幕宽度
*/
private int mScreenWidth;
/**
* 屏幕的高度
*/
private int mScreenHeight;
/**
* mRectWidth 实际上是相邻矩形左边缘的距离
* 但是把它当作每个矩形的宽度,后面会好理解一些
*/
private int mRectWidth;
/**
* 绘制矩形的画笔
*/
private Paint mPaint;
/**
* 矩形的数量
*/
private int mRectCount = 12;
/**
* 第一个矩形右边缘和第二个矩形左边缘之间的距离
*/
private int offset = 5;
/**
* 随机数
*/
private double mRandom;
/**
* 线性渐变
*/
private LinearGradient mLinearGradient;
public VolumeView(Context context) {
super(context);
}
public VolumeView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public VolumeView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 初始化画笔
*/
private void initPaint() {
// 创建绘制矩形的画笔
mPaint = new Paint();
// 蓝色
mPaint.setColor(Color.BLUE);
}
/**
* 当计算出控件的大小,就要回调此方法
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 获取屏幕宽度
mScreenWidth = w;
// 获取屏幕高度
mScreenHeight = h;
// 每个矩形的宽度
mRectWidth = (int) (mScreenWidth * 0.6 / mRectCount);
// 从坐标(0,0)到坐标(mRectWidth,mScreenHeight)线性渐变,
// 从黄色渐变到蓝色
// 第一个参数表示 渐变起始点的 x 坐标
// 第二个参数表示 渐变起始点的 y 坐标
// 第三个参数表示 渐变终点的 x 坐标
// 第四个参数表示 渐变终点的 y 坐标
// 第五个参数表示 渐变开始颜色
// 第六个参数表示 渐变结束颜色
// 第七个参数表示 渲染器平铺模式
mLinearGradient = new LinearGradient(
0,
0,
mRectWidth,
mScreenHeight,
Color.YELLOW,
Color.BLUE,
Shader.TileMode.CLAMP);
// 给画笔设置渐变
mPaint.setShader(mLinearGradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mRectCount; i++) {
// 生成一个 0 到 1 之间的随机数
mRandom = Math.random();
// 随机生成矩形的高度
float RectHeight = (float) (mScreenHeight * mRandom);
// 绘制矩形
canvas.drawRect(
(float) (mScreenWidth * 0.4 / 2 + mRectWidth * i + offset),
RectHeight,
(float) (mScreenWidth * 0.4 / 2 + mRectWidth * (i + 1)),
mScreenHeight,
mPaint);
}
// 每隔 0.3 秒重绘一次
postInvalidateDelayed(300);
// 重绘
// invalidate();
}
}