Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義控件---繼承ProgressBar功能擴展

Android自定義控件---繼承ProgressBar功能擴展

編輯:關於Android編程

一、前言

前一段時間在做視頻開發,由於本人剛接觸視頻開發這塊,所以領導沒有對我提很高的要求,僅僅要求能夠播放本地視頻即可。我想怎麼簡單怎麼做。於是選擇用Android VideoView控件來播放視頻(後面發現VideoView的靈活性實在太差,我不想吐槽)。   最終的效果圖:
\

視頻全屏效果
\

這次的任務主要難度在於進度條這個控件。各位可以從上面的兩張圖中看到,進度條被分 為三段。每段表示一個視頻,並且每個視頻的長度不一,也就意味著每段視頻進度條的前進速度是不相同的。     難點總結: 1、自定義控件進度條的繪制 2、如何計算視頻的當前進度 3、如何控制進度條(線程)的暫停,開啟。     知識點總結: 1、Android View的繪制流程。 2、Java線程。 3、Android Canvas類的基本用法。 4、VideoView和ProgressBar的基本用法。 5、Activity生命周期。  

 

二、自定義CustomProgressBar控件 1、創建一個CustomProgress類繼承ProgressBar
//繪制進度條的X,Y坐標
private int marginXY = 0;
//進度條背景,進度條,分隔線的paint
private Paint progressbarBackgroundPaint, progressbarPaint, separtatedPaint;
//視頻總數
private int videoSum = 6;
//畫筆的寬度
private int strokeWidth = 20;
//進度條線程實例
private CustomProgressRunnable customProgressRunnable;
//進度條繪制標記位
private boolean startDrawProgress = false;
//進度條的背景顏色
private int background;
//分隔線的顏色
private int separtatedLineBackground;
//進度條的顏色
private int progressbarBackground;
//分隔線的寬度
private int separtatedLineWidth;
//線程池
private ExecutorService executorService;

這裡有個變量需要特別注意的就是strokeWidth。這是一個指定畫筆寬度的變量。後面繪制的進度條實質上是 一條線,但是一條線太細了,所以我把這條線的寬度設寬一點,默認設置為20。這樣這條線看起來就像進度條了。   2、構造方法初始化
public CustomProgress(Context context , AttributeSet attrs, int defStyleAttr) {
super (context, attrs , defStyleAttr);

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomProgress) ;
videoSum = typedArray.getInt(R.styleable.CustomProgress_CP_Video_Sum, 3);
background = typedArray.getColor(R.styleable.CustomProgress_CP_Background, Color.parseColor("#F1F1F1" ));//F1F1F1
separtatedLineBackground = typedArray.getColor(R.styleable.CustomProgress_CP_Separated_Line_Background, Color.parseColor("#D6D6D6" ));
progressbarBackground = typedArray.getColor(R.styleable.CustomProgress_CP_Progressbar_Background, Color.parseColor("#5BD290" ));//5BD290
separtatedLineWidth = typedArray.getDimensionPixelSize(R.styleable.CustomProgress_CP_Separated_Line_Width, 5);
typedArray.recycle() ;

//
progressbarBackgroundPaint = new Paint() ;
progressbarBackgroundPaint .setAntiAlias(true) ;
progressbarBackgroundPaint .setStrokeCap(Paint.Cap.ROUND) ;
progressbarBackgroundPaint .setStrokeWidth(strokeWidth) ;
progressbarBackgroundPaint .setColor(background) ;
//
progressbarPaint = new Paint() ;
progressbarPaint .setAntiAlias(true) ;
progressbarPaint .setStrokeWidth(strokeWidth) ;
progressbarPaint .setStrokeCap(Paint.Cap.ROUND) ;
progressbarPaint .setColor(progressbarBackground) ;

separtatedPaint = new Paint() ;
separtatedPaint .setAntiAlias(true) ;
separtatedPaint .setStrokeWidth(strokeWidth) ;
separtatedPaint .setColor(separtatedLineBackground) ;

