Android自定义view----音乐播放动画

作者: chaohx | 来源:发表于2017-09-20 13:04 被阅读308次

    先给大家看一下效果,因为我也不知道这个东西具体叫什么,标题上面写的是"音乐播放动画",可能描述的不太准确。

    效果图.gif

    前言

    最近项目中做了一个音频播放的功能,播放条上需要一个如上图的效果。一开始为了赶时间,就用帧动画实现的,但是效果看起来没那么丝滑。今天没啥事,就抽时间用自定义view的方式了一下。看上图的对比,自定义view的实现效果是不是像德芙一样丝滑。

    优势

    自定义view实现的方式比起帧动画的方式,有以下几个优势。
    1.视觉效果更佳。
    2.帧动画需要不同帧的图片,而自定义view的方式使用paint画出来的,更剩资源。
    3.可变化性强,可以通过属性来改变指针的颜色、宽高比、指针个数,从而应用到更多场景。
    4.使用方便,就像使用TextView、Button等。

    怎么使用

    使用非常简单,就像普通的控件一样,在xml配置属性,然后在Activity里,调用start()、stop()来控制开始与暂停。

        <com.chx.voiseplayingicon.VoisePlayingIcon
            android:id="@+id/voise_playint_icon"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_below="@+id/btn_stop"
            android:layout_marginLeft="300dp"
            android:layout_marginTop="50dp"
            android:background="#CCCCCC"
            android:padding="5dp"
            voisePlayingIconAttr:color="#FF6565"
            voisePlayingIconAttr:pointer_num="4"
            voisePlayingIconAttr:pointer_width="5" />
    
    //开始
     voisePlayingIcon.start();
    //停止
    voisePlayingIcon.stop();
    

    实现步骤

    - 思路
    1.自定义view,在画布上依次画几个不同高度的矩形。
    2.开启一个子线程,在这个子线程中写一个死循环,来模拟正弦函数的过程,x轴方向取值范围是0到正无穷。然后去Y轴的绝对值,那么Y轴的取值范围就是0到1。
    3.利用上一步y轴取值来乘以控件高度,改变每个小举行的高度。
    4.刷新画布。
    5.这样就达到了想要的效果,并且小矩形的高度变化速度,是符合正弦变化速率的。
    
    代码实现

    代码不是很多,这里直接贴出代码,具体讲解看代码中的注释。

    
    public class VoisePlayingIcon extends View {
    
        //画笔
        private Paint paint;
    
        //跳动指针的集合
        private List<Pointer> pointers;
    
        //跳动指针的数量
        private int pointerNum;
    
        //逻辑坐标 原点
        private float basePointX;
        private float basePointY;
    
        //指针间的间隙  默认5dp
        private float pointerPadding;
    
        //每个指针的宽度 默认3dp
        private float pointerWidth;
    
        //指针的颜色
        private int pointerColor = Color.RED;
    
        //控制开始/停止
        private boolean isPlaying = false;
    
        //子线程
        private Thread myThread;
    
        //指针波动速率
        private int pointerSpeed;
    
    
        public VoisePlayingIcon(Context context) {
            super(context);
            init();
        }
    
        public VoisePlayingIcon(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            //取出自定义属性
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.voisePlayingIconAttr);
            pointerColor = ta.getColor(R.styleable.voisePlayingIconAttr_color, Color.RED);
            pointerNum = ta.getInt(R.styleable.voisePlayingIconAttr_pointer_num, 4);//指针的数量,默认为4
            pointerWidth = DensityUtils.dp2px(getContext(),
                    ta.getFloat(R.styleable.voisePlayingIconAttr_pointer_width, 5f));//指针的宽度,默认5dp
            pointerSpeed = ta.getInt(R.styleable.voisePlayingIconAttr_pointer_speed, 40);
            init();
        }
    
        public VoisePlayingIcon(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.voisePlayingIconAttr);
            pointerColor = ta.getColor(R.styleable.voisePlayingIconAttr_color, Color.RED);
            pointerNum = ta.getInt(R.styleable.voisePlayingIconAttr_pointer_num, 4);
            pointerWidth = DensityUtils.dp2px(getContext(), ta.getFloat(R.styleable.voisePlayingIconAttr_pointer_width, 5f));
            pointerSpeed = ta.getInt(R.styleable.voisePlayingIconAttr_pointer_speed, 40);
            init();
        }
    
        /**
         * 初始化画笔与指针的集合
         */
        private void init() {
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setColor(pointerColor);
            pointers = new ArrayList<>();
        }
    
    
        /**
         * 在onLayout中做一些,宽高方面的初始化
         *
         * @param changed
         * @param left
         * @param top
         * @param right
         * @param bottom
         */
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            //获取逻辑原点的,也就是画布左下角的坐标。这里减去了paddingBottom的距离
            basePointY = getHeight() - getPaddingBottom();
            Random random = new Random();
            if (pointers != null)
                pointers.clear();
            for (int i = 0; i < pointerNum; i++) {
                //创建指针对象,利用0~1的随机数 乘以 可绘制区域的高度。作为每个指针的初始高度。
                pointers.add(new Pointer((float) (0.1 * (random.nextInt(10) + 1) * (getHeight() - getPaddingBottom() - getPaddingTop()))));
            }
            //计算每个指针之间的间隔  总宽度 - 左右两边的padding - 所有指针占去的宽度  然后再除以间隔的数量
            pointerPadding = (getWidth() - getPaddingLeft() - getPaddingRight() - pointerWidth * pointerNum) / (pointerNum - 1);
        }
    
    
        /**
         * 开始绘画
         *
         * @param canvas
         */
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //将x坐标移动到逻辑原点,也就是左下角
            basePointX = 0f + getPaddingLeft();
            //循环绘制每一个指针。
            for (int i = 0; i < pointers.size(); i++) {
                //绘制指针,也就是绘制矩形
                canvas.drawRect(basePointX,
                        basePointY - pointers.get(i).getHeight(),
                        basePointX + pointerWidth,
                        basePointY,
                        paint);
                basePointX += (pointerPadding + pointerWidth);
            }
        }
    
        /**
         * 开始播放
         */
        public void start() {
            if (!isPlaying) {
                if (myThread == null) {//开启子线程
                    myThread = new Thread(new MyRunnable());
                    myThread.start();
                }
                isPlaying = true;//控制子线程中的循环
            }
        }
    
        /**
         * 停止子线程,并刷新画布
         */
        public void stop() {
            isPlaying = false;
            invalidate();
        }
    
        /**
         * 处理子线程发出来的指令,然后刷新布局
         */
        private Handler myHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                invalidate();
            }
        };
    
        /**
         * 子线程,循环改变每个指针的高度
         */
        public class MyRunnable implements Runnable {
    
            @Override
            public void run() {
    
                for (float i = 0; i < Integer.MAX_VALUE; ) {//创建一个死循环,每循环一次i+0.1
                    try {
                        for (int j = 0; j < pointers.size(); j++) { //循环改变每个指针高度
                            float rate = (float) Math.abs(Math.sin(i + j));//利用正弦有规律的获取0~1的数。
                            pointers.get(j).setHeight((basePointY - getPaddingTop()) * rate); //rate 乘以 可绘制高度,来改变每个指针的高度
                        }
                        Thread.sleep(pointerSpeed);//休眠一下下,可自行调节
                        if (isPlaying) { //控制开始/暂停
                            myHandler.sendEmptyMessage(0);
                            i += 0.1;
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    
        /**
         * 指针类
         */
        public class Pointer {
            private float height;
    
            public Pointer(float height) {
                this.height = height;
            }
    
            public float getHeight() {
                return height;
            }
    
            public void setHeight(float height) {
                this.height = height;
            }
        }
    }
    
    

    自定义属性如下:

    <resources>
        <declare-styleable name="voisePlayingIconAttr">
            <attr name="pointer_color" format="color" />
            <attr name="pointer_num" format="integer" />
            <attr name="pointer_width" format="float" />
            <attr name="pointer_speed" format="integer" />
        </declare-styleable>
    </resources>
    

    这样的效果可以用在音乐播放的播放条,同样也可以当作一个Loading来使用。

    希望该文对大家有一定帮助。如有疑问,欢迎交流。

    这里贴出demo地址:https://github.com/chaohengxing/VoisePlayingIcon.git

    相关文章

      网友评论

      • Magic丶海:Handler不用static标记,可能会引发内存泄露,但是如果标记了就没法在handleMessage里面调用invalidate()方法了,该如何处理
        chaohx:@Magic丶海 网上搜一下,避免Habdler内存泄露的方法有很多,弱引用什么的。
      • KevinWen:好像是叫频谱
        chaohx:原来如此 多谢多系:pray:
      • 秦子帅:厉害了,坚持。可以的话关注一下我的公众号。一起努力
        秦子帅:@chaohx 谢谢哥们:innocent:
        chaohx:可以的:smile:

      本文标题:Android自定义view----音乐播放动画

      本文链接:https://www.haomeiwen.com/subject/fhmnsxtx.html