專案簡介
可以掃描記憶卡內的影片跟音樂檔案,並播放。並同時支援手機跟平板的畫面。



專案需求
- 使用File物件來取的音樂跟影片檔案清單。
- 使用MediaPlayer物件播放影片及音樂。
- Fragment應用。
- Notification物件應用。
- Service實戰。
- 同時支援手機與平板的應用程式開發。
實作步驟
- 建立Master/Detail Flow專案。
- 專案預設執行後看不到ActionBar,所以先將java/{package}/ItemListActivity.java內的
ItemListActivity
的父類別改為ActionBarActivity
。 - 將影片放上傳到模擬器的/storage/sdcard/Movies目錄內,將音樂上傳到/storage/sdcard/Music目錄內。
- 在AndroidManifest.xml內加入權限:
android.permission.READ_EXTERNAL_STORAGE
- 將res/layout/fragment_item_detail.xml檔案複製一份到res/layout-sw600dp目錄內。
- 將res/layout/fragment_item_detail.xml版面設計如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="match_parent" android:background="#000" android:orientation="vertical" tools:context=".ItemDetailFragment"> <TextView android:id="@+id/item_detail" style="?android:attr/textAppearanceLarge" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:textColor="#ff0" android:textIsSelectable="true"/> <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_gravity="center" android:layout_weight="12"/> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:orientation="horizontal" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1"> <Button android:id="@+id/play" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="播放"/> <Button android:id="@+id/pause" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="暫停"/> <Button android:id="@+id/stop" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="停止"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:orientation="horizontal" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1"> <Button android:id="@+id/forward" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="快轉"/> <Button android:id="@+id/backward" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="倒轉"/> <!-- ToggleButton有On跟Off兩種狀態--> <ToggleButton android:id="@+id/repeat" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:background="#833" android:textColor="#ff0" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1"/> </LinearLayout> </LinearLayout>
將res/layout-sw600dp/fragment_item_detail.xml版面設計如下:
- 修改java/{package}/dummy/DummyContent.java原始碼檔案,加入將記憶卡內的影片和音樂檔案掃描出來:
static { String musicPath = "/storage/sdcard/Music"; // 模擬器路徑 File musicFolder = new File(musicPath); String videoPath = "/storage/sdcard/Movies"; // 模擬器路徑 File videoFolder = new File(videoPath); // 建立MediaFileFilter物件來過濾檔案 FilenameFilter mediafilefilter = new FilenameFilter() { private String[] filter = {".mp3",".ogg",".3gp",".mp4"}; @Override public boolean accept(File dir, String filename) { // 依照filter字串陣列內的副檔名來判斷是不是要的檔案類型 for(int i= 0 ; i < filter.length ; i++) { if(filename.endsWith(filter[i])) return true; } return false; } }; // 取得影片目錄下的所有音樂檔案 File[] listVideo = videoFolder.listFiles(mediafilefilter); // 將影片檔名跟路徑加入List中 for(int i = 0 ; i < listVideo.length ; i++) { addItem(new DummyItem(listVideo[i].getPath(), listVideo[i].getName())); } // 取得音樂目錄下的所有音樂檔案(id=路徑, content=檔名) File[] listMusic = musicFolder.listFiles(mediafilefilter); // 將音樂檔名跟路徑加入List中(id=路徑, content=檔名) for(int i = 0 ; i < listMusic.length ; i++) { addItem(new DummyItem(listMusic[i].getPath(), listMusic[i].getName())); } }
- 修改java/{package}/ItemDetailFragment.java原始碼檔案,加入影片和音樂的播放功能:
private VideoView videoView; // 用來播影片的物件 private MediaPlayer mp; // 用來播音樂的物件 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments().containsKey(ARG_ITEM_ID)) { // 使用者選擇的項目在這裡會被取出來成為DummyItem類別, id=路徑, content=檔名 mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID)); } } @Override public void onPause() { super.onPause(); if(mp != null) mp.stop(); // 停止播放音樂 if(videoView != null) videoView.stopPlayback(); // 停止播放影片 } @Override public void onResume() { super.onResume(); if(isMusic(mItem.content)) loadSong(); // 使用者選的是音樂的話,就初始化音樂 if(isVideo(mItem.content)) loadMovie(); // 使用者選的是影片的話,就初始化影片 } // 判斷檔名是不是音樂檔 private boolean isMusic(String filename) { String[] filter = {".mp3"}; for(int i = 0 ; i < filter.length ; i++) { if(filename.endsWith(filter[i])) return true; } return false; } // 判斷檔名是不是影片檔 private boolean isVideo(String filename) { String[] filter = {".ogg",".3gp",".mp4"}; for(int i = 0 ; i < filter.length ; i++) { if(filename.endsWith(filter[i])) return true; } return false; } private void loadMovie() { // 關聯VideoView videoView = (VideoView)getView().findViewById(R.id.videoView); // 加上MediaController來控制影片 MediaController mc = new MediaController(getActivity()); videoView.setMediaController(mc); // 設定要撥放的影片路徑 videoView.setVideoPath(mItem.id); // 設定OnCompletionListener給VideoView videoView.setOnCompletionListener(onCompletionListener); // 開始撥放 videoView.start(); } // 影片播完完畢傾聽器 MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { Toast.makeText(getActivity(), "影片播完了", Toast.LENGTH_SHORT).show(); } }; // 初始化要播放的音樂檔 private void loadSong() { mp = new MediaPlayer(); // 初始化MediaPlayer try { mp.reset(); // 清除緩衝區資料 mp.setDataSource(mItem.id); // 設定檔案路徑 mp.prepare(); // 緩衝音樂 } catch(IOException e) { Toast.makeText(getActivity(), "錯誤: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } // 從頭播放 mp.seekTo(0); } // 處理按鈕事件 View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View v) { // 如果不是音樂檔, 就不處理按鈕事件 if(!isMusic(mItem.content)) return; switch(v.getId()) { case R.id.play: mp.start(); // 播放 showNotification(getActivity(), android.R.drawable.ic_media_play, "Play: " + mItem.content, ItemListActivity.class); break; case R.id.pause: mp.pause(); // 暫停 showNotification(getActivity(), android.R.drawable.ic_media_pause, "Pause: " + mItem.content, ItemListActivity.class); break; case R.id.stop: mp.stop(); // 停止 showNotification(getActivity(), android.R.drawable.ic_delete, "Stop: " + mItem.content, ItemListActivity.class); loadSong(); // 停止後,要重新播放,必須重新緩衝音樂 break; case R.id.backward: // 往前跳5秒鐘 if(mp.getCurrentPosition() - 5000 > 0) mp.seekTo(mp.getCurrentPosition() - 5000); break; case R.id.forward: // 往後跳5秒鐘 if(mp.getCurrentPosition() + 5000 < mp.getDuration()) mp.seekTo(mp.getCurrentPosition() + 5000); break; case R.id.repeat: ToggleButton tb = (ToggleButton)getView().findViewById(R.id.repeat); if(tb.isChecked()) { tb.setText("重複"); mp.setLooping(true); // 設定重複播放 } else { tb.setText("不重複"); mp.setLooping(false); // 設定不重複播放 } break; } } }; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_item_detail, container, false); // Show the dummy content as text in a TextView. if (mItem != null) { ((TextView) rootView.findViewById(R.id.item_detail)).setText(mItem.id); } // 設定按鈕傾聽器 rootView.findViewById(R.id.play).setOnClickListener(onClickListener); rootView.findViewById(R.id.pause).setOnClickListener(onClickListener); rootView.findViewById(R.id.stop).setOnClickListener(onClickListener); rootView.findViewById(R.id.backward).setOnClickListener(onClickListener); rootView.findViewById(R.id.forward).setOnClickListener(onClickListener); rootView.findViewById(R.id.repeat).setOnClickListener(onClickListener); ((ToggleButton)rootView.findViewById(R.id.repeat)).setText("不重複"); return rootView; } // 將Notification包裝成一個方法 // iconId: 要顯示的圖示 // msg: 要顯示的文字訊息 private void showNotification(Context context, int iconId, String msg, Class<?> cls) { // 在Notification Drawer點下去後要呼叫的Activity PendingIntent pit = PendingIntent.getActivity( context, // 目前所在的Activity 0, // request code, 這裡不重要 new Intent(context.getApplicationContext(), cls), // 想要備觸發的Intent PendingIntent.FLAG_UPDATE_CURRENT); // 建立Notification NotificationCompat.Builder b = new NotificationCompat.Builder(context); b.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS); b.setSmallIcon(iconId); // 設定Icon b.setTicker(msg); // 設定系統列訊息 b.setContentTitle("Title"); // 設定標題 b.setContentText(msg); // 訊息 b.setContentIntent(pit); // 設定在系統頁被點擊時要觸發的Intent b.setWhen(System.currentTimeMillis()); // 立即顯示 b.setContentInfo("info"); // 設定內容 Notification n = b.build(); // 下面兩行如果是在Activity裡面要丟訊息到系統列須使用NotificationManager NotificationManager nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); nm.notify(1, n); // 在Service內要改成呼叫startForeground()方法 // 參數1: Notification ID, 用來辨識或重新設定用 // 參數2: Notification物件 //startForeground(1, n); }
注意:
onCreate()
方法和onCreateView()
原來已經存在,必須先刪除原本的版本。 - 將程式執行在手機模擬器和平板模擬器中觀察結果。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="match_parent" android:background="#000" android:orientation="vertical" tools:context=".ItemDetailFragment"> <TextView android:id="@+id/item_detail" style="?android:attr/textAppearanceLarge" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:textColor="#ff0" android:textIsSelectable="true"/> <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_gravity="center" android:layout_weight="12"/> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:orientation="horizontal" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1"> <Button android:id="@+id/play" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="播放"/> <Button android:id="@+id/pause" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="暫停"/> <Button android:id="@+id/stop" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="停止"/> <Button android:id="@+id/forward" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="快轉"/> <Button android:id="@+id/backward" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:textColor="#ff0" android:background="#833" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1" android:text="倒轉"/> <!-- ToggleButton有On跟Off兩種狀態--> <ToggleButton android:id="@+id/repeat" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_margin="2dp" android:background="#833" android:textColor="#ff0" android:shadowDx="3" android:shadowDy="3" android:shadowColor="#000" android:shadowRadius="1"/> </LinearLayout> </LinearLayout>
如果你想讓影片畫面延伸到跟VideoView一樣大小,可以參考這裡。完整專案下載:MyMediaPlayerAS.zip(Android Studio專案)