executorService = Executors.newCachedThreadPool();
}
在這裡初始化了線程池以及三個筆刷的初始化。 setAntiAlias(true)設置抗鋸齒 setStrokeWidth(strokeWidth)設置筆刷的寬度 setStrokeCap(Paint.Cap.ROUND)設置筆刷末端為半圓形


3、重寫onMeasure

@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = MeasureSpec.getSize(widthMeasureSpec);

    int finalWidth = size + getPaddingLeft() + getPaddingRight();
    int finalHeight = strokeWidth + getPaddingBottom() + getPaddingTop();

setMeasuredDimension(finalWidth , finalHeight);
}
  在初始化完成之後,系統就會調用onMeasure方法。開始測量這個類(View)的大小。 如果在xml文件設置layout_width為match_parent或者wrap_content。int size=MeasureSpec.getSize(widthMeasureSpec)得到的值就是填充父布局的最大值。 如果輸入的是一個准確數值,int size=准確數值。再將size的值用來作為該視圖的寬度。   由進度條的寬度(strokeWidth)作為該視圖的高度。layout_height屬性在XML文件,無論你設置什麼都是無效的! 調用setMeasureDimension(finalWidth,finalHeight)方法設置視圖的最終大小。     4、重寫onDraw方法 說說我個人的繪制思路:
  • 先繪制一個橫線作為進度條的背景。
  • 在進度條的背景位置上,開始繪制進度條(onDraw方法會被不停的調用,進度條看起來就像在移動)。
  • 最後是繪制每段視頻之間的分隔線(也許你會問為什麼不先繪制分隔線,再繪制進度條。這是因為如果先繪制分隔線,那麼後面繪制的進度條就會把分隔線覆蓋掉,所以我把繪制的順序調換了一下)。
  繪制細節說明: 先看看繪制進度條背景的代碼
//開始繪制進度條的X,Y坐標
marginXY = strokeWidth / 2;
//繪制背景進度
canvas.drawLine(marginXY, marginXY, getWidth() - marginXY, marginXY, progressbarBackgroundPaint);

也許很多人不理解marginXY是什麼意思呢?為了方便解釋,請大家再看看下面的圖。
\

strokeWidth的默認值為20,因此它的一半就是marginXY(即值為10)。在這裡我們知道,marginXY表示的是藍色線的橫 縱坐標。因此紅色那個點的坐標就是(marginXY,marginXY)。所以也可以計算出黃色點的坐標就是(getWidth()-marginXY,marginXY). 從這個圖我們也知道,在Android中canvas.drawLine方法繪制直線的時候。它繪制的起始點的縱坐標是從直線的 中間距離開始的,也就是圖中紅色點的位置。有人可以會問,為什麼不是從黑色點開始呢?大家注意一下這條直線 的首位兩端。這是一個半圓形狀的末端。這是我通過progressbarBackgroundPaint .setStrokeCap(Paint.Cap.ROUND) ; (你也可以設置為矩形末端,把參數改為Paint.Cap.SQUARE即可)方法設置的。   根據我個人的推論應該是這樣理解:這一個整個進度條是由N個圓形緊密相連組成了一條直線。這N個圓形都有一個圓心。 很明顯上面圖中的紅色點和黃色點就是第一個圓和最後一個圓的圓心。(如果有錯請勿噴啊。我沒看過源碼,不知道底層的實現。純屬個人推測)   那麼接下來就好辦了,為了能夠在繪制的過程中顯示出首位兩端的半圓形狀。我們在繪制進度條的時候。應該將繪制的起 始點往後挪一點,就是紅色點的坐標(marginXY,marginXY)開始繪制。終點往前挪一點就是黃色點的坐標(getWidth()-marginXY,marginXY)。 調用canvas.drawLine(marginXY,marginXY, getWidth() -marginXY,marginXY, progressbarBackgroundPaint);將進度條繪制出來。   接著看看繪制進度條的代碼
//開始繪制進度條
if (startDrawProgress) {
    //繪制當前進度
    canvas.drawLine(marginXY, marginXY, getProgress(), marginXY, progressbarPaint);
}

