1. 程式人生 > >高仿手機QQ音樂之——Android帶進度條的開關

高仿手機QQ音樂之——Android帶進度條的開關

最新版的手機QQ音樂體驗確實不錯,發現首頁播放按鈕可以顯示歌曲當前進度條,覺得挺有新意!效果如下:

自己琢磨了下,可以用自定義元件來實現,試著做了一下,效果如下:

這裡寫圖片描述
整理了下思路,大概設計流程是這樣的:
首先,要實現音樂的關停,第一首選就是toggleButton 可以方便的控制,因此元件繼承自toggleButton然後我們根據toggleButton的 isChecked的狀態來 重寫onDraw()方法,來繪製我們所需求的形態:
暫停狀態 播放狀態
左邊的就是按鈕的暫停狀態,右邊就是播放狀態,最後在外圍畫一個圓標示播放的總體進度,畫一段圓弧(圓弧寬度要比外圍的圓寬,才能看到效果)標示當前進度,至此,構思完成,開始寫程式碼!
首先,此元件應該有這樣三個屬性: 主體顏色,第一進度條寬度,總進度條寬度。我們先在新建一個專案 在專案res目錄下的values資料夾中建一個 attrs.xml檔案 在裡面寫入我們的view屬性:
attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ProgressToggleButton">
        <attr name="progress_total_width" format="dimension" />
        <attr name="progress_current_width" format="dimension" />
        <attr name="main_color" format
="color" />
</declare-styleable> </resources>

然後我們在src目錄下的包中建一個ProgressToggleButton 繼承自ToggleButton 並在構造方法中獲得我們自定義的屬性:

public class ProgressToggleButton extends ToggleButton {
    public ProgressToggleButton(Context context) {
        super(context);
    }
    private int mainColor;//主體顏色
private int circleTotalWidth, circleCurrentWidth;//總進度和當前進度的寬度 public ProgressToggleButton(Context context, AttributeSet attrs) { super(context, attrs); TypedArray arry = context.obtainStyledAttributes(attrs, R.styleable.ProgressToggleButton); mainColor = arry.getColor(R.styleable.ProgressToggleButton_main_color, getResources().getColor(R.color.main_color));//前面值就是xml檔案指定的值,後者就是前者未指定的預設值 circleTotalWidth = (int) arry.getDimension( R.styleable.ProgressToggleButton_progress_total_width, getResources().getDimension(R.dimen.progress_total_width)); circleCurrentWidth = (int) arry.getDimension( R.styleable.ProgressToggleButton_progress_current_width, getResources().getDimension(R.dimen.progress_current_width)); arry.recycle();//一定要讓recycle 否則會出問題 } }

然後再重寫onDraw()方法繪出自己要的屬性:

@Override
    protected void onDraw(Canvas canvas) {
        mPaint.setColor(mainColor);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(0);
        int center = getWidth() / 2;// 三角形,圓圈中央
        int sideLength = center / 5 * 4; // 三角形邊長
        if (this.isChecked()) {
            drawPlay(canvas, center, sideLength);//繪製兩條豎線
        } else {
            drawStop(canvas, center, sideLength);//繪製正三角形
        }
        // 最外圍的總進度
        mPaint.setStrokeWidth(circleTotalWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        int radius = center - circleTotalWidth;
        canvas.drawCircle(center, center, radius, mPaint);
        // 最當前進度
        RectF oval = new RectF(center - radius + circleTotalWidth, center
                - radius + circleTotalWidth,
                center + radius - circleTotalWidth, center + radius
                        - circleTotalWidth);
        mPaint.setStrokeWidth(circleCurrentWidth);
        canvas.drawArc(oval, -90, mProgress, false, mPaint);//mProgress指圓弧的度數,這裡就代表了我們的進度
    }

這裡的drawPlay方法沒有給出,等等在詳細程式碼中展示,下面是監聽,
為了方便我們使用,我們重新寫一個介面,在我們使用的時候回撥:

public interface onCheckChangesListener {

        void onchechkchanges(boolean isChecked);
    }

    private onCheckChangesListener listener;

    /**
     * 監聽
     * 
     * @param l
     */
    public void setOnCheckChangesListener(onCheckChangesListener l) {
        this.listener = l;
    }

在構造方法中呼叫自帶的OnCheckChangeLister:

this.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                if (listener != null) {
                    listener.onchechkchanges(isChecked);//調介面中的方法
                }
                postInvalidate();//重新整理介面
            }
        });

好了,來看看先階段的效果:
主佈局檔案:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:progres="http://schemas.android.com/apk/res/com.example.progrestogglebutton"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.progrestogglebutton.weight.ProgressToggleButton
        android:id="@+id/user_div"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_alignParentRight="true"
        android:layout_margin="10dp"
        android:background="@color/transparent_color"
        progres:main_color="#FAA532"
        progres:progress_current_width="5dp"
        progres:progress_total_width="2dp"
        tools:ignore="RtlHardcoded" />

    <com.example.progrestogglebutton.weight.ProgressToggleButton
        android:id="@+id/defult"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        android:background="@color/transparent_color"
        tools:ignore="RtlHardcoded" />

    <com.example.progrestogglebutton.weight.ProgressToggleButton
        android:id="@+id/play"
        android:layout_width="52dp"
        android:layout_height="52dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_margin="10dp"
        android:background="@color/transparent_color"
        tools:ignore="RtlHardcoded" />

    <SeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/play"
        android:max="100"
        android:progress="0" />

