zoukankan      html  css  js  c++  java
  • Android自定义View之旅(二)继承View实现自定义

    继承View是实现自定义View的重要方式,通过自定义属性以实现xml中的便捷使用,通过重写onMeasure和onDraw方法自定义View的绘制过程,通过拦截事件响应完成特定的行为,让想法变为现实。

    上一篇文章Android自定义View之旅(一)自定义View的几种方式

    本文将通过实战来讲述如何通过继承View实现自定义,先仔细看看需求,一个声音波形控件,持续动画效果,单靠上面的简单实现是不可能的了,需要的效果如下:
    在这里插入图片描述

    1、继承View实现自定义View

    在上一篇文章中,我们详细介绍了简单的“自定义”如何实现,继承了安卓系统的原生控件,再在此基础上完成附加的功能样式或者更改原有的功能样式,可以迅速达到想要的效果,缺点是会受到父类的限制、无法完成复杂的需求。

    对于这次需要实现的声音波形控件,我们需要新建一个类VoiceLineView,继承自View

    /**
     * 自定义声音振动曲线view
     */
    public class VoiceLineView extends View {
    
        public VoiceLineView(Context context) {
            super(context);
        }
    
        public VoiceLineView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public VoiceLineView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
    }
    

    2、提供一些自定义的属性

    通过自定义属性以实现xml中的便捷使用,就像我们在使用TextView的时候,设置android:text="Hello World!"

    首先,需要在src/main/res/values文件夹下的attrs.xml文件(如不存在此文件新建一个即可)中新增如下代码:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    	······
    	<!--name为获取属性时使用,不可与其他控件的自定义属性重复-->
        <declare-styleable name="voiceView">
            <!--中间线的颜色,就是波形的时候,大家可以看到,中间有一条直线,就是那个-->
            <attr name="middleLine" format="color" />
            <!--中间线的高度,中间线的宽度是充满的无需设定-->
            <attr name="middleLineHeight" format="dimension" />
            <!--波动的线的颜色-->
            <attr name="voiceLine" format="color" />
            <!--波动线的横向移动速度,线的速度的反比,即这个值越小,线横向移动越快,越大线移动越慢,默认90-->
            <attr name="lineSpeed" format="integer" />
            <!--所输入音量的最大值,默认是100-->
            <attr name="maxVolume" format="float" />
            <!--灵敏度,默认值是4-->
            <attr name="sensibility">
                <enum name="one" value="1" />
                <enum name="two" value="2" />
                <enum name="three" value="3" />
                <enum name="four" value="4" />
                <enum name="five" value="5" />
            </attr>
            <!--精细度,绘制曲线的时候,每几个像素绘制一次,默认是1,一般,这个值越小,曲线越顺滑,但在一些旧手机上,会出现帧率过低的情况,可以把这个值调大一点,在图片的顺滑度与帧率之间做一个取舍-->
            <attr name="fineness">
                <enum name="one" value="1" />
                <enum name="two" value="2" />
                <enum name="three" value="3" />
            </attr>
        </declare-styleable>
        ······
    </resources>
    

    新增一个declare-styleable标签声明一个属性集,根据需要,在declare-styleable标签中增加多个attr标签声明属性,attr标签根据控件的可设置属性进行配置。关于attr自定义属性的类型可以看文章Android中attr属性的类型

    随后在VoiceLineView类中修改代码如下:

    public class VoiceLineView extends View {
    
        private int middleLineColor = Color.BLACK;
        private int voiceLineColor = Color.BLACK;
        private float middleLineHeight = 4;
        private int sensibility = 4;
        private float maxVolume = 100;
        private int fineness = 1;
        private int lineSpeed = 90;
    
        public VoiceLineView(Context context) {
            super(context);
        }
    
        public VoiceLineView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initAtts(context, attrs);
        }
    
        public VoiceLineView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initAtts(context, attrs);
        }
    
        private void initAtts(Context context, AttributeSet attrs) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.voiceView);
            voiceLineColor = typedArray.getColor(R.styleable.voiceView_voiceLine, Color.BLACK);
            maxVolume = typedArray.getFloat(R.styleable.voiceView_maxVolume, 100);
            sensibility = typedArray.getInt(R.styleable.voiceView_sensibility, 4);
            middleLineColor = typedArray.getColor(R.styleable.voiceView_middleLine, Color.BLACK);
            middleLineHeight = typedArray.getDimension(R.styleable.voiceView_middleLineHeight, 4);
            lineSpeed = typedArray.getInt(R.styleable.voiceView_lineSpeed, 90);
            fineness = typedArray.getInt(R.styleable.voiceView_fineness, 1);
            typedArray.recycle();
        }
    
    }
    

    在布局中使用如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <VoiceLineView
            android:id="@+id/voice_line"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            app:middleLine="@color/colorPrimary"
            app:middleLineHeight="1dp"
            app:voiceLine="@color/colorPrimary"
            app:lineSpeed="90"
            app:maxVolume="100"
            app:sensibility="four"
            app:fineness="one" />
    
    </LinearLayout>
    

    3、重写onDraw方法,持续绘制声波

    话不多说,直接上代码:

    public class VoiceLineView extends View {
    	······
    
        private Paint paint;
        private Paint paintVoicLine;
        private float translateX = 0;
        private boolean isSet = false;
        private float amplitude = 1;
        private float volume = 10;
        private long lastTime = 0;
        private int lineSpeed = 90;
        List<Path> paths = null;
    
    	······
    
        public void setVolume(int volume) {
            if (volume > maxVolume * sensibility / 25) {
                isSet = true;
                this.targetVolume = getHeight() * volume / 2 / maxVolume;
            }
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            drawMiddleLine(canvas);
            drawVoiceLine(canvas);
            run();
        }
    
        private void drawMiddleLine(Canvas canvas) {
            if (paint == null) {
                paint = new Paint();
                paint.setColor(middleLineColor);
                paint.setAntiAlias(true);
            }
            canvas.save();
            canvas.drawRect(0, getHeight() / 2 - middleLineHeight / 2, getWidth(), getHeight() / 2 + middleLineHeight / 2, paint);
            canvas.restore();
        }
    
        private void drawVoiceLine(Canvas canvas) {
            lineChange();
            if (paintVoicLine == null) {
                paintVoicLine = new Paint();
                paintVoicLine.setColor(voiceLineColor);
                paintVoicLine.setAntiAlias(true);
                paintVoicLine.setStyle(Paint.Style.STROKE);
                paintVoicLine.setStrokeWidth(2);
            }
            canvas.save();
            if (paths == null) {
                paths = new ArrayList<>(20);
                for (int i = 0; i < 20; i++) {
                    paths.add(new Path());
                }
            }
            int moveY = getHeight() / 2;
            for (int i = 0; i < paths.size(); i++) {
                paths.get(i).reset();
                paths.get(i).moveTo(getWidth(), getHeight() / 2);
            }
            for (float i = getWidth() - 1; i >= 0; i -= fineness) {
                amplitude = 4 * volume * i / getWidth() - 4 * volume * i * i / getWidth() / getWidth();
                for (int n = 1; n <= paths.size(); n++) {
                    float sin = amplitude * (float) Math.sin((i - Math.pow(1.22, n)) * Math.PI / 180 - translateX);
                    paths.get(n - 1).lineTo(i, (2 * n * sin / paths.size() - 15 * sin / paths.size() + moveY));
                }
            }
            for (int n = 0; n < paths.size(); n++) {
                if (n == paths.size() - 1) {
                    paintVoicLine.setAlpha(255);
                } else {
                    paintVoicLine.setAlpha(n * 130 / paths.size());
                }
                if (paintVoicLine.getAlpha() > 0) {
                    canvas.drawPath(paths.get(n), paintVoicLine);
                }
            }
            canvas.restore();
        }
    
        private void lineChange() {
            if (lastTime == 0) {
                lastTime = System.currentTimeMillis();
                translateX += 1.5;
            } else {
                if (System.currentTimeMillis() - lastTime > lineSpeed) {
                    lastTime = System.currentTimeMillis();
                    translateX += 1.5;
                } else {
                    return;
                }
            }
            if (volume < targetVolume && isSet) {
                volume += getHeight() / 30;
            } else {
                isSet = false;
                if (volume <= 10) {
                    volume = 10;
                } else {
                    if (volume < getHeight() / 30) {
                        volume -= getHeight() / 60;
                    } else {
                        volume -= getHeight() / 30;
                    }
                }
            }
        }
    
        public void run() {
            invalidate();
        }
    
    }
    

    到此,需求就已经完成啦,使用时按第2点加入布局,代码中使用:

    VoiceLineView voiceLine = findViewById(R.id.voice_line);
    
    // 循环设置音量即可绘制声波图
    voiceLine.setVolume(volume);
    
  • 相关阅读:
    maven 创建web项目出错
    poj1699--Best Sequence(dfs+剪枝)
    HDU-1042-N!(Java大法好 &amp;&amp; HDU大数水题)
    “XXX.Index”不扩展类“System.Web.UI.Page”,因此此处不同意的问题
    scala模式匹配
    scala匿名函数
    scala特质
    group by的使用
    liux之我用过的zip解压命令
    liunx之zip格式的解压命令
  • 原文地址:https://www.cnblogs.com/hwb04160011/p/13960621.html
Copyright © 2011-2022 走看看