startDrawProgress是一個布爾類型的標記位默認為false。目的是為了避免控件在初始化的時候系統調用onDraw方法 將進度條繪制出來(這個時候我的視頻都沒點擊播放呢,把進度條畫出來干啥?)。   在點擊視頻的時候,通過向外提供的方法。修改startDrawProgress的值為true。 就可以執行canvas.drawLine(marginXY,marginXY, getProgress(),marginXY, progressbarPaint);了 這些參數我已經在上面講的很清楚了,在這裡不再敘述。   最後就是分隔線的繪制
//分隔線的X坐標(注意,這一步必須放在“開始繪制進度條”之後,否則繪制的進度條會把分隔線覆蓋。)
        int separtatedLineX = 0;
        for (int i = 0; i < videoSum - 1; i++) {
            //計算分隔線的X坐標
            separtatedLineX += (getWidth() - 2 * marginXY) / videoSum;
            //繪制分隔線
//            canvas.drawLine(marginXY + separtatedLineX - (separtatedLineWidth / 2), marginXY, marginXY + separtatedLineX + (separtatedLineWidth/2), marginXY, separtatedPaint);
//            canvas.drawLine(separtatedLineX  , marginXY, separtatedLineX + separtatedLineWidth , marginXY, separtatedPaint);
            canvas.drawLine(marginXY + separtatedLineX - (separtatedLineWidth / 2), marginXY, marginXY + separtatedLineX + (separtatedLineWidth / 2), marginXY, separtatedPaint);
        }
    }

  

就是separtatedLineX =((getWidth() - 2 *marginXY) / videoSum ) +separtatedLineX; getWidth()表示視圖的寬度,減去上面所說的紅點和黃點兩端的橫坐標,除以videoSum(這個表示視頻的 總數),再加上原來separtatedLineX值,就是視頻分隔線橫坐標。   每次循環之後得到了separtatedLineX就能把分隔線繪制出來了嗎?我嘗試調用 canvas.drawLine(separtatedLineX,marginXY, separtatedLineX + separtatedLineWidth,marginXY, separtatedPaint); (這裡的separtatedLineWidth表示分隔線的寬度)為了看得更清楚,我特意將分隔線的寬度設寬了。: \
很明顯的看到,整個分隔線的總體往左偏了。突然想起來,我們剛才在計算separtatedLineX的時候為了得到每個視頻的橫坐標,把marginXY值給減去了。   那麼現在將marginXY的值加上,改成如下形式 canvas.drawLine(separtatedLineX +marginXY,marginXY, separtatedLineX + separtatedLineWidth +marginXY,marginXY, separtatedPaint); 再看看運行情況 \

雖然分隔線偏差沒有上面那麼離譜但還是沒能完全對齊。是什麼原因導致沒有對齊呢?請看下圖

\

綠色是marginXY的距離,黑色是separtatedLineX的距離。因此 separtatedLineX +marginXY就是紅點的橫坐標。 separtatedLineX +marginXY+ separtatedLineWidth就是藍點的橫坐標。這中間的寬度就是separtatedLineWidth。 可以很明顯的看出。上面的紅點應該要在分隔線的中間位置。如下圖 \
因此我要讓分隔線對齊就應該讓分隔線往左偏移分隔線寬度的二分之一。也就是separtatedLineWidth/2。所以就有 canvas.drawLine(marginXY+ separtatedLineX - (separtatedLineWidth / 2),marginXY,marginXY+ separtatedLineX + (separtatedLineWidth/2),marginXY, separtatedPaint); 運行一下程序 \
分隔線完美對齊!至此整個自定義控件所需要重寫的方法已經寫完了。   現在還要解決如何計算進度條的進度的問題。詳情請看下面!  

 

