首先咱們打開電腦中的畫圖板,截取像素800*400的區域,在圖中做畫:
android
點擊左上角另存爲,有以下幾種常見的圖片格式:
數據庫
咱們以bmp格式保存圖片,保存圖片的類型以下:
canvas
分別以24位位圖,單色位圖,16色位圖,256色位圖分別保存成4個文件:
api
查看以上幾個圖片文件,發現圖片的大小不一致,那麼圖片的總大小是如何計算的呢?如下是計算公式:網絡
圖片的總大小 = 圖片的總像素 * 每一個像素的大小
每一個像素的大小取決於圖片能表示的顏色的數量。框架
只能表示黑白兩種顏色,使用0和1就足夠表示了,每一個像素須要一個長度爲1的二進制數字表示顏色即每一個像素佔用1/8個字節。根據公式,上面保存的單色的圖片的大小爲:
400*800*(1/8) = 40000字節,查看單色圖片的屬性如1下圖:
ide
發現圖片的總大小爲40062字節,爲何會比咱們計算的值大呢?由於圖片還要存儲一些額外的信息,好比時間等等。佈局
只能表示16種顏色,使用16個數字(0 - 15),換成二進制爲0000 - 1111,每一個像素須要一個長度爲4的二進制數字能表示顏色即一個像素佔1/2個字節。post
只能表示256種顏色,使用256個數字(-128 ~ 127),換成二進制位0000 0000-1111 1111,一個像素佔1個字節。this
24位圖表示範圍爲24位個二進制數,咱們利用計算器能夠看到,24位最大能夠表示16777215個數字,因此24位圖能表一千六百多萬種顏色。
每一個像素佔用24位,也就是3個字節,分別用RGB表示:
R:0 - 255,使用1個字節就能夠表示;
G:同上;
B:同上。
每一個像素佔用4個字節,分別用ARGB表示:
A:透明度,0 - 255
若是圖片要顯示到界面,那麼內存中須要保存圖片的全部像素的顏色信息,內存中使用ARGB保存。
Android系統以ARGB表示每一個像素,因此每一個像素佔用4個字節,很容易內存溢。下面,使用ImageView加載SD卡中的一張大內存的圖片,該圖片大小以下圖:
界面佈局:
<RelativeLayout 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" tools:context=".MainActivity" > <ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
MainActivity中獲取到圖片的bitmap對象,利用ImageView顯示:
public class MainActivity extends Activity { @SuppressWarnings("deprecation") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView iv = (ImageView) findViewById(R.id.iv); BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg"); iv.setImageBitmap(bitmap); } }
運行後,程序會崩潰,查看Logcat日誌輸出,發現內存溢出以下圖:
爲何會出現內存溢出呢?當建立模擬器的時候,有一個VM Heap選項,這個選項表明手機模擬器給每一個應用默認分配的內存大小,當一個應用的使用內存超過了16M,那麼就會報內存溢出的錯誤。
繼續查看日誌,能夠看到有這麼一行日誌,以下圖:
上圖的日誌是說建立一個30720012字節的文件時內存溢出。咱們來計算下加載的圖片顯示到手機上的總的大小,圖片的分辨率爲2400*3200以下圖:
在安卓中,是使用ARGB表示圖片像素的,因此一個像素是4個byte,根據計算公式,該圖片的總大小爲2400*3200*4=30720000,發現和日誌輸出中的數值是一致的。
1.獲取屏幕寬高
//經過Context的getWindowManager()方法獲取Window的管理者對象 Display dp = getWindowManager().getDefaultDisplay(); int screenWidth = dp.getWidth(); int screenHeight = dp.getHeight();
另外一種獲取Windowmanager對象的方法:
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
2.獲取圖片寬高
Options opts = new Options(); //inJustDecodeBounds屬性值爲true,表示只請求圖片屬性,不申請內存 opts.inJustDecodeBounds = true; BitmapFactory.decodeFile("sdcard/dog.jpg", opts); //獲取圖片的寬 int imageWidth = opts.outWidth; //獲取圖片的高 int imageHeight = opts.outHeight;
3.獲取縮放比例
圖片的寬高除以屏幕寬高,算出寬和高的縮放比例,取較大值做爲圖片的縮放比例。
int scale = 1; int scaleX = imageWidth / screenWidth; int scaleY = imageHeight / screenHeight; if(scaleX >= scaleY && scaleX > 1){ scale = scaleX; } else if(scaleY > scaleX && scaleY > 1){ scale = scaleY; }
4.按縮放比例加載圖片
//設置縮放比例 opts.inSampleSize = scale; //inJustDecodeBounds屬性爲false,表示爲圖片申請內存 opts.inJustDecodeBounds = false; //從文件中獲取Bitmap,參數1表示文件路徑,參數2表示圖片參數。 Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts); iv.setImageBitmap(bm);
運行效果:
咱們在使用美圖秀秀對圖片進行編輯操做後,保存圖片的時候,是保存爲另一張圖片。其實其內部原理是先建立一張原圖的副本,而後再副本圖片上進行用戶編輯操做,因此最後保存的時候是新的一張圖片。
此外,直接加載的bitmap對象是隻讀的,沒法修改,要修改圖片只能在內存中建立出一個如出一轍的bitmap副本,而後修改副本。
首先,建立佈局,有兩個ImageView:
<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" > <ScrollView android:layout_width="wrap_content" android:layout_height="wrap_content" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" <ImageView android:id="@+id/iv_src" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:id="@+id/iv_copy" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </ScrollView> </LinearLayout>
Activity中,建立圖片副本,修改副本圖片,並顯示到另外一個ImageView上。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy); ImageView iv_src = (ImageView) findViewById(R.id.iv_src); Bitmap srcBitmap = BitmapFactory.decodeFile("mnt/sdcard/meinv.png"); iv_src.setImageBitmap(srcBitmap); //建立一個和原圖大小同樣,而且圖片參數同樣的空白bitmap Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(),srcBitmap.getHeight(), srcBitmap.getConfig()); //建立畫布對象 Canvas canvas = new Canvas(copyBitmap); //建立畫筆對象 Paint paint = new Paint(); //調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆 canvas.drawBitmap(srcBitmap, new Matrix(), paint); //將拷貝圖片的bitmap對象上座標(20,30)點設置成紅色 copyBitmap.setPixel(20,30, Color.RED); //將建立的拷貝的bitmap對象設置到另外一個ImageView上 iv_copy.setImageBitmap(copyBitmap); } }
運行效果:
Matrix matrix = new Matrix(); //讓矩陣旋轉30度 matrix.setRotate(30); //參數1表示旋轉的角度,參數2和參數3是旋轉的中心點 matrix.setRotate(30, copyBitmap.getWidth()/2, copyBitmap.getHeight()/2); //調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆 canvas.drawBitmap(srcBitmap, matrix, paint); iv_copy.setImageBitmap(copyBitmap);
運行效果:
Matrix matrix = new Matrix(); matrix.setTranslate(20, 0); //調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆 canvas.drawBitmap(srcBitmap, matrix, paint); iv_copy.setImageBitmap(copyBitmap);
運行效果:
Matrix matrix = new Matrix(); matrix.setScale(0.5f, 0.5f); //調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆 canvas.drawBitmap(srcBitmap, matrix, paint); iv_copy.setImageBitmap(copyBitmap);
運行效果:
Matrix matrix = new Matrix(); //設置縮放 matrix.setScale(-1.0f, 1.0f); //設置位移 matrix.postTranslate(copyBitmap.getWidth(), 0); //調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆 canvas.drawBitmap(srcBitmap, matrix, paint); iv_copy.setImageBitmap(copyBitmap);
注意:setXXX方法每次修改都是最新的的操做,會覆蓋上一次操做,post是在上一次修改的基礎上繼續修改。
運行效果:
Matrix matrix = new Matrix(); matrix.setScale(1.0f, -1.0f); matrix.postTranslate(0, copyBitmap.getHeight()); //調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆 canvas.drawBitmap(srcBitmap, matrix, paint); iv_copy.setImageBitmap(copyBitmap);
運行效果:
本案例模擬實現安卓版的美圖秀秀中的功能,拖動SeekBar,圖片的顏色值隨着變化。
界面效果:
<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:layout_width="match_parent" android:layout_height="wrap_content" android:text="青------紅" /> <SeekBar android:id="@+id/sb_red" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="紫------綠" /> <SeekBar android:id="@+id/sb_green" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="黃------藍" /> <SeekBar android:id="@+id/sb_blue" android:layout_width="match_parent" android:layout_height="wrap_content" /> <ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
MainActivity中初始化控件,給SeekBar設置監聽而且建立原圖的一個副本:
public class MainActivity extends Activity implements OnSeekBarChangeListener { private Paint paint; private Canvas canvas; private Bitmap srcBitmap; private ImageView iv; private Bitmap copyBitmap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv = (ImageView) findViewById(R.id.iv); SeekBar sb_blue = (SeekBar) findViewById(R.id.sb_blue); SeekBar sb_green = (SeekBar) findViewById(R.id.sb_green); SeekBar sb_red = (SeekBar) findViewById(R.id.sb_red); sb_blue.setOnSeekBarChangeListener(this); sb_green.setOnSeekBarChangeListener(this); sb_red.setOnSeekBarChangeListener(this); srcBitmap = BitmapFactory.decodeFile("mnt/sdcard/meinv.png"); copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig()); canvas = new Canvas(copyBitmap); paint = new Paint(); canvas.drawBitmap(srcBitmap, new Matrix(), paint); iv.setImageBitmap(copyBitmap); } }
實現SeekBar監聽:
//當進度改變的時候調用 @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } //當開始拖動的時候調用 @Override public void onStartTrackingTouch(SeekBar seekBar) { } //當中止拖動的時候調用 @Override public void onStopTrackingTouch(SeekBar seekBar) { int id = seekBar.getId(); //獲取當前SeekBar的進度 int progress = seekBar.getProgress(); //建立一個顏色矩陣對象 ColorMatrix cm = new ColorMatrix(); float rf = 0; float gf = 0; float bf = 0; switch (id) { case R.id.sb_red: rf = progress / 128.0f; break; case R.id.sb_green: gf = progress / 128.0f; break; case R.id.sb_blue: bf = progress / 128.0f; break; } /**設置顏色矩陣,顏色矩陣的計算公式以下 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 New Red Value = 1*128 + 0*128 + 0*128 + 0*0 + 0 New Blue Value = 0*128 + 1*128 + 0*128 + 0*0 + 0 New Green Value = 0*128 + 0*128 + 1*128 + 0*0 + 0 New Alpha Value = 0*128 + 0*128 + 0*128 + 1*0 + 0 */ cm.set(new float[] { rf, 0 , 0 , 0, 0, 0, gf, 0 , 0, 0, 0, 0 , bf, 0, 0, 0, 0 , 0 , 1, 0 }); //給Paint對象設置顏色過濾 paint.setColorFilter(new ColorMatrixColorFilter(cm)); canvas.drawBitmap(srcBitmap, new Matrix(), paint); iv.setImageBitmap(copyBitmap); }
運行效果:
本案例實如今一塊背景上能夠畫畫的功能,相似畫畫板的功能。
佈局界面:
<RelativeLayout 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" tools:context=".MainActivity" > <ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
在MainActivity中,建立一個背景的圖片副本,讓ImageView顯示:
public class MainActivity extends Activity { private Paint paint; private Canvas canvas; private ImageView iv; private Bitmap copyBitmap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv = (ImageView) findViewById(R.id.iv); Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.bg); copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(),srcBitmap.getHeight(), srcBitmap.getConfig()); canvas = new Canvas(copyBitmap); paint = new Paint(); canvas.drawBitmap(srcBitmap, new Matrix(), paint); //畫一條線,參數1是起點x座標,參數2是起點y座標,參數3是終點x座標,參數4是終點y座標,參數5是畫筆對象。 canvas.drawLine(10, 10, 40, 100, paint); iv.setImageBitmap(copyBitmap); } }
運行效果:
接下來,咱們給ImageView設置觸摸事件,隨着觸摸事件在ImageView上畫畫:
iv.setOnTouchListener(new OnTouchListener() { int startX = 0; int startY = 0; @Override public boolean onTouch(View v, MotionEvent event) { //調用event.getAction()獲取觸摸事件類型 int action = event.getAction(); switch (action) { //MotionEvent.ACTION_DOWN表示按下事件 case MotionEvent.ACTION_DOWN: //獲取當前手指觸摸的x和y座標 startX = (int) event.getX(); startY = (int) event.getY(); break; //MotionEvent.ACTION_MOVE表示手指移動事件 case MotionEvent.ACTION_MOVE: //獲取當前手指觸摸的x和y座標 int newX = (int) event.getX(); int newY = (int) event.getY(); //實時更改畫線的起點座標 canvas.drawLine(startX, startY, newX, newY, paint); startX = newX; startY = newY; iv.setImageBitmap(copyBitmap); break; //MotionEvent.ACTION_UP表示擡起事件 case MotionEvent.ACTION_UP: break; default: break; } return true; } });
運行效果:
接下來,實現刷子效果,能夠改變畫筆顏色:
public void click1(View view) { //設置畫筆粗細 paint.setStrokeWidth(20); } public void click2(View view) { //設置畫筆顏色爲綠色 paint.setColor(Color.GREEN); } public void click3(View view) { paint.setColor(Color.RED); }
運行效果:
將圖片保存到SD卡:
public void click4(View view){ File file = new File(Environment.getExternalStorageDirectory().getPath(), 」haha.png」); FileOutputStream fos; try { fos = new FileOutputStream(file); //調用Bitmap對象的compress()方法保存圖片,參數1是圖片的格式,參數2是圖片壓縮的質量,參數3是輸出流。 copyBitmap.compress(CompressFormat.PNG, 100, fos); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
加入權限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
運行效果:
導出到電腦,用圖片查看器查看:
系統每次收到SD卡就緒廣播時,都會去遍歷SD卡的全部文件和文件夾,把遍歷到的全部多媒體文件都在MediaStore數據庫保存一個索引,這個索引包含多媒體文件的文件名、路徑、大小。
圖庫每次打開時,並不會去遍歷SD卡獲取圖片,而是經過內容提供者從MediaStore數據庫中獲取圖片的信息,而後讀取該圖片。
系統開機或者點擊掛載SD卡按鈕時,系統會發送sd卡就緒廣播,咱們也能夠手動發送掛載SD卡就緒廣播。
Intent intent = new Intent(); intent.setAction(Intent.ACTION_MEDIA_MOUNTED); intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory())); sendBroadcast(intent);
運行效果:
本案例實現撕衣服的效果,手指在圖片上滑動,劃過的地方將會把衣服去掉。效果圖以下:
手指在圖片上滑動:
實現原理:在屏幕上面有兩個ImageView,下面的ImageView是沒有穿衣服的圖片,上面的是穿衣服的。而後建立一個穿衣服的圖片的Bitmap副本,設置給上方的ImageView,給上方的ImageView設置觸摸監聽,當手指移動的時候在副本Bitmap上畫透明像素,這樣下方的ImageView就顯示出來了。
1.佈局代碼:
<RelativeLayout 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" tools:context=".MainActivity" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/after19" /> <ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
2.具體代碼邏輯:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //上方的ImageView final ImageView iv = (ImageView) findViewById(R.id.iv); //經過BitmapFactory.decodeResource()方法加載圖片 Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pre19); //建立圖片副本 final Bitmap alterBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig()); Paint paint = new Paint(); Canvas canvas = new Canvas(alterBitmap); canvas.drawBitmap(srcBitmap, new Matrix(), paint); //給上方ImageView設置圖片副本背景 iv.setImageBitmap(alterBitmap); //給ImageView設置觸摸事件 iv.setOnTouchListener(new OnTouchListener() { //當手指滑動的時候調用 @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: for (int i = -7; i < 7; i++) { for (int j = -7; j < 7; j++) { if (Math.sqrt(i * i + j * j) < 7) { try { //在圖片副本上畫透明像素 alterBitmap.setPixel((int) event.getX() + i, (int) event.getY() + j, Color.TRANSPARENT); } catch (Exception e) { } } } } //給ImageView設置修改後的背景,實時刷新 iv.setImageBitmap(alterBitmap); break; } return true; } }); } }
本案例實現點擊按鈕實現播放音頻的功能。點擊按鈕播放SD卡上的音頻文件。播放音頻須要使用到MediaPlayer這個api,下圖是MediaPlayer的狀態圖解:
將xiaopingguo.mp3音頻文件導入到SD卡中,以下圖:
Activity中點擊按鈕播放音樂:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click(View v) { //建立MediaPlayer對象 final MediaPlayer player = new MediaPlayer(); //設置播放的類型 player.setAudioStreamType(AudioManager.STREAM_MUSIC); try { //設置播放的數據源 player.setDataSource("/mnt/sdcard/xiaopingguo.mp3"); //player.setDataSource("http://192.168.116.132:8080/xiaopingguo.mp3"); //準備播放 player.prepare(); //若是播放的是網絡資源,那麼使用prepareAsync()方法來準備播放,由於prepare()方法是阻塞線程的 // player.prepareAsync(); //設置準備監聽,當播放準備好後回調onPrepared()方法 player.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { //開始播放 player.start(); } }); } catch (Exception e) { e.printStackTrace(); } } }
在Activity中播放音頻,當按返回鍵回到Home界面後,因爲應用的進程變成了空進程,因此很容易被系統回收。那麼怎麼解決這個問題呢?能夠在Service中操做播放音頻,這樣就不容易被系統回收。
1.建立佈局
<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" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="click1" android:text="播放" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="click2" android:text="暫停" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="click3" android:text="繼續播放" /> <SeekBar android:id="@+id/sb_control" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
2.建立播放音頻Service
定義IService接口提供服務中的方法:
public interface Iservice { public void callPlay(); public void callPause(); public void callReplay(); public void callSeekTo(int position); }
編寫Service類,並在Service中定義操做音頻的方法並建立Binder對象:
public class MusicService extends Service { private MediaPlayer player; @Override public IBinder onBind(Intent intent) { return new MyBinder(); } @Override public void onCreate() { player = new MediaPlayer(); super.onCreate(); } @Override public void onDestroy() { super.onDestroy(); } public void play(){ //player.reset()方法用來重置MediaPlayer player.reset(); player.setAudioStreamType(AudioManager.STREAM_MUSIC); try { player.setDataSource("/mnt/sdcard/xiaopingguo.mp3"); player.prepare(); player.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { player.start(); //當開始播放時開始更新進度 updateSeekBar(); } }); } catch (Exception e) { e.printStackTrace(); } } //定義暫停播放的方法 public void pause(){ player.pause(); } //定義繼續播放的方法 public void rePlay(){ player.start(); } //定義指定從某個地方開始播放 public void seekTo(int position){ player.seekTo(position); } private class MyBinder extends Binder implements Iservice{ @Override public void callPlay() { play(); } @Override public void callPause() { pause(); } @Override public void callReplay() { rePlay(); } @Override public void callSeekTo(int position) { seekTo(position); } } }
更新進度條:
private void updateSeekBar() { //歌曲的總的時長 final int duration = player.getDuration(); //建立Timer定時任務對象 Timer timer = new Timer(); //建立任務對象 TimerTask task = new TimerTask() { @Override public void run() { //getCurrentPosition()獲取當前播放的位置 int currentPosition = player.getCurrentPosition(); //建立Message消息對象,將歌曲總長度和當前的位置做爲數據存入Message Message msg = Message.obtain(); Bundle bundle = new Bundle(); bundle.putInt("duration", duration); bundle.putInt("currentPosition", currentPosition); msg.setData(bundle); //發送消息 MainActivity.handler.sendMessage(msg); } }; //執行定時任務,參數1爲任務對象,參數2爲多久開始執行任務,參數3爲任務執行的間隔時間 timer.schedule(task, 50, 1000); }
3.Activity中實現播放音頻,暫停播放,繼續播放和處理更新進度
public class MainActivity extends Activity { private Myconn myconn; private Iservice iservice; private static SeekBar sb_contrller; //定義Handler,處理進度更新操做 public static Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { Bundle data = msg.getData(); int duration = data.getInt("duration"); int currentPosition = data.getInt("currentPosition"); //設置SeekBar的總進度 sb_contrller.setMax(duration); //設置SeekBar的當前位置 sb_contrller.setProgress(currentPosition); }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sb_contrller = (SeekBar) findViewById(R.id.sb_control); //設置SeekBar改變監聽 sb_contrller.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { int position = seekBar.getProgress(); //調用服務中的方法,從SeekBar的拖動到的位置開始播放 iservice.callSeekTo(position); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) { } }); Intent intent = new Intent(this, MusicService.class); //先調用startService()讓服務能一直運行,再調用bindService()方法使Activity能夠調用服務中的方法 startService(intent); myconn = new Myconn(); bindService(intent, myconn, BIND_AUTO_CREATE); } public void click1(View v) { iservice.callPlay(); } public void click2(View v) { iservice.callPause(); } public void click3(View v) { iservice.callReplay(); } private class Myconn implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { iservice = (Iservice) service; } @Override public void onServiceDisconnected(ComponentName name) { } } @Override protected void onDestroy() { unbindService(myconn); super.onDestroy(); } }
運行效果:
本案例實現播放SD卡中的一個mp4文件的視頻。實現該功能須要用到MediaPlayer中的api,此外還須要SurfaceView來顯示播放的視頻。
SurfaceView是用來播放視頻的控件,使用了雙緩衝技術:內存中有兩個畫布,A畫布顯示至屏幕,B畫布在內存中繪製下一幀畫面,繪製完畢後B顯示至屏幕,A在內存中繼續繪製下一幀畫面。
首先建立佈局,佈局中使用SurfaceView:
<RelativeLayout 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" tools:context=".MainActivity" > <SurfaceView android:id="@+id/sv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
在MainActivity中實現播放:
public class MainActivity extends Activity { private SurfaceView sfv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sfv = (SurfaceView) findViewById(R.id.sfv); } public void click(View v) { new Thread() { public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } final MediaPlayer player = new MediaPlayer(); try { player.setDataSource("http://192.168.116.132:8080/test.mp4"); player.prepareAsync(); //獲取SurfaceHolder對象 SurfaceHolder holder = sfv.getHolder(); //給MediaPlayer設置holder對象 player.setDisplay(holder); //設置準備好的監聽,當準備好了以後調開始播放 player.setOnPreparedListener(new OnPreparedListener(){ @Override public void onPrepared(MediaPlayer mp) { //開始播放 player.start(); } }); } catch (Exception e) { e.printStackTrace(); } }; }.start(); } }
運行效果,發現播放不了,以下圖:
這是因爲SurfaceView是重量級組件,對畫面的實時更新要求較高。咱們查看SurfaceView的API文檔,以下圖:
文檔須要咱們實現兩個回調方法,給SurfaceHolder設置CallBack,經過回調能夠知道SurfaceView的狀態,SurfaceView一旦不可見,就會被銷燬,一旦可見,就會被建立,銷燬時中止播放,再次建立時再開始播放。
SurfaceHolder holder = sv.getHolder(); holder.addCallback(new Callback() { //當SurfaceView銷燬時調用 @Override public void surfaceDestroyed(SurfaceHolder holder) { //記錄SurfaceView不可見的時候播放的位置 lastPosition = player.getCurrentPosition(); //判斷player是否爲空或者是否正在播放,若是不爲空而且正在播放,須要將player暫停 if (player != null && player.isPlaying()) { player.pause(); } } //當SurfaceView建立的時候調用,能夠在這個方法中,建立MediaPlayer對象和準備工做等操做 @Override public void surfaceCreated(SurfaceHolder holder) { try { player = new MediaPlayer(); player.setDataSource("http://192.168.116.132:8080/test.mp4"); player.setDisplay(holder); player.prepareAsync(); player.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { player.start(); //調用MediaPlayer的seekTo()方法從指定位置播放,好比當咱們按Home鍵,下次進入的時候須要從以前的位置播放 player.seekTo(lastPosition); } }); } catch (Exception e) { e.printStackTrace(); } } //當SurfaceView改變的時候調用 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } });
運行結果:
VideoView也是播放視頻的一個控件,這個類繼承了SurfaceView。
界面佈局:
<RelativeLayout 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" tools:context=".MainActivity" > <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
Activity中利用VideoView播放視頻:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); VideoView vv = (VideoView) findViewById(R.id.videoView); vv.setVideoPath("http://192.168.116.132:8080/test.mp4"); vv.start(); } }
運行結果:
因爲系統原生的SurfaceView和VideoView對視頻播放功能的不完整,因此現實開發中,開發人員不多使用這兩個控件。開發中通常都會使用開源框架來實現播放視頻的功能,由於開源框架支持播放視頻的格式比較多,並且功能比較強大。Vitamio是一款Android與iOS平臺上的全能多媒體開發框架,它的官網地址是https://www.vitamio.org。下圖是Vitamio的官網首頁:
下面使用Vitamio框架播放視頻。Vitamio開源框架是以類庫的方式提供給開發者的,實際上就是一個安卓工程,咱們將Vitamio類庫導入到Eclipse,右擊查看項目屬性能夠發現它是一個類庫,以下圖:
那麼咱們的項目如何引入Vitamio類庫呢?右擊項目選擇Properties,以下圖:
選擇Android,而後點擊Add按鈕,以下圖:
選擇vitamio_lib,而後點擊OK按鈕,以下圖:
這時候,類庫就引入到了咱們本身的項目中,以下圖:
這時候項目中就能夠使用vitamio中的api了。
首先咱們在佈局中使用vitamio提供的播放視頻的控件:
<RelativeLayout 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" tools:context=".MainActivity" > <io.vov.vitamio.widget.VideoView android:id="@+id/vv" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
在MainActivity中使用VideoView播放視頻:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //檢查類庫 if (!LibsChecker.checkVitamioLibs(this)) { return; } final VideoView vv = (VideoView) findViewById(R.id.vv); //設置播放文件資源路徑 vv.setVideoPath("http://192.168.116.132:8080/test.mp4"); vv.setOnPreparedListener(new OnPreparedListener() { //設置準備監聽,當準備好了以後才能夠播放 @Override public void onPrepared(MediaPlayer mp) { //開始播放 vv.start(); } }); //設置控制器 vv.setMediaController(new MediaController(getApplicationContext())); } }
注意,最後還須要在清單文件中配置一個Activity:
<activity android:name="io.vov.vitamio.activity.InitActivity"></activity>
運行結果:
咱們能夠隱式的開啓系統提供的照相Activity,經過系統照相功能進行拍照。
//建立意圖對象 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //建立照片保存的File對象 File file = new File(Environment.getExternalStorageDirectory(),"paizhao.png"); //設置文件保存的Uri intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); //開啓Activity startActivityForResult(intent, 0);
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); File file = new File(Environment.getExternalStorageDirectory(),"luxiang.3gp"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); //設置視頻的質量 intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); startActivityForResult(intent, 0);