多媒體程式設計
一、對話框
1. 确定取消對話框
- 建立對話框建構器對象,類似工廠模式
AlertDialog.Builder builder = new Builder(this);
- 設定标題和正文
builder.setTitle("警告"); builder.setMessage("若練此功,必先自宮");
- 設定确定和取消按鈕
builder.setPositiveButton("現在自宮", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "恭喜你自宮成功,現在程式退出", 0).show(); } }); builder.setNegativeButton("下次再說", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "若不自宮,一定不成功", 0).show(); } });
- 使用建構器建立出對話框對象
AlertDialog ad = builder.create(); ad.show();
2. 單選對話框
- 建立對話框對象
AlertDialog.Builder builder = new Builder(this); builder.setTitle("選擇你的性别");
- 定義單選選項
final String[] items = new String[]{ "男", "女", "其他" }; // -1表示預設選擇 builder.setSingleChoiceItems(items, -1, new OnClickListener() { // which表示點選的是哪一個選項 @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "您選擇了" + items[which], 0).show(); // 對話框消失 dialog.dismiss(); } }); builder.show();
3. 多選對話框
- 定義多選的選項,因為可以多選,是以需要一個boolean數組來記錄哪些選項被選了
AlertDialog.Builder builder = new Builder(this); builder.setTitle("請選擇你認為最帥的人"); final String[] items = new String[]{ "趙帥哥", "趙師哥", "趙老師", "侃哥" }; // true表示對應位置的選項被選了 final boolean[] checkedItems = new boolean[]{ true, false, false, false, }; builder.setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener() { // 點選某個選項,如果該選項之前沒被選擇,那麼此時isChecked的值為true @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { checkedItems[which] = isChecked; } }); builder.setPositiveButton("确定", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { StringBuffer sb = new StringBuffer(); for(int i = 0;i < items.length; i++){ sb.append(checkedItems[i] ? items[i] + " " : ""); } Toast.makeText(MainActivity.this, sb.toString(), 0).show(); } }); builder.show();
二、國際化
- 字元串國際化:隻要在res檔案夾下建立對應語言的values檔案夾就好了
- 美國英文環境:values-en-rUS
- 中文環境為:values-zh
- 大陸地區中文環境: values-zh-cn
三、樣式與主題
- 樣式與主題定義方式一樣
- 樣式用于布局檔案中的元件
- 主題用于Activity
四、多媒體程式設計
4.1 計算機圖檔大小的計算
- 圖檔大小 = 圖檔的總像素 * 每個像素占用的大小
- 單色圖:每個像素占用1/8個位元組
- 16色圖:每個像素占用1/2個位元組
- 256色圖:每個像素占用1個位元組
- 24位圖:每個像素占用3個位元組
4.2 加載大圖檔到記憶體
- Android系統以ARGB表示每個像素,每個像素占用4個位元組,是以很容易記憶體溢出,是以我們需要對圖檔進行縮放
4.3 對圖檔進行縮放
- 擷取螢幕寬高
Display dp = getWindowManager().getDefaultDisplay(); int screenWidth = dp.getWidth(); int screenHeight = dp.getHeight();
- 擷取圖檔寬高
Options opts = new Options(); // 請求圖檔屬性但不申請記憶體 opts.inJustDecodeBounds = true; BitmapFactory.decodeFile("sdcard/dog.jpg", opts); int imageWidth = opts.outWidth; int imageHeight = opts.outHeight;
- 圖檔的寬高除以螢幕寬高,算出寬和高的縮放比例,取較大值作為圖檔的縮放比例
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; }
- 按縮放比例加載圖檔
// 設定縮放比例 opts.inSampleSize = scale; // 為圖檔申請記憶體 opts.inJustDecodeBounds = false; Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts); iv.setImageBitmap(bm);
4.4 在記憶體中建立圖檔的副本
- 直接加載的bitmap對象是隻讀的,無法修改,要修改圖檔隻能在記憶體中建立出一個一模一樣的bitmap副本,然後修改副本
// 加載原圖 Bitmap srcBm = BitmapFactory.decodeFile("sdcard/photo3.jpg"); iv_src.setImageBitmap(srcBm); // 建立與原圖大小一緻的空白bitmap Bitmap copyBm = Bitmap.createBitmap(srcBm.getWidth(), srcBm.getHeight(), srcBm.getConfig()); // 定義畫筆 Paint paint = new Paint(); // 把紙鋪在畫版上 Canvas canvas = new Canvas(copyBm); // 把srcBm的内容繪制在copyBm上 canvas.drawBitmap(srcBm, new Matrix(), paint); iv_copy.setImageBitmap(copyBm);
4.5 對圖檔進行特效處理
- 首先定義一個矩陣對象
Matrix mt = new Matrix();
- 縮放效果
// x軸縮放1倍,y軸縮放0.5倍 mt.setScale(1, 0.5f);
- 旋轉效果
// 以圖檔寬高的一半中心點為軸點,順時旋轉30度 mt.setRotate(30, copyBm.getWidth() / 2, copyBm.getHeight() / 2);
- 平移
// x軸坐标+10,y軸坐标+20 mt.setTranslate(10, 20);
- 鏡面
// 把X坐标都變成負數 mt.setScale(-1, 1); // 圖檔整體向右移 mt.postTranslate(copyBm.getWidth(), 0);
- 倒影
// 把Y坐标都變成負數 mt.setScale(1, -1); // 圖檔整體向下移 mt.postTranslate(0, copyBm.getHeight());
五、多媒體案例
5.1 畫畫闆
功能:記錄使用者觸摸事件的XY坐标,繪制直線
- 給ImageView設定觸摸偵聽,得到使用者的觸摸事件,并獲知使用者觸摸ImageView的坐标
iv.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { // 觸摸螢幕 case MotionEvent.ACTION_DOWN: // 得到觸摸螢幕時手指的坐标 startX = (int) event.getX(); startY = (int) event.getY(); break; // 在螢幕上滑動 case MotionEvent.ACTION_MOVE: // 使用者滑動手指,坐标不斷的改變,擷取最新坐标 int newX = (int) event.getX(); int newY = (int) event.getY(); // 用上次onTouch方法得到的坐标和本次得到的坐标繪制直線 canvas.drawLine(startX, startY, newX, newY, paint); iv.setImageBitmap(copyBm); startX = newX; startY = newY; break; } return true; } });
- 刷子效果,加粗畫筆
paint.setStrokeWidth(8);
- 調色闆,改變畫筆顔色
paint.setColor(Color.GREEN);
- 儲存圖檔至SD卡
FileOutputStream fos = null; try { fos = new FileOutputStream(new File("sdcard/dazuo.png")); } catch (FileNotFoundException e) { e.printStackTrace(); } // 儲存圖檔 copyBm.compress(CompressFormat.PNG, 100, fos);
- 系統每次收到SD卡就緒廣播時,都會去周遊sd卡的所有檔案和檔案夾,把周遊到的所有多媒體檔案都在MediaStore資料庫儲存一個索引,這個索引包含多媒體檔案的檔案名、路徑、大小
- 圖庫每次打開時,并不會去周遊sd卡擷取圖檔,而是通過内容提供者從MediaStore資料庫中擷取圖檔的資訊,然後讀取該圖檔
- 系統開機或者點選加載sd卡按鈕時,系統會發送sd卡就緒廣播,我們也可以手動發送就緒廣播
Intent intent = new Intent(); intent.setAction(Intent.ACTION_MEDIA_MOUNTED); intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory())); sendBroadcast(intent);
5.2 撕衣服
原理:把穿内衣和穿外衣的照片重疊顯示,内衣照在下面,使用者滑動螢幕時,觸摸的是外衣照,把手指經過的像素都置為透明,内衣照就顯示出來了
- 給螢幕設定移動監聽,把指定的像素設定為透明
iv.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: int newX = (int) event.getX(); int newY = (int) event.getY(); // 把指定的像素變成透明 copyBm.setPixel(newX, newY, Color.TRANSPARENT); iv.setImageBitmap(copyBm); break; } return true; } });
- 每次隻設定一個像素點太慢,以觸摸的像素為圓心,半徑為5畫圓,圓内的像素全部置為透明
for (int i = -5; i < 6; i++) { for (int j = -5; j < 6; j++) { if(Math.sqrt(i * i + j * j) <= 5) copyBm.setPixel(newX + i, newY + j, Color.TRANSPARENT); } }
5.3 音樂播放器
5.3.1 播放服務
- 播放音頻的代碼應該運作在服務中,定義一個播放服務MusicService
- 服務裡定義play、stop、pause、continuePlay等方法
// 播放 private void play() { player.reset(); try { player.setDataSource("sdcard/bzj.mp3"); player.prepare(); } catch (Exception e) { e.printStackTrace(); } player.start(); } // 暫停 private void pause() { player.pause(); } // 停止播放 private void stop() { player.stop(); } // 繼續播放 private void continuePlay() { player.start(); }
- 把這幾個方法抽取成一個接口MusicInterface
- 定義一個中間人類,繼承Binder,實作MusicInterface
- 先start啟動MusicService,再bind
Intent intent = new Intent(this, MusicService.class); startService(intent); bindService(intent, conn, BIND_AUTO_CREATE);
5.3.2 根據播放進度設定進度條
- 擷取目前的播放時間和目前音頻的最長時間
int currentPosition = player.getCurrentPosition(); int duration = player.getDuration();
- 播放進度需要不停的擷取,不停的重新整理進度條,使用計時器每500毫秒擷取一次播放進度
- 發消息至Handler,把播放進度放進Message對象中,在Handler中更新SeekBar的進度
Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { int currentPosition = player.getCurrentPosition(); int duration = player.getDuration(); Message msg = Message.obtain(); // 把播放進度存入Message中 Bundle data = new Bundle(); data.putInt("currentPosition", currentPosition); data.putInt("duration", duration); msg.setData(data); MainActivity.handler.sendMessage(msg); } }, 5, 500);
- 在Activity中定義Handler
static Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { // 取出消息攜帶的資料 Bundle data = msg.getData(); int currentPosition = data.getInt("currentPosition"); int duration = data.getInt("duration"); // 設定播放進度 sb.setMax(duration); sb.setProgress(currentPosition); }; };
5.3.3 拖動進度條改變播放進度
- 設定拖動監聽,拖動後把進度條設定到拖動停止的位置
// 給sb設定一個拖動偵聽 sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { //停止拖動時調用 @Override public void onStopTrackingTouch(SeekBar seekBar) { int progress = seekBar.getProgress(); mi.seekTo(progress); } // 開始拖動時調用 @Override public void onStartTrackingTouch(SeekBar seekBar) { } // 拖動的時候不斷調用 @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } });
5.4 視訊播放器
SurfaceView
- 對畫面的實時更新要求較高
- 雙緩沖技術:記憶體中有兩個畫布,A畫布顯示至螢幕,B畫布在記憶體中繪制下一幀畫面,繪制完畢後B顯示至螢幕,A在記憶體中繼續繪制下一幀畫面
- 播放視訊也是用MediaPlayer,不過跟音頻不同,要設定顯示在哪個SurfaceView
SurfaceView sv = (SurfaceView) findViewById(R.id.sv); SurfaceHolder sh = sv.getHolder(); MediaPlayer player = new MediaPlayer(); player.reset(); try { player.setDataSource("sdcard/2.3gp"); player.setDisplay(sh); player.prepare(); } catch (Exception e) { e.printStackTrace(); } player.start();
- SurfaceView是重量級元件,可見時才會建立
- 給SurfaceHolder設定CallBack,類似于偵聽,可以知道SurfaceView的狀态
sh.addCallback(new Callback() { // SurfaceView銷毀時調用 @Override public void surfaceDestroyed(SurfaceHolder holder) { } // SurfaceView建立時調用 @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } });
- SurfaceView一旦不可見,就會被銷毀,一旦可見,就會被建立,銷毀時停止播放,再次建立時再開始播放
5.5 照相機
- 啟動系統提供的拍照程式
// 隐式啟動系統提供的拍照Activity Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 設定照片的儲存路徑 File file = new File(Environment.getExternalStorageDirectory(), "haha.jpg"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); startActivityForResult(intent, 0);
- 啟動系統提供的攝像程式
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); File file = new File(Environment.getExternalStorageDirectory(), "haha.3gp"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); // 設定儲存視訊檔案的品質 intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); startActivityForResult(intent, 0);