</RelativeLayout>

主Activity:

package com.example.progrestogglebutton;

import com.example.progrestogglebutton.weight.ProgressToggleButton;
import com.example.progrestogglebutton.weight.ProgressToggleButton.onCheckChangesListener;

import android.app.Activity;
import android.media.MediaPlayer;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;

public class MainActivity extends Activity {
    private ProgressToggleButton tDefult, tDiv, tplayer;

    private MediaPlayer mMediaPlayer;

    private SeekBar mSeekBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        tDefult = (ProgressToggleButton) findViewById(R.id.defult);
        tDiv = (ProgressToggleButton) findViewById(R.id.user_div);
        tplayer = (ProgressToggleButton) findViewById(R.id.play);
        mSeekBar = (SeekBar) findViewById(R.id.seekbar);
        mMediaPlayer = MediaPlayer.create(MainActivity.this, R.raw.penta_kill);
        tDefult.setOnCheckChangesListener(new onCheckChangesListener() {

            @Override
            public void onchechkchanges(boolean isChecked) {
                Toast.makeText(MainActivity.this, "預設樣式被點選,狀態為" + isChecked,
                        Toast.LENGTH_SHORT).show();
            }
        });
        tDiv.setOnCheckChangesListener(new onCheckChangesListener() {

            @Override
            public void onchechkchanges(boolean isChecked) {
                Toast.makeText(MainActivity.this, "使用者指定樣式被點選,狀態為" + isChecked,
                        Toast.LENGTH_SHORT).show();
            }
        });
        tplayer.setOnCheckChangesListener(new onCheckChangesListener() {

            @Override
            public void onchechkchanges(boolean isChecked) {
                if (musicAsyTask != null) {
                    musicAsyTask.cancel(true);
                }
                musicAsyTask = (MusicAsyTask) new MusicAsyTask().execute();
                if (isChecked) {
                    mMediaPlayer.start();
                } else {
                    mMediaPlayer.pause();
                }
            }
        });
        mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                musicAsyTask = (MusicAsyTask) new MusicAsyTask().execute();
                Log.d("LOG", "finish");

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                if (musicAsyTask != null) {
                    musicAsyTask.cancel(true);
                    musicAsyTask = null;
                    Log.d("LOG", "starttoch");
                }
            }

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress,
                    boolean fromUser) {
                if (fromUser) {
                    Log.d("LOG", progress + "");
                    mMediaPlayer.seekTo((progress * mMediaPlayer.getDuration()) / 100);
                    tplayer.setProgress(progress);
                }

            }
        });
    }

    private MusicAsyTask musicAsyTask;

    public class MusicAsyTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected Void doInBackground(Void... params) {
            publishProgress(getPercent(mMediaPlayer.getCurrentPosition(),
                    mMediaPlayer.getDuration()));
            try {
                Thread.sleep(1000);
                this.doInBackground();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            int progress = values[0];
            tplayer.setProgress(progress);
            mSeekBar.setProgress(progress);
            super.onProgressUpdate(values);
        }

    }

    /**
     * 計算百分比
     * 
     * @param progress
     *            當前進度
     * @param total
     *            總進度
     * @return
     */
    public int getPercent(int progress, int total) {
        double baiy = progress * 1.0;
        double baiz = total * 1.0;
        double fen = baiy / baiz;
        return (int) (fen * 100);
    }

}

來看看效果:
這裡寫圖片描述
細節闡述:
1.在xml檔案中,每個ProgressToggleButton,我都將背景指定為完全透明的 顏色,android:background=”@color/transparent_color”,以覆蓋系統自帶的樣式。

2.左邊的為預設樣式,右邊的在佈局檔案中指定了自己的屬性,這裡要注意的時,如果你要引入自定義屬性,就一定要在xml中引入xmlns:progres=”http://schemas.android.com/apk/res/com.example.progrestogglebutton” 這是呼叫了此佈局檔案的 activity所在的包路徑

3.至於設定進度,實現方案就是 在其中新增setProgress()方法,根據設定的值,來確定第二進度的值,在activity中藉助 handler+thread 或者 asytask 動態獲取 播放媒體時間的進度 動態設定給progressToggleButton即可!

完整元件程式碼:


import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.CompoundButton;
import android.widget.ToggleButton;

import com.example.progrestogglebutton.R;

@SuppressLint("DrawAllocation")
public class ProgressToggleButton extends ToggleButton {
    public interface onCheckChangesListener {

        void onchechkchanges(boolean isChecked);
    }

