Android開(kāi)發(fā)中的Surface庫(kù)及用其制作播放器UI的例子
1、Surface
1.1、 就如在C語(yǔ)言編程一樣,通過(guò)一個(gè)文件的句柄,就可以操作文件,獲取文件的內(nèi)容。 同樣的,通過(guò)Surface就可以獲取raw buffer其中的內(nèi)容。原生緩沖區(qū)(raw buffer)存儲(chǔ)著當(dāng)前窗口的像素?cái)?shù)據(jù)。
1.2、事實(shí)上,當(dāng)?shù)玫揭粋€(gè)Surface對(duì)象時(shí),同時(shí)會(huì)得到一個(gè)Canvas(畫(huà)布)對(duì)象。這一點(diǎn)可以通過(guò)查看\frameworks\base\core\java\android\view\Surface.java文件可知道Surface類(lèi)定義了一個(gè)Canvas成員變量
private int mSurfaceControl; private int mSaveCount; private Canvas mCanvas; private int mNativeSurface; private int mSurfaceGenerationId; private String mName;
1.3、 理解Canvas對(duì)象,可以把它當(dāng)做畫(huà)布,Canvas的方法大多數(shù)是設(shè)置畫(huà)布的大小、形狀、畫(huà)布背景顏色等等,要想在畫(huà)布上面畫(huà)畫(huà),一般要與Paint對(duì)象結(jié)合使用,顧名思義,Paint就是畫(huà)筆的風(fēng)格,顏料的色彩之類(lèi)的。
// 創(chuàng)建畫(huà)筆 Paint paint = new Paint(); paint.setColor(Color.RED);// 設(shè)置紅色 canvas.drawCircle(60, 20, 10, paint);// 畫(huà)一個(gè)圓
1.4、Surface本身的作用類(lèi)似一個(gè)句柄,得到了這個(gè)句柄就可以得到其中的Canvas、原生緩沖器以及其它方面的內(nèi)容。
1.5、Surface實(shí)現(xiàn)了Parcelable接口,(implements Parcelable),也就是說(shuō)Surface對(duì)象可以把顯示內(nèi)容的數(shù)據(jù)寫(xiě)入到 Parcel 中,并且能夠從Parcel讀回?cái)?shù)據(jù)。
2、SurfaceView
SurfaceView提供了一個(gè)專(zhuān)門(mén)用于繪制的surface,這個(gè)surface內(nèi)嵌于。你可以控制這個(gè)Surface的格式和尺寸。Surfaceview控制這個(gè)Surface在屏幕的正確繪制位置。
surface是Z-ordered的(也就是說(shuō)在xyz坐標(biāo)系中,按照Z(yǔ)坐標(biāo)排序的,Z值大的表面覆蓋在Z值小的表面的上方),這表明它總在自己所在窗口的后面。surfaceview在顯示窗口處為Surface提供了一個(gè)可見(jiàn)區(qū)域,通過(guò)這個(gè)區(qū)域,才能看到Surface里面的內(nèi)容。可以放置一些覆蓋圖層(overlays)在Surface上面,如Button、Textview之類(lèi)的。但是,需要注意的是,如果Surface上面有全透明的控件,那么隨著Surface的每一次變化,這些全透明的控件就會(huì)重新渲染,這樣的話,就影響性能與顯示的效果。
你可以通過(guò)SurfaceHolder這個(gè)接口去訪問(wèn)Surface,而執(zhí)行g(shù)etHolder()方法可以得到SurfaceHolder接口。
當(dāng)SurfaceView的窗口可見(jiàn)時(shí),Surface就會(huì)被創(chuàng)建,當(dāng)SurfaceView窗口隱藏時(shí),Surface就會(huì)被銷(xiāo)毀。當(dāng)然了,你也可以通過(guò)復(fù)寫(xiě)surfaceCreated(SurfaceHolder) 和 surfaceDestroyed(SurfaceHolder) 這兩個(gè)方法來(lái)驗(yàn)證一下Surface何時(shí)被創(chuàng)建與何時(shí)被銷(xiāo)毀。
SurfaceView提供了一個(gè)運(yùn)行在渲染線程的surface,若你要更新屏幕,你需要了解以下線程知識(shí)。
所有SurfaceView 和 SurfaceHolder.Callback的方法都應(yīng)該在主線程(UI線程)里面調(diào)用,應(yīng)該要確保渲染進(jìn)程所訪問(wèn)變量的同步性。
你必須確保只有當(dāng)Surface有效的時(shí)候,(也就是當(dāng)Surface的生命周期在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之間)才能讓渲染進(jìn)程訪問(wèn)。
2.1、SurfaceView與Surface的聯(lián)系
簡(jiǎn)單來(lái)說(shuō),SurfaceView與Surface的聯(lián)系就是,Surface是管理顯示內(nèi)容的數(shù)據(jù)(implementsParcelable),包括存儲(chǔ)于數(shù)據(jù)的交換。而SurfaceView就是把這些數(shù)據(jù)顯示出來(lái)到屏幕上面。
兩者聯(lián)系如圖所示:

