Android 音量調節View

導語

手機直播一般都會通過移動屏幕來調節音量的大小,本篇只實現了圖例,並不能改變音量
先看效果:
這裏寫圖片描述

需要的素材:小喇叭圖片,點擊這裏獲取
這裏寫圖片描述

預熱

如果你對Path,PathMeasure,RectF,Canvas等不適很瞭解的話,強烈建議看這位哥們的教程:
點擊這裏查看教程
如果你將這哥們的十幾篇帖子都看完了的話,這個View實際上是非常簡單的

步驟介紹

用動態圖來介紹:

這裏寫圖片描述

這裏用文字翻譯下:

  1. 將小喇叭畫到中心位置
  2. 圍繞着喇叭畫一個圓圈,淺色的
  3. 畫一個圓弧,深色的
  4. 根據觸摸的位置來改變圓弧的大小

分解之後,發現並沒有什麼難度(可能本身就沒有什麼難度),下面來看每一步的操作,最後會將整個View的代碼貼出來

繪製小喇叭

這裏寫圖片描述

相關代碼片(不要複製,只是看的)

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //座標移動到中心
    canvas.translate(mViewWidth / 2, mViewHeight / 2);
    //拿到小喇叭圖片
    Bitmap voice = BitmapFactory.decodeResource(getResources(), R.mipmap.voice);
    //獲取圖片的寬高
    int bWidth = voice.getWidth();
    int bHeight = voice.getHeight();
    //移動座標到中心位置
    canvas.drawBitmap(voice, -bWidth / 2, -bHeight / 2, outerCirclePaint);
}

分析
核心:將圖片放到中心位置:
將圖片向上移動高度的一半,向左移動寬度的一半,就可以移動到中心如圖所示:
這裏寫圖片描述

繪製淺色的圓環

這裏寫圖片描述
相關代碼片(不要複製,只是看的)

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    initPaint();
    //座標移動到中心
    canvas.translate(mViewWidth / 2, mViewHeight / 2);

    //底層圓圈
    if (cirPath == null)
        cirPath = new Path();
    if (rectF == null) {
        //半徑選取爲圖片寬度一半的1.3倍,可以調節
        r = (int) (bWidth / 2 * 1.3f);
        rectF = new RectF(-r, -r, r, r);
    }
    cirPath .addArc(rectF, 0, 360);
    //畫底層淺色的圓圈
    canvas.drawPath(cirPath, outerCirclePaint);
}

分析:
這步沒有什麼難度,只是繪製一個圓圈

畫一個深色的圓弧

這裏寫圖片描述
相關代碼片(不要複製,只是看的)

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //座標移動到中心
    canvas.translate(mViewWidth / 2, mViewHeight / 2);
    //音量大小
    if (innerCirclePath == null)
        innerCirclePath = new Path();
    //繪製外層深色的音量弧形
    drawVoicePath(innerCirclePath);
    //畫音量強度
    canvas.drawPath(voicePath, voicePaint);
}



private void drawVoicePath(Path path) {
    if (voiceRectf == null) {
        //與底層圓圈保持一致
        voiceRectf = new RectF(-r, -r, r, r);
        voicePath = new Path();
    }
    voicePath.reset();

    //道聽途說使用359.9可以測量的更準
    path.addArc(voiceRectf, -90, 359.9f);
    //通過PathMeasure來繪製部分的圓圈,表現出來就是繪製了弧形
    PathMeasure measure = new PathMeasure(path, false);
    //獲取圓的總長度
    float length = measure.getLength();
    //根據音量大小來繪製部分的弧形
    measure.getSegment(0, voiceNumber * length, voicePath, true);
}

分析
這段代碼需要了解PathMeasure的用法點擊這裏瞭解
圓弧從上往下繪製,所以:addArc(RectF oval, float startAngle, float sweepAngle)中的第二個參數startAngle爲-90;
圓各部分的對應的Angle:
這裏寫圖片描述
另外,通過可變參數voiceNumber來控制圓弧的大小,用於之後的修改。

觸摸改變圓弧的大小

這裏寫圖片描述