三、CustomProgressRunnable內部類 CustomProgress類裡面的創建一個CustomProgressRunnable內部類並且讓這個內部內繼承Runnable接口。   1、CustomProgressRunnable內部類的定義如下變量。
private Handler handler;
private Thread thread;
private VideoView videoView;
//視頻進度條的長度
private int perVideoLength;
//當前正在播放視頻的下標(下標從0開始)
private int currentVideoIndex;
//線程正在運行標記位
private boolean running = false;
//線程正在等待標記位
private boolean waiting = false;
其中比較重要的兩個變量就是running和waiting,前者是用來標記線程是否正在運行,後者用來標記線程是否正在等待(暫停)。   2、內部類的構造方法
public CustomProgressRunnable(Handler handler, VideoView videoView, int currentVideoIndex) {
    this.handler = handler;
    this.currentVideoIndex = currentVideoIndex;
    this.perVideoLength = (getWidth() - 2 * marginXY) / videoSum;
    this.videoView = videoView;
    
    thread = new Thread(this);
    //設置進度條的最大進度值
    setMax((currentVideoIndex + 1) * perVideoLength + marginXY);
}
下圖中綠色線
\
就是perVideoLength變量的值。 setMax();這是ProgressBar的內部API。用來設置進度條的最大值。 現在假設我要播放第一個視頻,那麼currentVideoIndex的值就是0(記住currentVideoIndex下標從0開始)。 所以setMax((currentVideoIndex + 1) * perVideoLength + marginXY);如下圖
\
就是第一個視頻所能達到的最大進度值。第二三個視頻,同理。 其它的都是一些簡單的初始化操作,我就不講了。   3、接下來就要回答兩個問題
  • 如何計算視頻的進度?
  • 如何控制進度條的移動?
​ 先計算當前視頻的開始進度的位置。接著根據當前視頻進度是否小於進度條(setMax)的最大值。如果不小於繼續進行循環。 在循環期間,如果用戶進行了相應的操作。例如非運行狀態則跳出循環。等待狀態,則暫停循環。
@Override
public void run() {
    //當前視頻開始的位置
    int currentVideoStartPosition = (currentVideoIndex * perVideoLength);
    while (currentVideoStartPosition < getMax()) {
        synchronized (this) {
            if (!running) {
                break;
            }

            if (waiting) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        execute(currentVideoStartPosition);
    }
}


最後執行execute方法,計算當前進度條位置的邏輯操作。
public void execute(int currentVideoStartPosition) {
            if (running && !waiting) {
                int videoDuration = videoView.getDuration();
                if (videoDuration != -1) {
                    //獲取比例
                    float proportion = (float) videoView.getCurrentPosition() / (float) videoDuration;
                    //計算移動進度
                    int currentVideoPosition = (int) (proportion * perVideoLength);
                    //計算當前最新進度
                    int newestVideoPosition = currentVideoPosition + currentVideoStartPosition + marginXY;
//                CommonTool.showLog("測試:" + newestVideoPosition + "     " + videoView.getCurrentPosition() + "     " + videoView.getDuration() + "     " + proportion + "      " + perVideoLength);
                    handler.sendMessage(Message.obtain(handler, newestVideoPosition));
                }
            }
        }

      

在execute方法中,先判斷一下此時線程的狀態,如果是線程“正在運行”且“非等待”狀態,就往下執行操作。
  1. 先獲取當前視頻的總長度videoDuration = videoView.getDuration()。假設第一個視頻總長度為1000
  2. 計算當前視頻進度比例float proportion = (float) videoView.getCurrentPosition() / (float) videoDuration;獲取到的videoView.getCurrentPosition()假設值為500。得到的視頻進度比例為50%
  3. 將50%乘以下面綠色線的長度。得到當前視頻的坐標currentVideoPosition。
\
4.將視頻的起始坐標+當前坐標+開始繪制進度條的坐標相加,就是當前視頻的最新坐標。int newestVideoPosition =currentVideoPosition + currentVideoStartPosition + marginXY;此時currentVideoStartPosition的值為0,紅色線的marginXY的值, 綠色線是currentVideoPosition的值。
\
6、最後通過handler.sendMessage(Message.obtain(handler, newestVideoPosition));將當前視頻的最新坐標發送給主線程(即UI線程)。   這裡的TestHandler類就是用來接收第6步驟的newestVideoPosition的值。temp變量得到發送過來的坐標值。 再調用setProgress(temp)方法,設置當前進度條的進度。setProgress方法的底層代碼會重新繪制(執行)我們上面說過的onDraw()方法。
class TestHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        int temp = msg.what;
        //每設置一次setProgress的值就會調用onDraw方法
        setProgress(temp);
    }
}