    private onCheckChangesListener listener;

    /**
     * 監聽
     * 
     * @param l
     */
    public void setOnCheckChangesListener(onCheckChangesListener l) {
        this.listener = l;
    }

    private Paint mPaint;

    private int mainColor;

    private int circleTotalWidth, circleCurrentWidth;

    private int mProgress = 1;// 當前進度

    public ProgressToggleButton(Context context) {
        super(context);
    }

    public ProgressToggleButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        TypedArray arry = context.obtainStyledAttributes(attrs,
                R.styleable.ProgressToggleButton);
        mainColor = arry.getColor(R.styleable.ProgressToggleButton_main_color,
                getResources().getColor(R.color.main_color));
        circleTotalWidth = (int) arry.getDimension(
                R.styleable.ProgressToggleButton_progress_total_width,
                getResources().getDimension(R.dimen.progress_total_width));
        circleCurrentWidth = (int) arry.getDimension(
                R.styleable.ProgressToggleButton_progress_current_width,
                getResources().getDimension(R.dimen.progress_current_width));
        arry.recycle();
        this.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                if (listener != null) {
                    listener.onchechkchanges(isChecked);
                }
                postInvalidate();
            }
        });
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setColor(mainColor);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(0);
        int center = getWidth() / 2;// 三角形,圓圈中央
        int sideLength = center / 5 * 4; // 三角形邊長
        if (this.isChecked()) {
            drawPlay(canvas, center, sideLength);
        } else {
            drawStop(canvas, center, sideLength);
        }
        // 最外圍的總進度
        mPaint.setStrokeWidth(circleTotalWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        int radius = center - circleTotalWidth;
        canvas.drawCircle(center, center, radius, mPaint);
        // 最當前進度
        RectF oval = new RectF(center - radius + circleTotalWidth, center
                - radius + circleTotalWidth,
                center + radius - circleTotalWidth, center + radius
                        - circleTotalWidth);
        mPaint.setStrokeWidth(circleCurrentWidth);
        canvas.drawArc(oval, -90, mProgress, false, mPaint);
    }

    /**
     * 畫暫停狀態
     * 
     * @param canvas
     * @param center
     *            三角形中心橫縱座標
     * @param sideLength
     *            三角形邊長
     */
    private void drawStop(Canvas canvas, int center, int sideLength) {
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        float genSan = (float) Math.sqrt(3);
        Path path2 = new Path();
        path2.moveTo((center - sideLength / (2 * genSan)), center - sideLength
                / 2);
        path2.lineTo((center + 2 * sideLength / (2 * genSan)), center);
        path2.lineTo((center - sideLength / (2 * genSan)), center + sideLength
                / 2);
        path2.close();
        canvas.drawPath(path2, mPaint);
    }

    /**
     * 畫播放狀態
     * 
     * @param canvas
     * @param center
     *            兩條線的對稱軸中心橫縱座標
     * @param sideLength
     *            線的長度
     */

    private void drawPlay(Canvas canvas, int center, int sideLength) {
        float genSan = (float) Math.sqrt(3);
        float linesWidth = sideLength / 5;
        mPaint.setStrokeWidth(linesWidth);
        canvas.drawLine((center - sideLength / (2 * genSan)) + linesWidth / 2,
                center - sideLength / 2, (center - sideLength / (2 * genSan))
                        + linesWidth / 2, center + sideLength / 2, mPaint);
        canvas.drawLine((center + sideLength / (2 * genSan)) - linesWidth / 2,
                center - sideLength / 2, (center + sideLength / (2 * genSan))
                        - linesWidth / 2, center + sideLength / 2, mPaint);
    }

    /**
     * 設定進度
     * 
     * @param progress
     */

    public void setProgress(int progress) {
        if (progress >= 100) {
            mProgress = 360 ;
        } else {
            mProgress = (int) ((progress + 1) * 3.6);
        }
        postInvalidate();
    }

    public int getProgress() {
        return (int) (mProgress / 3.6);
    }

    // 設定為wrap_content 時的控制元件高寬
    private int defultWidth = (int) getResources().getDimension(
            R.dimen.weidght_size);

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int finalWidth = 0;
        int finaLHeight = 0;
        if (widthMode == MeasureSpec.EXACTLY) {
            finalWidth = widthSize;
        } else {
            finalWidth = (int) (getPaddingLeft() + defultWidth + getPaddingRight());
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            finaLHeight = heightSize;
        } else {
            finaLHeight = (int) (getPaddingTop() + defultWidth + getPaddingBottom());
        }
        setMeasuredDimension(finalWidth, finaLHeight);
    }
}

總結:
1.本人技術有限,又第一次寫部落格,肯定有很多紕漏之處,忘廣大網友批評斧正,共同進步。
2.本元件其實挺簡單,核心就是自定義元件,但是在研究過程中,那個畫三角形確實花了點時間,要用三角函式來確定座標,最終還是弄出來了,所以在這裡分享出來,分享才是一種快樂。