《Android 群英传》读书笔记:自定义 View -- 音频条形图

版权声明:本文为 like_program 原创文章,未经博主允许不得转载。 https://blog.csdn.net/like_program/article/details/53352899

转载请注明出处: 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

横向和纵向上以镜像的方式重复渲染位图

关于这三个参数的区别大家可以看下下面的三个链接,这里就不多说了。

使用BitmapShader画圆角图形

Android UI效果之绘图篇(三)

详解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_widthlayout_height 都是 match_parent,而父控件 LinearLayout 的 layout_widthlayout_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();
    }
}

源码下载

猜你喜欢

转载自blog.csdn.net/like_program/article/details/53352899