由於execute是在循環裡面,如果循環currentVideoStartPosition < getMax()成立並且if (running && !waiting)條件語句成立。 execute方法都會不停的被調用。進度條也會一直在移動(繪制)。   4、提供線程的相關操作。
//開始線程
public void start() {
    running = true;
    executorService.execute(thread);
}

//掛起線程
public void suspend() {
    if (waiting) {
        return;
    }
    synchronized (this) {
        this.waiting = true;
    }
}

//恢復線程
public void resume() {
    if (!waiting) {
        return;
    }

    synchronized (this) {
        this.waiting = false;
        this.notifyAll();
    }
}

//停止線程
public void stop() {
    if (!running) {
        return;
    }

    synchronized (this) {
        running = false;
    }
}
在這個內部類裡面我向外面提供了四種方法來操作線程。 這些方法都比較簡單。我就略過啦!  

 

四、CustomProgressBar線程功能擴展 寫完了內部類之後,此時的內部類已經具備了操作線程狀態的能力。 現在為了讓用戶更方便的操作線程。咋們還需要回到CustomProgreeBar類中再次封裝內部類的方法。方便外界直接調用。 在CustomProgreeBar類中寫下下面五個方法。
/**
 * 開始進度條
 *
 * @param videoView
 * @param currentVideoIndex
 */
public void startProgress(VideoView videoView, int currentVideoIndex) {
    if (currentVideoIndex > videoSum) {
        Log.e("-------------------->", "當前視頻下標不能大於視頻總數");
        return;
    }

    customProgressBarRunnable = new CustomProgressBarRunnable(new TestHandler(), videoView, currentVideoIndex);
    //開啟一個線程更新進度條
    customProgressBarRunnable.start();
    startDrawProgress = true;
}

public void hangUpThread() {
    if (customProgressBarRunnable == null) {
        return;
    }
    customProgressBarRunnable.suspend();
    Log.e("-------------------->", "掛起線程!");
}

public void recoverThread() {
    if (customProgressBarRunnable == null) {
        return;
    }
    customProgressBarRunnable.resume();
    Log.e("-------------------->", "恢復線程!");
}

public void stopThread() {
    if (customProgressBarRunnable == null) {
        return;
    }
    customProgressBarRunnable.stop();
    Log.e("-------------------->", "停止線程!");
}

/**
 * 關閉線程池
 */
public void closeThreadPool() {
    executorService.shutdown();
}
startProgress從名字就可以看出這是一個開始進度條的方法。當我們點擊視頻的播放按鈕的同時也要調用此方法開始繪制進度條 最後一個closeThreadPool關閉線程池,當播放視頻的頁面被銷毀的時候,通過onDestory回調方法調用關閉線程池。 其它的三個方法從Log中可以看出是掛起,恢復,停止線程等操作。  

 

五、CustomProgressBar控件的使用 1、創建一個名為activity_main的xml文件。具體代碼如下所示。



    


    

        

            

            

            

            

            

            

                

                

                
            
        
    


xml文件預覽圖
\
為了讓整個demo盡量簡單,我刪除了很多沒用的布局,現在的這個布局和我在前面展示的布局不太一樣。   需要特別注意的地方是在xml中使用自定用控件,應該用<包名路徑.自定義控類名 />的方式。 例如CustomProgressBar的控件是在per.edward.ui目錄下的類。 \

因此在xml文件使用的時候就是