相關代碼片(不要複製,只是看的)

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //記錄起始位置的座標和音量大小
            startY = (int) event.getY();
            oldVoiceNumber = voiceNumber;
            break;
        case MotionEvent.ACTION_MOVE:
            //記錄當前位置的座標
            moveY = (int) event.getY();
            //與起始位置進行比較來確定音量的大小
            changedVoiceNumber();
            break;
        case MotionEvent.ACTION_UP:
            //手指離開屏幕,重置數據
            resetData();
            break;
    }
    //繪製圖形
    invalidate();
    //這裏需要改View處理事件,所以放回true;
    return true;
}

最後一步的代碼比較多,這裏沒有貼完,詳細代碼在最後都會貼出來。
分析:
既然要通過滑動來改變深色圓弧的大小,那麼可定是在onTouchEvent()中來進行相關的操作。
1. 通過滑動,圖形改變,需要重新繪製,所以調用invalidate()
2. 滑動事件需要改View來處理,所以返回值爲true
3. 核心部分是通過ACTION_DOWN,ACTION_MOVE,ACTION_UP的相關操作來實現的

核心部分的大致流程圖是這樣的,如果與後面的代碼有出入,以代碼爲準:
這裏寫圖片描述

完整代碼

這裏可以複製粘貼了

/**
 * Created by Kevin on 2016/8/31.
 */
public class VoiceView extends View {

    //控件的寬高
    private int mViewWidth;
    private int mViewHeight;

    //小喇叭
    private Bitmap voice;
    //小喇叭的寬度
    private int bWidth;
    //小喇叭的高度
    private int bHeight;


    //表示音量大小
    private float voiceNumber = 0.5f;
    //調節之前音量的大小
    private float oldVoiceNumber;
    //開始時的座標
    private int startY;
    //移動後的座標
    private int moveY;
    //圓圈的半徑
    private int r;
    //音量從0-->1所需要移動的距離
    private int voiceChangedY;

    //音量的畫筆
    private Paint voicePaint;
    //頂層圓圈的畫筆
    private Paint outerCirclePaint;

    //音量的Path
    private Path voicePath;
    //頂層圓圈的的Path
    private Path cirPath;
    //音量圓圈的Path
    private Path innerCirclePath;

    //頂層的RectF
    private RectF rectF;
    //音量的RectF
    private RectF voiceRectf;

    public VoiceView(Context context) {
        super(context);
        initBitmap();
    }

