1. 程式人生 > >我的Android進階之旅------>Android如何通過自定義SeekBar來實現視訊播放進度條

我的Android進階之旅------>Android如何通過自定義SeekBar來實現視訊播放進度條


首先來看一下效果圖,如下所示:

其中進度條如下:

接下來說一說我的思路,上面的進度拖動條有自定義的Thumb,在Thumb正上方有一個PopupWindow視窗,窗口裡面顯示當前的播放時間。在SeekBar右邊有一個文字框顯示當前播放時間/總時間

step1、先來看一看PopupWindow的佈局檔案,seek_popu.xml,效果如下圖所示:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/seek_dialog_bg" >
    <!-- 展現當前播放進度時間的文字框-->
    <TextView
        android:id="@+id/dialogSeekTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dip"
        android:layout_marginTop="12dip"
        android:text="@string/unknow_seek_time"
        android:textColor="@color/black"
        android:textSize="12sp" />
</RelativeLayout>

step2、自定義一個SeekBar
import com.canplay.video.R;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.PopupWindow;
import android.widget.SeekBar;
import android.widget.TextView;

/**
 * 自定義進度拖動條控制元件
 */
public class MySeekBar extends SeekBar {
	/**
	 * 定義一個展現時間的PopupWindow
	 */
	private PopupWindow mPopupWindow;
	
	private View mView;
	/**
	 * 顯示時間的TextView
	 */
	private TextView dialogSeekTime;
	/**
	 * 用來表示該元件在整個螢幕內的絕對座標,其中 mPosition[0] 代表X座標,mPosition[1] 代表Y座標。
	 */
	private int[] mPosition;
	/**
	 * SeekBar上的Thumb的寬度,即那個託動的小黃點的寬度
	 */
	private final int mThumbWidth = 25;

	public MySeekBar(Context context) {
		this(context, null);
	}

	public MySeekBar(Context context, AttributeSet attrs) {
		super(context, attrs);
		mView = LayoutInflater.from(context).inflate(R.layout.seek_popu, null);
		dialogSeekTime = (TextView) mView.findViewById(R.id.dialogSeekTime);
		mPopupWindow = new PopupWindow(mView, mView.getWidth(), mView.getHeight(), true);
		mPosition = new int[2];
	}

	/**
	 * 獲取控制元件的寬度
	 * 
	 * @param v
	 * @return 控制元件的寬度
	 */
	private int getViewWidth(View v) {
		int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
		int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
		v.measure(w, h);
		return v.getMeasuredWidth();
	}

	/**
	 * 獲取控制元件的高度
	 * 
	 * @param v
	 * @return 控制元件的高度
	 */
	private int getViewHeight(View v) {
		int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
		int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
		v.measure(w, h);
		return v.getMeasuredHeight();
	}

	/**
	 * 隱藏進度拖動條的PopupWindow
	 */
	public void hideSeekDialog() {
		if (mPopupWindow != null && mPopupWindow.isShowing()) {
			mPopupWindow.dismiss();
		}
	}

	/**
	 * 顯示進度拖動條的PopupWindow
	 * 
	 * @param str
	 *                  時間值
	 */
	public void showSeekDialog(String str) {
		dialogSeekTime.setText(str);
		int progress = this.getProgress();
		// 計算每個進度值所佔的寬度
		int thumb_x = (int) (progress * (1.0f * (this.getWidth() - 22) / this.getMax())); //22是兩邊的空白部分寬度
		// 更新後的PopupWindow的Y座標
		int middle = this.getHeight() / 2 + 120;
		if (mPopupWindow != null) {
			try {
				/*
				 * 獲取在整個螢幕內的絕對座標,注意這個值是要從螢幕頂端算起,也就是包括了通知欄的高度。
				 * 其中 mPosition[0] 代表X座標,mPosition[1]代表Y座標。
				 */
				this.getLocationOnScreen(mPosition);
				// 相對某個控制元件的位置(正左下方),在X、Y方向各有偏移
				mPopupWindow.showAsDropDown(this, (int) mPosition[0], mPosition[1]);
				/*
				 * 更新後的PopupWindow的X座標
				 * 首先要把當前座標值減去PopWindow的寬度的一半,再加上Thumb的寬度一半。
				 * 這樣才能使PopWindow的中心點和Thumb的中心點的X座標相等
				 */
				int x = thumb_x + mPosition[0] - getViewWidth(mView) / 2 + mThumbWidth / 2;
				// 更新popup視窗的位置
				mPopupWindow.update(x, middle, getViewWidth(mView), getViewHeight(mView));
			} catch (Exception e) {
			}
		}
	}
}