另外,在使用CustomProgressBar時,其默認形式是一個圓形進度條(因為這個類是繼承ProgressBar的)。 其中style="?android:attr/progressBarStyleHorizontal"代碼的意思是將一個圓形進度條改為水平形式。   2、首先在res的目錄下創建一個名為raw的文件夾。將准備好的小視頻放在src/main/res/raw目錄裡面。 如下圖: \
一般Android中都支持MP4,3gp等視頻格式。如果你所放的視頻格式不支持,可以去找軟件轉換格式。   3、XML和視頻准備好之後。創建一個名為MainActivity的類並且讓它繼承Activity。
/**
 * 播放視頻頁面
 * Created by Edward on 2016/4/15.
 */
public class MainActivity extends Activity {
    //視頻視圖實例
    private VideoView videoView;
    //視頻路徑列表
    private ArrayList videoUriList;
    //咋們寫的自定義控件
    private CustomProgressBar progressBar;
    //播放視頻按鈕
    private ImageView imagePlayStop;
    //用來顯示當前視頻下標以及視頻的總數
    private TextView txtVideoNumber;
    //當前視頻的下標
    private int currentVideoIndex = 0;
    //是否開啟視頻線程標記位
    private boolean isOpenVideoThread = false;
    //獲取內部類實例
    private CustomProgressBar.CustomProgressBarRunnable customProgressBarRunnable;

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

        videoView = (VideoView) findViewById(R.id.video_view);
        videoUriList = new ArrayList<>();
        progressBar = (CustomProgressBar) findViewById(R.id.progressbar);
        imagePlayStop = (ImageView) findViewById(R.id.image_play_stop);
        txtVideoNumber = (TextView) findViewById(R.id.txt_video_number);