3、SurfaceHolder
SurfaceHolder是控制surface的一個(gè)抽象接口,你可以通過(guò)SurfaceHolder來(lái)控制surface的尺寸和格式,或者修改surface的像素,監(jiān)視surface的變化等等,SurfaceHolder是SurfaceView的典型接口。
與直接控制SurfaceView來(lái)修改surface不同,使用SurfaceHolder來(lái)修改surface時(shí),需要注意lockCanvas() 和Callback.surfaceCreated().這兩個(gè)方法。
SurfaceHolder控制surface的流程所使用的幾個(gè)方法。
3.1、abstract void addCallback(SurfaceHolder.Callback callback)
給SurfaceHolder一個(gè)回調(diào)對(duì)象。
3.2、abstract Canvas lockCanvas(Rect dirty)
鎖定畫(huà)布中的某一個(gè)區(qū)域,返回的畫(huà)布對(duì)象Canvas(當(dāng)更新的內(nèi)容只有一個(gè)區(qū)域時(shí),同時(shí)要追求高效,可以只更
新一部分的區(qū)域,而不必更新全部畫(huà)布區(qū)域)
3.3、abstract Canvas lockCanvas()
鎖定畫(huà)布,返回的畫(huà)布對(duì)象Canvas
3.4、abstract void removeCallback(SurfaceHolder.Callback callback)
移除回調(diào)對(duì)象
3.5、abstract void unlockCanvasAndPost(Canvas canvas)
結(jié)束鎖定畫(huà)圖,并提交改變。
4、SurfaceHolder.Callback
SurfaceHolder.Callback是監(jiān)聽(tīng)surface改變的一個(gè)接口
4.1、public abstract voidsurfaceChanged(SurfaceHolder holder, int format, int width, int height)
surface發(fā)生改變時(shí)被調(diào)用
4.2、public abstract voidsurfaceCreated(SurfaceHolder holder)
在surface創(chuàng)建時(shí)被調(diào)用,一般在這個(gè)方法里面開(kāi)啟渲染屏幕的線程。
4.3、public abstract voidsurfaceDestroyed(SurfaceHolder holder)
銷(xiāo)毀時(shí)被調(diào)用,一般在這個(gè)方法里將渲染的線程停止。
附上上述所說(shuō)幾種的聯(lián)系方法
SurfaceHolder = SurfaceView.getHolder(); Surface = SurfaceHolder.getSurface(); Canvas =SurfaceHolder.LockCanvas(Rect dirty) Canvas =Surface.lockCanvas(Rect dirty)
5、DEMO:通過(guò)SurfaceView以及SurfaceHolder進(jìn)行視頻播放
使用AudioView進(jìn)行視頻播放的時(shí)候,是不是很不爽,千篇一律的模式,惡心吧。這里,我們可以通過(guò)一些方式對(duì)MediaPlayer進(jìn)行包裝。而所用到的正是SurfaceView以及SurfaceHolder。
最終效果圖:

我們提供了四個(gè)按鈕,可以進(jìn)行播放控制。
布局文件media.xml代碼:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<SurfaceView android:id="@+id/surfaceView1"
android:layout_width="320px" android:layout_height="160px"></SurfaceView>
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content"
android:orientation="horizontal"
>
<ImageButton android:id="@+id/button_play" android:src="@drawable/play" android:onClick="buttonClick"
android:layout_width="wrap_content" android:layout_height="wrap_content"></ImageButton>
<ImageButton android:id="@+id/button_pause" android:src="@drawable/pause" android:onClick="buttonClick"
android:layout_width="wrap_content" android:layout_height="wrap_content"></ImageButton>
<ImageButton android:id="@+id/button_stop" android:src="@drawable/stop" android:onClick="buttonClick"
android:layout_width="wrap_content" android:layout_height="wrap_content"></ImageButton>
<ImageButton android:id="@+id/button_reset" android:src="@drawable/reset" android:onClick="buttonClick"
android:layout_width="wrap_content" android:layout_height="wrap_content"></ImageButton>
</LinearLayout>
</LinearLayout>
activity代碼:
package cn.com.chenzheng_java.media;
import android.app.Activity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
/**
* @description 通過(guò)SurfaceView/SurfaceHolder實(shí)現(xiàn)自己的播放器
* @author chenzheng_java
* @since 2011/03/23
* @description 這里進(jìn)行一下補(bǔ)充說(shuō)明,我們可以通過(guò)mediaplayer添加OnPreparedListener
* 以及OnCompletionListener等事件對(duì)準(zhǔn)備好播放以及播放完成后的操作進(jìn)行控制。
* 使用SurfaceView以及SurfaceHolder進(jìn)行視頻播放時(shí),結(jié)構(gòu)是這樣的:
* 1、首先,我們從布局文件中獲取一個(gè)surfaceView
* 2、通過(guò)surfaceView.getHolder()方法獲取與該容器想對(duì)應(yīng)的surfaceHolder
* 3、對(duì)srufaceHolder進(jìn)行一些默認(rèn)的設(shè)置,如addCallback()和setType()
* 4、通過(guò)mediaPlayer.setDisplay()方法將視頻播放與播放容器鏈接起來(lái)
*/
public class MyMediaPlayerActivity extends Activity {
MediaPlayer mediaPlayer ; // 播放器的內(nèi)部實(shí)現(xiàn)是通過(guò)MediaPlayer
SurfaceView surfaceView ;// 裝在視頻的容器
SurfaceHolder surfaceHolder;// 控制surfaceView的屬性(尺寸、格式等)對(duì)象
boolean isPause ; // 是否已經(jīng)暫停了
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.media);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView1);
/**
* 獲取與當(dāng)前surfaceView相關(guān)聯(lián)的那個(gè)的surefaceHolder
*/
surfaceHolder = surfaceView.getHolder();
/**
* 注冊(cè)當(dāng)surfaceView創(chuàng)建、改變和銷(xiāo)毀時(shí)應(yīng)該執(zhí)行的方法
*/
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i("通知", "surfaceHolder被銷(xiāo)毀了");
if(mediaPlayer!=null)
mediaPlayer.release();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i("通知", "surfaceHolder被create了");
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.i("通知", "surfaceHolder被改變了");
}
});
/**
* 這里必須設(shè)置為SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS哦,意思
* 是創(chuàng)建一個(gè)push的'surface',主要的特點(diǎn)就是不進(jìn)行緩沖
*/
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
/***
* @param targetButton 被用戶(hù)點(diǎn)擊的按鈕
*/
public void buttonClick(View targetButton){
int buttonId = targetButton.getId();
switch (buttonId) {
case R.id.button_play:
play();
break;
case R.id.button_pause:
pause();
break;
case R.id.button_reset:
reset();
break;
case R.id.button_stop:
stop();
break;
default:
break;
}
}
/**
* 播放
*/
private void play(){
mediaPlayer = new MediaPlayer();
// 設(shè)置多媒體流類(lèi)型
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// 設(shè)置用于展示mediaPlayer的容器
mediaPlayer.setDisplay(surfaceHolder);
try {
mediaPlayer.setDataSource("/data/jinsha.3gp");
mediaPlayer.prepare();
mediaPlayer.start();
isPause = false;
} catch (Exception e) {
Log.i("通知", "播放過(guò)程中出現(xiàn)了錯(cuò)誤哦");
}
}
/**
* 暫停
*/
private void pause(){
Log.i("通知", "點(diǎn)擊了暫停按鈕");
if(isPause==false){
mediaPlayer.pause();
isPause=true;
}else{
mediaPlayer.start();
isPause=false;
}
}
/**
* 重置
*/
private void reset(){
Log.i("通知", "點(diǎn)擊了reset按鈕");
// 跳轉(zhuǎn)到視頻的最開(kāi)始
mediaPlayer.seekTo(0);
mediaPlayer.start();
}
/**
* 停止
*/
private void stop(){
Log.i("通知", "點(diǎn)擊了stop按鈕");
mediaPlayer.stop();
mediaPlayer.release();
}
}
- android暫停或停止其他音樂(lè)播放器的播放實(shí)現(xiàn)代碼
- android webvie指定視頻播放器播放網(wǎng)站視頻
- 教你輕松制作Android音樂(lè)播放器
- Android自定義播放器控件VideoView
- Android編程開(kāi)發(fā)音樂(lè)播放器實(shí)例
- android音樂(lè)播放器監(jiān)聽(tīng)電話狀態(tài)實(shí)現(xiàn)代碼
- Android應(yīng)用開(kāi)發(fā)之簡(jiǎn)易、大氣音樂(lè)播放器實(shí)現(xiàn)專(zhuān)輯倒影效果
- Android App中使用AudioManager類(lèi)來(lái)編寫(xiě)音頻播放器
- Android繪制音樂(lè)播放器示波器
- Android基于Service的音樂(lè)播放器
相關(guān)文章
Flutter將整個(gè)App變?yōu)榛疑暮?jiǎn)單實(shí)現(xiàn)方法
Flutter?是?Google?開(kāi)源的?UI?工具包,幫助開(kāi)發(fā)者通過(guò)一套代碼庫(kù)高效構(gòu)建多平臺(tái)精美應(yīng)用,這篇文章主要給大家介紹了關(guān)于Flutter將整個(gè)App變?yōu)榛疑膶?shí)現(xiàn)方法,在Flutter中實(shí)現(xiàn)整個(gè)App變?yōu)榛疑欠浅:?jiǎn)單的,需要的朋友可以參考下2021-12-12
超簡(jiǎn)單Android集成華為HMS Scankit 掃碼SDK實(shí)現(xiàn)掃一掃二維碼
這篇文章主要介紹了超簡(jiǎn)單Android集成華為HMS Scankit 掃碼SDK實(shí)現(xiàn)掃一掃二維碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Kotlin如何安全訪問(wèn)lateinit變量的實(shí)現(xiàn)
這篇文章主要介紹了Kotlin如何安全訪問(wèn)lateinit變量的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
cocos2d-2.0-x-2.0.3 交叉編譯到android報(bào)錯(cuò)解決
我用的是cocos2d-2.0-x-2.0.3 之前弄了一天也沒(méi)成功 今天來(lái)了下載了最新的ndk8 更新了sdk 又重新是了一遍 居然成功了,不知道是工具的版本問(wèn)題還是哪一步出錯(cuò)誤了,在這里詳細(xì)的整理一下,感興趣的朋友可以了解下2013-01-01
Android多線程處理機(jī)制中的Handler使用介紹
本文將為大家介紹下Android的Handler的使用方法,Handler可以發(fā)送Messsage和Runnable對(duì)象到與其相關(guān)聯(lián)的線程的消息隊(duì)列,感興趣的朋友可以了解下哈2013-06-06
安卓應(yīng)用開(kāi)發(fā)通過(guò)java調(diào)用c++ jni的圖文使用方法
這篇文章主要介紹了2013-11-11
Android實(shí)現(xiàn)簡(jiǎn)易版彈鋼琴效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)易版彈鋼琴效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
Android使用Intent實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了Android使用Intent實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10