step3、將自定義的拖動條加入到佈局檔案中,下面是部分程式碼
 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/black" >
......
<!-- 進度拖動條 -->
        <RelativeLayout
            android:id="@+id/seek_bar_container"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_above="@id/control_btn_container"
            android:background="@drawable/seek_bg" >

            <com.canplay.video.view.MySeekBar
                android:id="@+id/seek_progress"
                android:layout_width="600dip"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true" />

            <TextView
                android:id="@+id/currentTime"
                style="@style/seekTime"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_toRightOf="@id/seek_progress"
                android:paddingLeft="20dip"
                android:text="@string/unknow_time" />
        </RelativeLayout>
...............
</RelativeLayout>

step4、在主檔案中對拖動條進行託動監聽
mSeekBar = (MySeekBar) findViewById(R.id.seek_progress);
mSeekBar.setOnSeekBarChangeListener(mSeekBarListener);
/**
	 * 進度拖動條監聽器
	 */
	private OnSeekBarChangeListener mSeekBarListener = new OnSeekBarChangeListener() {
		// 通知進度已經被修改
		public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
			if (isTouchSeeked) {
				mSeekBar.showSeekDialog(makeTimeString(progress));//動態展示當前播放時間
			} else {
				mSeekBar.hideSeekDialog();
			}
		}

		// 通知使用者已經開始一個觸控拖動手勢
		public void onStartTrackingTouch(SeekBar seekBar) {
			showControlView(3600000);
			isTouchSeeked = true;
		}

		// 通知使用者觸控手勢已經結束
		public void onStopTrackingTouch(SeekBar seekBar) {
			Message msg = Message.obtain();
			msg.what = PROGRESS_SEEKTO;
			msg.arg1 = seekBar.getProgress();
			mHandler.removeMessages(PROGRESS_SEEKTO);
			mHandler.sendMessageAtTime(msg, 1000);// 1秒之後開始傳送更新進度的訊息
			isTouchSeeked = false;
			showControlView(sDefaultTimeout);
		}
	};

其中將進度值轉換為時間的方法makeTimeString(int secs)如下所示:
/**
	 * 格式化的Builder
	 */
	private StringBuilder sFormatBuilder = new StringBuilder();
	/**
	 * 格式化的Formatter
	 */
	private Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());
	/**
	 * 格式化的相關屬性
	 */
	private final Object[] sTimeArgs = new Object[3];

	/**
	 * 轉換進度值為時間
	 * 
	 * @param secs
	 * @return
	 */
	private String makeTimeString(int secs) {
		/**
		 * %[argument_index$][flags][width]conversion 可選的
		 * argument_index 是一個十進位制整數,用於表明引數在引數列表中的位置。第一個引數由 "1$"
		 * 引用,第二個引數由 "2$" 引用,依此類推。 可選 flags
		 * 是修改輸出格式的字符集。有效標誌集取決於轉換型別。 可選 width
		 * 是一個非負十進位制整數,表明要向輸出中寫入的最少字元數。 可選 precision
		 * 是一個非負十進位制整數,通常用來限制字元數。特定行為取決於轉換型別。 所需 conversion
		 * 是一個表明應該如何格式化引數的字元。給定引數的有效轉換集取決於引數的資料型別。
		 */
		String durationformat = getString(R.string.durationformat);// <xliff:g
								// id="format">%1$02d:%2$02d:%3$02d</xliff:g>
		sFormatBuilder.setLength(0);
		secs = secs / 1000;
		Object[] timeArgs = sTimeArgs;
		timeArgs[0] = secs / 3600; // 秒
		timeArgs[1] = (secs % 3600) / 60; // 分
		timeArgs[2] = (secs % 3600 % 60) % 60; // 時
		return sFormatter.format(durationformat, timeArgs).toString().trim();
	}


當然,這裡只是簡單的介紹了下自定義進度條,而該進度條的樣式都沒有展現出來,樣式讀者可以自己定義。

PS: 2017-11-4,今天看到github上有個很棒的類似的開源庫,效果如下,大家可以看看!

如果你使用Google Play Movies,你可能注意到了這個動畫效果很棒,可以預覽電影的SeekBar。 Rúben Sousa ()實現了這種效果並開源。下面的gif圖片很好的說明了其功能。如果你的app是一個播放器,你決定應該試試。





====================================================================================

  作者:歐陽鵬  歡迎轉載,與人分享是進步的源泉!

===================================================================================