        initData();
        setCallBackListener();
    }

    public void initData() {
        int[] videosId = {R.raw.bbb, R.raw.aaa, R.raw.ccc};
        //獲取raw文件夾內視頻
        for (int i = 0; i < videosId.length; i++) {
            videoUriList.add("android.resource://" + getPackageName() + "/" + videosId[i]);
        }

        //設置視頻段數
        progressBar.setVideoSum(videoUriList.size());
        txtVideoNumber.setText(currentVideoIndex + 1 + "/" + videoUriList.size());
    }

    /**
     * 設置回調監聽事件
     */
    public void setCallBackListener() {
        //視頻播放完畢之後回調此方法,關閉線程
        videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                //將繪制進度條的線程暫停
                progressBar.stopThread();
                //將視頻下標移動下一個
                ++currentVideoIndex;
                //如果沒有視頻
                if (currentVideoIndex > videoUriList.size() - 1) {
                    Toast.makeText(MainActivity.this, "沒有視頻了", Toast.LENGTH_LONG).show();
                    //更換播放視頻按鈕的圖標
                    imagePlayStop.setImageResource(R.mipmap.video_play);
                    //將當前視頻的下標設為0
                    currentVideoIndex = 0;
                    //將開啟線程的標記位設置false
                    isOpenVideoThread = false;
                } else {
                    //每當一個視頻播放結束之後,再播放下一個視頻。
                    openVideoThread(currentVideoIndex);
                    txtVideoNumber.setText(currentVideoIndex + 1 + "/" + videoUriList.size());
                }
            }
        });

        videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                Toast.makeText(MainActivity.this, "視頻播放錯誤!", Toast.LENGTH_LONG).show();
                return false;
            }
        });
    }

    public void onClick(View v) {
        switch (v.getId()) {
            //播放或暫停視頻
            case R.id.image_play_stop:
                imagePlayStop();
                break;

            //下一個視頻
            case R.id.image_netx_video:
                ++currentVideoIndex;
                if (currentVideoIndex > videoUriList.size() - 1) {
                    Toast.makeText(MainActivity.this, "沒有視頻了", Toast.LENGTH_LONG).show();
                    currentVideoIndex = videoUriList.size() - 1;
                } else {
                    isOpenVideoThread = false;
                    progressBar.stopThread();
                    imagePlayStop.setImageResource(R.mipmap.video_stop);

                    txtVideoNumber.setText(currentVideoIndex + 1 + "/" + videoUriList.size());
                    openVideoThread(currentVideoIndex);
                }
                break;

            //上一個視頻
            case R.id.image_prev_video:
                --currentVideoIndex;
                if (currentVideoIndex < 0) {
                    Toast.makeText(MainActivity.this, "沒有視頻了", Toast.LENGTH_LONG).show();
                    ++currentVideoIndex;
                } else {
                    isOpenVideoThread = false;
                    progressBar.stopThread();
                    imagePlayStop.setImageResource(R.mipmap.video_stop);

                    txtVideoNumber.setText(currentVideoIndex + 1 + "/" + videoUriList.size());
                    openVideoThread(currentVideoIndex);
                }
                break;
        }
    }

    /**
     * 視頻播放與暫停
     */
    public void imagePlayStop() {
        txtVideoNumber.setText(currentVideoIndex + 1 + "/" + videoUriList.size());
        //如果沒有開啟視頻(線程),那麼就打開
        if (!isOpenVideoThread) {
            imagePlayStop.setImageResource(R.mipmap.video_stop);
            openVideoThread(currentVideoIndex);
        } else {
            customProgressBarRunnable = progressBar.getCustomProgressBarRunnable();
            //當前視頻線程打開了之後,如果線程處於運行狀態,那麼就掛起,否則恢復線程。
            if (!customProgressBarRunnable.isWaiting()) {
                //掛起進度條的線程
                progressBar.hangUpThread();
                //暫停視頻播放
                videoView.pause();
                imagePlayStop.setImageResource(R.mipmap.video_play);
            } else {
                //恢復進度條的線程
                progressBar.recoverThread();
                //開始視頻播放
                videoView.start();
                imagePlayStop.setImageResource(R.mipmap.video_stop);
            }
        }
    }

    /**
     * 開啟視頻線程
     *
     * @param currentVideoIndex
     */
    public void openVideoThread(int currentVideoIndex) {
        //把線程標記位設為已開啟
        isOpenVideoThread = true;
        //設置uri
        videoView.setVideoURI(Uri.parse(videoUriList.get(currentVideoIndex)));
        //開始視頻播放
        videoView.start();
        //開啟進度條繪制
        progressBar.startProgress(videoView, currentVideoIndex);
    }

    @Override
    protected void onPause() {
        CustomProgressBar.CustomProgressBarRunnable customProgressBarRunnable = progressBar.getCustomProgressBarRunnable();
        //當APP進入“不可見”狀態的時候(例如返回桌面,鎖屏,跳轉到另一個Activity等情況),將視頻暫停
        if (customProgressBarRunnable != null && !customProgressBarRunnable.isWaiting())
            imagePlayStop();
        super.onPause();
    }


    /**
     * 在該頁面被銷毀之前,關閉線程池,停止線程
     */
    @Override
    protected void onDestroy() {
        progressBar.closeThreadPool();
        progressBar.stopThread();
        super.onDestroy();
    }
}

onPause和onDestroy方法。這兩個方法都屬於Activity生命周期的回調方法。
  • 從當前Activity返回桌面,鎖屏,或者跳轉到另一個Activity等情況。就會回調onPause方法,將當前正在播放的視頻暫停(線程掛起)。
  • 如果當前的Activity頁面被銷毀。就會回調onDestroy方法。關閉線程池和停止線程。
  其它代碼雖然比較多,但邏輯比較簡單。無非就是“前進”,“後退”,“播放”三個按鈕的操作。 以及數據的初始化,還有監聽事件的回調等等,略過。   最後效果圖 \

 

六、結束 這篇博客,寫了好幾天,這中間修修改改,為了減少博客的篇幅砍掉了很多代碼和功能,將博客的重點放在自定義控件的講解上。結果最後dem實現的效果與我給公司寫的差別很大。不過沒關系。寫這篇博客主要目的就是為了總結一下近段時間學習Android自定義控件的成果,同時希望這一系列的博客能夠幫助到各位。謝謝!
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved