學習內容:
1.掌握Surface的使用...
2.Android中如何實現視頻播放...
1.SurfaceView類的使用
在Android中,一般播放音頻時我們可以去使用Android提供的MediaPlayer類,但是想要播放視頻僅僅依靠MediaPlayer類是遠遠不夠的...這里還需要使用到一個SurfaceView這個組件來完成..為什么?因為像視頻和SD圖形等都需要迅速的更新...如果這個更新實在主線程內去完成,那么顯然是不合理的,因為一個視頻的播放,系統會首先確定視頻的格式,然后得到視頻的編碼..然后對編碼進行解碼,得到一幀一幀的圖像,最后在畫布上進行迅速更新...這就是視頻播放的機制...那么這個過程顯然我們需要在另外一個線程內部去完成...這樣主線程的其他內容,比如說其他的渲染操作,圖片加載什么的,就不會導致主線程阻塞...這樣我們就可以使用SurfaceView來完成...
SurfaceView繼承了View類,也是屬於視圖的一部分...SurfaceView視圖內嵌了一個專門用於繪制的Surface...其實每一個Surface都在每一個窗口的后面,我們可以通過SurfaceView類來控制哪些Surface的內容可以顯示出來...其實說白了它的作用就是可以直接從內存或者是DMA硬件里獲取圖像數據...將這些圖像數據迅速的顯示出來,通過啟動另外一個線程可以迅速的完成畫面的更新操作,不會導致主線程阻塞...這個在游戲開發中也是起着非常重要的作用...
SurfaceView使用雙緩沖機制,這個機制可以使SurfaceView同時對兩張圖片進行渲染操作...其實目的也是為了迅速更新圖片的顯示,第一個緩沖對這一幀進行解析,第二個會對下一幀進行解析...這樣就可以避免在上一幀的圖片顯示完成后,下一幀的圖片還沒有進行顯示的情況發生...這樣就可以流暢的播放一個視頻文件...
SurfaceView的實現...
首先需要一個類去繼承這個類,然后實現SurfaceHolder.Callback()接口...使用這個接口的目的就是在Android中,SurfaceView的雙緩沖機制是非常消耗資源的,因此Android規定,在SurfaceView可見的時候,SurfaceView會對文件進行解析並顯示,當其不可見時,要直接銷毀掉SurfaceView以節省資源...那么這個接口的目的就是形成這個過程...
需要重寫的方法
(1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//在surface的大小發生改變時激發
(2)public void surfaceCreated(SurfaceHolder holder){}
//在創建時激發,一般在這里調用畫圖的線程。然后畫圖的工作開始...
(3)public void surfaceDestroyed(SurfaceHolder holder) {}
//銷毀時激發,一般在這里將畫圖的線程停止、釋放。
這三個方法就形成了上述說的那種關系...在視頻的播放當中,它會結合MediaPlayer完成視頻的流暢播放..而在游戲的開發當中,它可以結合Canvas元素來完成游戲中的一些畫面迅速更新的操作...
總而言之,SurfaceView就是一個繪圖的容器,它可以在不干擾主線程的情況下,調用另一個線程完成繪圖的操作,並迅速的更新圖像...以完成更好的顯示效果....
這里來兩個實例...一個是SurfaceView配合MediaPlayer完成視頻的播放,另一個是結合Canvas完成一個圖像在畫布上顯示出來...
1.結合Canvas完成圖像在畫布上的顯示...
這里做了一個摩爾斯燈塔....什么是摩爾斯燈塔...這玩意其實在以前的戰爭中可以使用的到,這里有一個概念就是摩爾斯碼...這個碼就是將數字,字母等符號以"."和"-"的形式顯示出來就是對文字的一種加密操作...出了可以發報文的形式,還可以以信號燈的形式來發送密報,然后另一方根據對應的信息進行解析...扯遠了...

a .- b -... c -.-. d -.. e . f ..-. g --. h .... i .. j .--- k -.- l .-.. m -- n -. o --- p .--. q --.- r .-. s ... t - u ..- v ...- w .-- x -..- y -.-- z --.. 0 ----- 1 .---- 2 ..--- 3 ...-- 4 ....- 5 ..... 6 -.... 7 --... 8 ---.. 9 ----. . .-.-.- - -....- , ..--.. / -..- ; -.-.-. ( -.--. ) -.--. @ .--.- * ...-.- + .-.-. % .-... ! =---. $ ...-..-
這上面的就是摩爾斯碼...我寫在了txt文檔當中...為了方便...但是我們需要注意一個地方就是我們在讀取txt文檔的時候,一定要把txt文檔的對應內容拷貝到res/asset文件夾下面...或者是放在模擬器的內存卡中...千萬別放在本地文件...因為Android的資源加載全在res文件夾下面或者是內存卡中,本地文件在正常的java程序中是可以讀到的,但是在Android是一定讀不到的..這個我已經試過,被坑3小時...因此這點需要注意....

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout_1" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <EditText android:id="@+id/input" android:layout_height="wrap_content" android:layout_width="fill_parent" android:hint=""/> <Button android:id="@+id/translate" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/input" android:text="轉換"/> <TextView android:id="@+id/show" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_below="@id/translate" android:text="輸出"/> <EditText android:id="@+id/output" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_below="@id/show" android:enabled="false"/> <Button android:id="@+id/send" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_below="@id/output" android:text="發送"/> </RelativeLayout>
布局文件非常簡單..看看就行....然后就是如何使用SurfaceView和Canvas的結合使用....在沒有Canvas的基礎下,還是去腦補一下Canvas。。。
package com.example.mouse; import java.util.HashMap; import java.util.Scanner; import android.os.Bundle; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.Menu; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.EditText; import android.widget.RelativeLayout; public class MainActivity extends Activity implements View.OnClickListener { public static HashMap<String, String> map = new HashMap<String, String>(); private EditText input; private EditText output; private char chars[]; private RelativeLayout layout; private boolean flag = false; private boolean loop = false; int count; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); input = (EditText) findViewById(R.id.input); output = (EditText) findViewById(R.id.output); findViewById(R.id.translate).setOnClickListener(this); findViewById(R.id.send).setOnClickListener(this); layout= (RelativeLayout) findViewById(R.id.layout_1);//獲取布局的id... } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.translate: try { Scanner in = new Scanner(getResources().getAssets().open( "morce.txt"));//獲取資源文件morce.txt文件內部的內容... while (in.hasNextLine()) { String str = in.nextLine(); String abc[] = str.trim().split("[\\p{Space}]+"); map.put(abc[0], abc[1]);//定義了一個HashMap<String,String>,以鍵值對的形式來保存內容... } map.put(" ", "/"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } String text = Mouse.morcecode(input.getText().toString(), map);//對我們輸入的字符進行匹配..來完成加密操作... output.setText(text); break; case R.id.send: if (output.getText() != null && output.getText().toString().length() > 0) { chars = output.getText().toString().toCharArray(); count = chars.length; LightView light = new LightView(MainActivity.this); layout.addView(light);//將畫布的繪畫內容加載到布局中.... } } } class LightView extends SurfaceView {//定義一個類繼承SurfaceView類... SurfaceHolder holder; public LightView(Context context) { // TODO Auto-generated constructor stub super(context); holder = this.getHolder();//調用getHolder()來獲取SurfaceHolder對象.. holder.addCallback(new SurfaceHolder.Callback() {//實現接口... class LightThread implements Runnable {//內部定義異步線程來完成繪畫操作... @Override public void run() { // TODO Auto-generated method stub while (loop) { if (count > 0) { String s = String.valueOf(chars[chars.length - count]); Canvas canvas = holder.lockCanvas(null);//鎖定畫布... Paint paint = new Paint(); paint.setAntiAlias(true);//去掉鋸齒.. paint.setColor(Color.BLACK);//畫布的顏色為黑色... canvas.drawRect(0, 0, 480, 480, paint); //下面這個是對報文的每一個字節解析以信號燈的形式顯示出來... if (flag) { paint.setColor(Color.BLACK); } else { if (s.equalsIgnoreCase(".")) { sleep(2); paint.setColor(Color.YELLOW); } else if (s.equalsIgnoreCase("-")) { sleep(4); paint.setColor(Color.YELLOW); } else if (s.equalsIgnoreCase(" ")) { sleep(2); paint.setColor(Color.BLACK); } else if (s.equalsIgnoreCase("/")) { sleep(2); paint.setColor(Color.BLACK); } count--; } canvas.drawCircle(250.0f, 200.0f, 100, paint);//畫出一個圓用來顯示信號... flag = !flag; holder.unlockCanvasAndPost(canvas);//解除鎖定 } } } public void sleep(int time) { try { Thread.sleep(time * 80); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub loop = false; } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub new Thread(new LightThread()).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } }); loop = true; } } }
這樣就完成了對輸入的文本以摩爾斯碼的形式進行加密操作....很簡單的一個小東西....
2.SurfaceView與MediaPlayer的結合操作....
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" > <TextView android:id="@+id/info" android:layout_height="wrap_content" android:layout_width="fill_parent" android:text="等待播放"/> <LinearLayout android:orientation="horizontal" android:layout_height="wrap_content" android:layout_width="wrap_content"> <ImageButton android:id="@+id/play" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/play"/> <ImageButton android:id="@+id/pause" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/pause"/> <ImageButton android:id="@+id/stop" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/stop"/> </LinearLayout> <SeekBar android:id="@+id/seekbar" android:layout_height="wrap_content" android:layout_width="fill_parent"/> <SurfaceView android:id="@+id/surface_1" android:layout_height="fill_parent" android:layout_width="fill_parent"/> </LinearLayout>
一個簡單的布局文件...三個圖片按鈕...一個進度條和一個表面視圖....
package com.example.exam7_2; import java.io.IOException; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.os.AsyncTask; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.SeekBar; import android.widget.TextView; public class MainActivity extends Activity implements View.OnClickListener, SeekBar.OnSeekBarChangeListener{ private MediaPlayer media; private boolean playflag=true; private boolean pauseflag=false; private SeekBar seekbar=null; private TextView tv; private SurfaceView suf=null; private SurfaceHolder sufh=null; @SuppressWarnings("deprecation") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv=(TextView) findViewById(R.id.info); findViewById(R.id.play).setOnClickListener(this); findViewById(R.id.pause).setOnClickListener(this); findViewById(R.id.stop).setOnClickListener(this); suf=(SurfaceView) findViewById(R.id.surface_1); sufh=suf.getHolder(); //獲取SurfaceHolder。。。 seekbar=(SeekBar) findViewById(R.id.seekbar); sufh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//設置SurfaceView的類型... media=new MediaPlayer(); try { media.setDataSource("/sdcard/test.3gp");//這里需要把想要播放的文件拷貝到模擬器的sd卡中去... } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private class updateseekbar extends AsyncTask<Integer, Integer, String>{//AsyncTask的使用... protected void onPostExecute(String result){} protected void onProgressUpdate(Integer...progress){ seekbar.setProgress(progress[0]); } @Override protected String doInBackground(Integer... params) { // TODO Auto-generated method stub while(MainActivity.this.playflag){ try { Thread.sleep(params[0]); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.publishProgress(MainActivity.this.media.getCurrentPosition()); } return null; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void onClick(View v) { // TODO Auto-generated method stub switch(v.getId()){ case R.id.play: { MainActivity.this.media.setOnCompletionListener(new OnCompletionListener() { @Override public void onCompletion(MediaPlayer arg0) { // TODO Auto-generated method stub playflag=false; media.release(); } }); seekbar.setMax(MainActivity.this.media.getDuration()); updateseekbar update=new updateseekbar(); //執行者execute update.execute(1000); seekbar.setOnSeekBarChangeListener(this); if(MainActivity.this.media!=null){ MainActivity.this.media.stop(); } try { MainActivity.this.media.prepare(); MainActivity.this.media.start(); tv.setText("正在播放文件..."); } catch (Exception e) { // TODO Auto-generated catch block tv.setText("文件出現異常..."); e.printStackTrace(); } break; } case R.id.pause: if(media!=null){ if(pauseflag){ media.start(); pauseflag=false; }else{ media.pause(); pauseflag=true; } } break; case R.id.stop: if(media!=null){ media.stop(); tv.setText("停止播放文件..."); } break; } } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // TODO Auto-generated method stub } @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } @Override public void onStopTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub media.seekTo(seekbar.getProgress()); } }
這樣就完成了MediaPlayer和SurfaceView的配合使用...完成了視頻的播放...同時添加了一個進度條來控制播放的進度...