Android 音乐播放器的实现(二)界面的实现
写程序的过程中,想法总会不断地变,有时候会很纠结,到底做哪种效果好,怎么做好呢?
就比如这个音乐播放器,我原来的想法是把列表页面跟歌词页面放在同一个Activity中的两个Fragment,然后通过左右滑动来进行页面的切换。
但是看了酷狗的播放器,它是在启动页面点击了左下角的按钮,就会把歌词页面从右下角斜切上来,我觉得也挺帅的呀,又想做这个效果了。
不管怎么样,先做出一个来再说吧。
下面先看一下效果动态图,是用4.4的AVD来播放的,因为那个斜切上来的动画,会用到一些属性是3.0才支持的,所以2.3的机器做不出来。机器有点差,开个模拟器真是慢到死。
做得比较匆忙,其实还是有不少Bug的,还有一些功能,比如歌词也还没实现,请各位多包涵。
效果图:
下面我们分几步来讲:
界面:
<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" > <ListView android:id="@+id/lvSongs" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="8" android:cacheColorHint="#00000000" /> <RelativeLayout android:layout_width="match_parent" android:background="#BBBBBB" android:layout_height="0dp" android:layout_weight="1" > <Button android:id="@+id/btnDetail" android:layout_width="48dip" android:layout_height="48dip" android:layout_margin="0dip" android:background="@drawable/music_app" android:layout_alignParentBottom="true" android:layout_alignParentTop="true"/> <SeekBar android:id="@+id/pbDuration" style="?android:attr/progressBarStyleHorizontal" android:layout_width="wrap_content" android:layout_marginTop="3dip" android:layout_height="10dip" android:layout_marginBottom="3dip" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/btnDetail" android:maxHeight="1dip" android:minHeight="1dip" android:progressDrawable="@drawable/progress_style" android:thumbOffset="0dip" android:thumb="@drawable/seekbar_thumb"/> <TextView android:id="@+id/tvCurrentMusic" android:layout_width="190dp" android:layout_height="32dip" android:layout_alignBaseline="@+id/btnNext" android:layout_toRightOf="@+id/btnDetail" android:gravity="left|center_vertical" android:paddingLeft="5dip" android:paddingRight="5dip" /> <Button android:id="@+id/btnStartStop" android:layout_width="32dip" android:layout_height="32dip" android:layout_alignBaseline="@+id/btnNext" android:layout_below="@+id/pbDuration" android:layout_toLeftOf="@+id/btnNext" android:background="@drawable/play" android:layout_marginRight="5dip" /> <Button android:id="@+id/btnNext" android:layout_width="24dp" android:layout_height="24dp" android:layout_alignParentRight="true" android:layout_below="@+id/pbDuration" android:layout_marginRight="20dip" android:background="@drawable/forward" /> </RelativeLayout> </LinearLayout>其中进度条是用自定义的风格的(也是在CSDN上找的,还没去具体了解,到时了解了再讲一下)。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res/com.example.nature" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/tvTitle" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:background="#00EEEE" android:gravity="center_vertical" android:paddingLeft="5dip" android:paddingRight="5dip" /> <TextView android:id="@+id/tvLyric" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="9" android:gravity="center_vertical" android:text="To Continue...." /> <LinearLayout android:id="@+id/llProgress" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:gravity="center_horizontal" android:orientation="horizontal" > <TextView android:id="@+id/tvTimeElapsed" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|center_vertical" android:layout_weight="1" android:text="00:00" /> <SeekBar android:id="@+id/pbDuration" style="?android:attr/progressBarStyleHorizontal" android:layout_width="0dip" android:layout_height="10dip" android:layout_gravity="center_horizontal|center_vertical" android:layout_weight="6" android:maxHeight="1dip" android:minHeight="1dip" android:progressDrawable="@drawable/progress_style" android:secondaryProgress="0" android:thumb="@drawable/seekbar_thumb" android:thumbOffset="0dip" /> <TextView android:id="@+id/tvDuration" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|center_vertical" android:layout_weight="1" android:text="00:00" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="2" android:orientation="horizontal" > <com.example.nature.CustomAudioIcon android:id="@+id/btnMode" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" custom:type="mode" custom:color="#66DD22" /> <com.example.nature.CustomAudioIcon android:id="@+id/btnPrevious" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" custom:type="backward" custom:color="#66DD22" /> <com.example.nature.CustomAudioIcon android:id="@+id/btnStartStop" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" custom:type="start" custom:color="#66DD22" /> <com.example.nature.CustomAudioIcon android:id="@+id/btnNext" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" custom:type="forward" custom:color="#66DD22" /> <com.example.nature.CustomAudioIcon android:id="@+id/btnExit" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" custom:type="exit" custom:color="#66DD22" /> </LinearLayout> </LinearLayout>这里面有三部分:
控制逻辑
public class MainActivity extends Activity implements OnClickListener{ public static final String TAG = "com.example.nature.MAIN_ACTIVITY"; private ListView lvSongs; private SeekBar pbDuration; private TextView tvCurrentMusic; private List<MusicInfo> musicList; private int currentMusic; // The music that is playing. private int currentPosition; //The position of the music is playing. private int currentMax; private Button btnStartStop; private Button btnNext; private Button btnDetail; private ProgressReceiver progressReceiver; private NatureBinder natureBinder; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { natureBinder = (NatureBinder) service; } }; private void connectToNatureService(){ Intent intent = new Intent(MainActivity.this, NatureService.class); bindService(intent, serviceConnection, BIND_AUTO_CREATE); } @Override protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "OnCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MusicLoader musicLoader = MusicLoader.instance(getContentResolver()); musicList = musicLoader.getMusicList(); connectToNatureService(); initComponents(); } public void onResume(){ Log.v(TAG, "OnResume register Progress Receiver"); super.onResume(); registerReceiver(); if(natureBinder != null){ if(natureBinder.isPlaying()){ btnStartStop.setBackgroundResource(R.drawable.pause); }else{ btnStartStop.setBackgroundResource(R.drawable.play); } natureBinder.notifyActivity(); } } public void onPause(){ Log.v(TAG, "OnPause unregister Progress Receiver"); super.onPause(); unregisterReceiver(progressReceiver); } public void onStop(){ Log.v(TAG, "OnStop"); super.onStop(); } public void onDestroy(){ Log.v(TAG, "OnDestroy"); super.onDestroy(); if(natureBinder != null){ unbindService(serviceConnection); } } private void initComponents(){ pbDuration = (SeekBar) findViewById(R.id.pbDuration); pbDuration.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) {} @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if(fromUser){ natureBinder.changeProgress(progress); } } }); tvCurrentMusic = (TextView) findViewById(R.id.tvCurrentMusic); btnStartStop = (Button)findViewById(R.id.btnStartStop); btnStartStop.setOnClickListener(this); btnNext = (Button)findViewById(R.id.btnNext); btnNext.setOnClickListener(this); btnDetail = (Button) findViewById(R.id.btnDetail); btnDetail.setOnClickListener(this); MusicAdapter adapter = new MusicAdapter(); lvSongs = (ListView) findViewById(R.id.lvSongs); lvSongs.setAdapter(adapter); lvSongs.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { currentMusic = position; natureBinder.startPlay(currentMusic,0); if(natureBinder.isPlaying()){ btnStartStop.setBackgroundResource(R.drawable.pause); } } }); } private void registerReceiver(){ progressReceiver = new ProgressReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(NatureService.ACTION_UPDATE_PROGRESS); intentFilter.addAction(NatureService.ACTION_UPDATE_DURATION); intentFilter.addAction(NatureService.ACTION_UPDATE_CURRENT_MUSIC); registerReceiver(progressReceiver, intentFilter); } class MusicAdapter extends BaseAdapter{ @Override public int getCount() { return musicList.size(); } @Override public Object getItem(int position) { return musicList.get(position); } @Override public long getItemId(int position) { return musicList.get(position).getId(); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if(convertView == null){ convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.music_item, null); ImageView pImageView = (ImageView) convertView.findViewById(R.id.albumPhoto); TextView pTitle = (TextView) convertView.findViewById(R.id.title); TextView pDuration = (TextView) convertView.findViewById(R.id.duration); TextView pArtist = (TextView) convertView.findViewById(R.id.artist); viewHolder = new ViewHolder(pImageView, pTitle, pDuration, pArtist); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.imageView.setImageResource(R.drawable.audio); viewHolder.title.setText(musicList.get(position).getTitle()); viewHolder.duration.setText(FormatHelper.formatDuration(musicList.get(position).getDuration())); viewHolder.artist.setText(musicList.get(position).getArtist()); return convertView; } } class ViewHolder{ public ViewHolder(ImageView pImageView, TextView pTitle, TextView pDuration, TextView pArtist){ imageView = pImageView; title = pTitle; duration = pDuration; artist = pArtist; } ImageView imageView; TextView title; TextView duration; TextView artist; } @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) { switch (v.getId()) { case R.id.btnStartStop: play(currentMusic,R.id.btnStartStop); break; case R.id.btnNext: natureBinder.toNext(); break; case R.id.btnDetail: Intent intent = new Intent(MainActivity.this,DetailActivity.class); intent.putExtra(DetailActivity.MUSIC_LENGTH, currentMax); intent.putExtra(DetailActivity.CURRENT_MUSIC, currentMusic); intent.putExtra(DetailActivity.CURRENT_POSITION, currentPosition); startActivity(intent); break; } } private void play(int position, int resId){ if(natureBinder.isPlaying()){ natureBinder.stopPlay(); btnStartStop.setBackgroundResource(R.drawable.play); }else{ natureBinder.startPlay(position,currentPosition); btnStartStop.setBackgroundResource(R.drawable.pause); } } class ProgressReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(NatureService.ACTION_UPDATE_PROGRESS.equals(action)){ int progress = intent.getIntExtra(NatureService.ACTION_UPDATE_PROGRESS, 0); if(progress > 0){ currentPosition = progress; // Remember the current position pbDuration.setProgress(progress / 1000); } }else if(NatureService.ACTION_UPDATE_CURRENT_MUSIC.equals(action)){ //Retrive the current music and get the title to show on top of the screen. currentMusic = intent.getIntExtra(NatureService.ACTION_UPDATE_CURRENT_MUSIC, 0); tvCurrentMusic.setText(musicList.get(currentMusic).getTitle()); }else if(NatureService.ACTION_UPDATE_DURATION.equals(action)){ //Receive the duration and show under the progress bar //Why do this ? because from the ContentResolver, the duration is zero. currentMax = intent.getIntExtra(NatureService.ACTION_UPDATE_DURATION, 0); int max = currentMax / 1000; Log.v(TAG, "[Main ProgressReciver] Receive duration : " + max); pbDuration.setMax(currentMax / 1000); } } } }
接下来说说主要的实现逻辑:
public void onResume(){ Log.v(TAG, "OnResume register Progress Receiver"); super.onResume(); registerReceiver(); if(natureBinder != null){ ... natureBinder.notifyActivity(); } }在Service中的Binder也提供了这样一个接口,来让Activity告诉Service,我想知道当前在播哪一首歌。。。
class NatureBinder extends Binder{ public void startPlay(int currentMusic, int currentPosition){ play(currentMusic,currentPosition); } public void stopPlay(){ stop(); } public void toNext(){ playNext(); } public void toPrevious(){ playPrevious(); } /** * MODE_ONE_LOOP = 1; * MODE_ALL_LOOP = 2; * MODE_RANDOM = 3; * MODE_SEQUENCE = 4; */ public void changeMode(){ currentMode = (currentMode + 1) % 4; Log.v(TAG, "[NatureBinder] changeMode : " + currentMode); Toast.makeText(NatureService.this, MODE_DESC[currentMode], Toast.LENGTH_SHORT).show(); } /** * return the current mode * MODE_ONE_LOOP = 1; * MODE_ALL_LOOP = 2; * MODE_RANDOM = 3; * MODE_SEQUENCE = 4; * @return */ public int getCurrentMode(){ return currentMode; } /** * The service is playing the music * @return */ public boolean isPlaying(){ return isPlaying; } /** * Notify Activities to update the current music and duration when current activity changes. */ public void notifyActivity(){ toUpdateCurrentMusic(); toUpdateDuration(); } /** * Seekbar changes * @param progress */ public void changeProgress(int progress){ ... }
public class DetailActivity extends Activity implements OnClickListener{ private static final String TAG = "com.example.natrue.DetailActivity"; public static final String MUSIC_LENGTH = "com.example.nature.DetailActivity.MUSIC_LENGTH"; public static final String CURRENT_POSITION = "com.example.nature.DetailActivity.CURRENT_POSITION"; public static final String CURRENT_MUSIC = "com.example.nature.DetailActivity.CURRENT_MUSIC"; private SeekBar pbDuration; private TextView tvTitle,tvTimeElapsed, tvDuration; private List<MusicInfo> musicList; private int currentMusic; private int currentPosition; private ProgressReceiver progressReceiver; private NatureBinder natureBinder; private int[] btnResIds = new int[] { R.id.btnMode, R.id.btnPrevious, R.id.btnStartStop, R.id.btnNext, R.id.btnExit }; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { natureBinder = (NatureBinder) service; if(natureBinder.isPlaying()){ CustomAudioIcon btnStartStop = (CustomAudioIcon)findViewById(R.id.btnStartStop); btnStartStop.setFlagStart(false); } CustomAudioIcon btnMode = (CustomAudioIcon)findViewById(R.id.btnMode); btnMode.setCurrentMode(natureBinder.getCurrentMode()); } }; private void connectToNatureService(){ Intent intent = new Intent(DetailActivity.this, NatureService.class); bindService(intent, serviceConnection, BIND_AUTO_CREATE); } @Override public void onCreate(Bundle savedInstanceState){ Log.v(TAG, "OnCreate"); super.onCreate(savedInstanceState); overridePendingTransition(R.anim.push_right_in,R.anim.hold); MusicLoader musicLoader = MusicLoader.instance(getContentResolver()); musicList = musicLoader.getMusicList(); setContentView(R.layout.detail_layout); connectToNatureService(); initComponents(); } public void onResume(){ Log.v(TAG, "OnResume"); super.onResume(); initReceiver(); } public void onPause(){ Log.v(TAG, "OnPause unregister progress receiver"); super.onPause(); unregisterReceiver(progressReceiver); overridePendingTransition(R.anim.hold, R.anim.push_right_out); } public void onStop(){ Log.v(TAG, "OnStop"); super.onStop(); } public void onDestroy(){ Log.v(TAG, "Destroy"); super.onDestroy(); if(natureBinder != null){ unbindService(serviceConnection); } } private void initComponents(){ tvTitle = (TextView) findViewById(R.id.tvTitle); currentMusic = getIntent().getIntExtra(CURRENT_MUSIC,0); tvTitle.setText(musicList.get(currentMusic).getTitle()); tvDuration = (TextView) findViewById(R.id.tvDuration); int max = getIntent().getIntExtra(MUSIC_LENGTH, 0); tvDuration.setText(FormatHelper.formatDuration(max)); pbDuration = (SeekBar) findViewById(R.id.pbDuration); pbDuration.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) {} @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if(fromUser){ natureBinder.changeProgress(progress); } } }); pbDuration.setMax(max/1000); currentPosition = getIntent().getIntExtra(CURRENT_POSITION,0); pbDuration.setProgress(currentPosition / 1000); tvTimeElapsed = (TextView) findViewById(R.id.tvTimeElapsed); tvTimeElapsed.setText(FormatHelper.formatDuration(currentPosition)); for(int resId : btnResIds){ CustomAudioIcon icon = (CustomAudioIcon)findViewById(resId); icon.setOnClickListener(this); } } private void initReceiver(){ progressReceiver = new ProgressReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(NatureService.ACTION_UPDATE_PROGRESS); intentFilter.addAction(NatureService.ACTION_UPDATE_DURATION); intentFilter.addAction(NatureService.ACTION_UPDATE_CURRENT_MUSIC); registerReceiver(progressReceiver, intentFilter); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btnStartStop: play(currentMusic,R.id.btnStartStop); break; case R.id.btnNext: natureBinder.toNext(); break; case R.id.btnPrevious: natureBinder.toPrevious(); break; case R.id.btnExit: finish(); break; case R.id.btnMode: natureBinder.changeMode(); break; default: break; } } private void play(int currentMusic, int resId){ CustomAudioIcon btnStartStop = (CustomAudioIcon) findViewById(resId); if(btnStartStop.isStartStatus()){ natureBinder.stopPlay(); }else{ natureBinder.startPlay(currentMusic,currentPosition); } } class ProgressReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(NatureService.ACTION_UPDATE_PROGRESS.equals(action)){ int progress = intent.getIntExtra(NatureService.ACTION_UPDATE_PROGRESS, currentPosition); if(progress > 0){ currentPosition = progress; // Remember the current position tvTimeElapsed.setText(FormatHelper.formatDuration(progress)); pbDuration.setProgress(progress / 1000); } }else if(NatureService.ACTION_UPDATE_CURRENT_MUSIC.equals(action)){ //Retrieve the current music and get the title to show on top of the screen. currentMusic = intent.getIntExtra(NatureService.ACTION_UPDATE_CURRENT_MUSIC, 0); tvTitle.setText(musicList.get(currentMusic).getTitle()); }else if(NatureService.ACTION_UPDATE_DURATION.equals(action)){ //Receive the duration and show under the progress bar //Why do this ? because from the ContentResolver, the duration is zero. int duration = intent.getIntExtra(NatureService.ACTION_UPDATE_DURATION, 0); tvDuration.setText(FormatHelper.formatDuration(duration)); pbDuration.setMax(duration / 1000); } } }实现的功能也是类似的,不过为了实现动画效果斜切入的一个功能,我们可以看到在OnCreate函数和OnPause函数中,分别有如下两行代码:
@Override public void onCreate(Bundle savedInstanceState){ ... overridePendingTransition(R.anim.push_right_in,R.anim.hold); ... } @Override public void onPause(){ ... overridePendingTransition(R.anim.hold, R.anim.push_right_out) ; }
public void overridePendingTransition(int enterAnim, int exitAnim) { try { ActivityManagerNative.getDefault().overridePendingTransition( mToken, getPackageName(), enterAnim, exitAnim); } catch (RemoteException e) { } }
<set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromYDelta="100%p" android:toYDelta="0" android:fromXDelta="100%p" android:toXDelta="0" android:duration="500"/> <rotate android:fromDegrees="90" android:toDegrees="0" android:pivotX="100%p" android:pivotY="100%p" android:duration="500"/> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="500" /> </set>
res/anim/push_right_out.xml
<set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="0" android:toXDelta="100%p" android:fromYDelta="0" android:toYDelta="100%p" android:duration="500"/> <rotate android:fromDegrees="0" android:toDegrees="90" android:pivotX="100%p" android:pivotY="100%p" android:duration="500"/> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="500" /> </set>
res/anim/hold.xml
<set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="0" android:toXDelta="0" android:duration="500"/> </set>
其中push_right_in 和 push_right_out实现的是相反的效果,一个是从右边斜切进来,一个是斜切出去,而hold是为了保证MainActivity不动的。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。