    public VoiceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initBitmap();
    }

    public VoiceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initBitmap();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        initPaint();
        //座標移動到中心
        canvas.translate(mViewWidth / 2, mViewHeight / 2);

        //外層圓圈
        if (cirPath == null)
            cirPath = new Path();
        //音量大小
        if (innerCirclePath == null)
            innerCirclePath = new Path();
        //繪製底層淺色圓圈
        drawCirclePath(cirPath);
        //繪製外層深色的音量弧形
        drawVoicePath(innerCirclePath);
        //移動座標到中心位置
        canvas.drawBitmap(voice, -bWidth / 2, -bHeight / 2, outerCirclePaint);
        //畫頂層淺色的圓圈
        canvas.drawPath(cirPath, outerCirclePaint);
        //畫音量強度
        canvas.drawPath(voicePath, voicePaint);
    }

    /**
     * 通過觸摸來改變音量的大小
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //記錄起始位置的座標和音量大小
                startY = (int) event.getY();
                oldVoiceNumber = voiceNumber;
                break;
            case MotionEvent.ACTION_MOVE:
                //記錄當前位置的座標
                moveY = (int) event.getY();
                //與起始位置進行比較來確定音量的大小
                changedVoiceNumber();
                break;
            case MotionEvent.ACTION_UP:
                //手指離開屏幕,重置數據
                resetData();
                break;
        }
        //繪製圖形
        invalidate();
        //這裏需要改View處理事件,所以放回true;
        return true;
    }

    /**
     * 根據起始位置和當前位置來確定音量的大小
     * <p>
     * 一般情況下:音量大小的改變量 = (當前位置 - 起始位置) / 一個固定的長度
     * 註釋:這裏選取的“一個固定的長度”爲高度的一半
     * <p>
     * 極端情況:如果音量調節到1或者0,仍然以最開始的位置作爲起始位置,感覺會很奇怪(可以將resetData()中的的代碼屏蔽來感覺一下);
     * 處理:當爲0或者1時,重置數據
     */
    private void changedVoiceNumber() {
        int changeY = moveY - startY;
        float changedVoice = changeY / (voiceChangedY * 1.0f);
        if (changedVoice > 0) {
            //音量增加
            if (voiceNumber >= 1) {
                voiceNumber = 1;
                resetData();
                return;
            } else {
                float afterChange = oldVoiceNumber + changedVoice;
                if (afterChange >= 1) {
                    voiceNumber = 1;
                    resetData();
                } else {
                    voiceNumber = afterChange;
                }
            }
        } else if (changedVoice < 0) {
            //音量減少
            if (voiceNumber <= 0) {
                voiceNumber = 0;
                resetData();
                return;
            } else {
                float afterChange = oldVoiceNumber + changedVoice;
                if (afterChange <= 0) {
                    voiceNumber = 0;
                    resetData();
                } else {
                    voiceNumber = afterChange;
                }
            }
        } else if (changedVoice == 0) {
            //音量不變
        }
//        String print = String.format("startY-->%d,moveY-->%d,voiceNumber-->%f,changeVoice-->%f", startY, moveY, voiceNumber, changedVoice);
//        Log.e("ddd", print);
    }

    /**
     * 當音量達到0或者1時,重置數據
     */
    private void resetData() {
        startY = moveY;
        oldVoiceNumber = voiceNumber;
    }

    /**
     * 繪製底層層圓圈
     *
     * @param path
     */
    private void drawCirclePath(Path path) {
        if (rectF == null) {
            //半徑選取爲圖片寬度一半的1.3倍,可以調節
            r = (int) (bWidth / 2 * 1.3f);
            rectF = new RectF(-r, -r, r, r);
        }
        path.addArc(rectF, 0, 360);
    }

    /**
     * 繪製音量
     *
     * @param path
     */
    private void drawVoicePath(Path path) {
        if (voiceRectf == null) {
            //與底層圓圈保持一致
            voiceRectf = new RectF(-r, -r, r, r);
            voicePath = new Path();
        }
        voicePath.reset();

        //道聽途說使用359.9可以測量的更準
        path.addArc(voiceRectf, -90, 359.9f);
        //通過PathMeasure來繪製部分的圓圈,表現出來就是繪製了弧形
        PathMeasure measure = new PathMeasure(path, false);
        //獲取圓的總長度
        float length = measure.getLength();
        //根據音量大小來繪製部分的弧形
        measure.getSegment(0, voiceNumber * length, voicePath, true);
    }


    /**
     * 小喇叭
     */
    private void initBitmap() {
        voice = BitmapFactory.decodeResource(getResources(), R.mipmap.voice);
        bWidth = voice.getWidth();
        bHeight = voice.getHeight();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mViewWidth = w;
        mViewHeight = h;
        //音量從0-->1所需要移動的距離
        voiceChangedY = h / 2;
    }


    /**
     * 初始化畫筆
     */
    private void initPaint() {
        int stroke = 20;
        if (outerCirclePaint == null) {
            outerCirclePaint = new Paint();
            outerCirclePaint.setStrokeWidth(stroke);
            outerCirclePaint.setStyle(Paint.Style.STROKE);
            outerCirclePaint.setColor(0x8089cff0);
            outerCirclePaint.setAntiAlias(true);
        }
        if (voicePaint == null) {
            voicePaint = new Paint();
            voicePaint.setStrokeWidth(stroke);
            voicePaint.setStyle(Paint.Style.STROKE);
            voicePaint.setColor(0xff1d8ffe);
            voicePaint.setAntiAlias(true);
        }
    }
}

結語

該View的繪製難度並不大,涉及了不少基礎,當做練習是一個很好的素材。

轉載請標明出處:http://blog.csdn.net/qq_26411333/article/details/52383186#t6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章