Android 音乐播放器的开发教程(一) ----- 小达
2014-12-25 10:53
351 查看
Android小卷毛音乐播放器开发教程(一)
前言:2014年12月5号的时候,刚加入学校里的团队不久,组长会给新人发布任务,让我们在做项目的过程中能快速的熟悉android的各项基本知识。第一次的任务就是做一个android的音乐播放器。当时拿到任务的时候也是愣了≡ ̄﹏ ̄≡,什么都不知道,怎么做。然而身为一个程序员,速效的学习并且能灵活的使用,是一项必备的技能,面对问题,咱不能怂,怂就输一半儿,二话不说就是干
。大二的小达课还是比较多的,抽出零碎的时间,还有牺牲周末等等,花了18天的时间,憋出了个播放器(辣鸡播放器,大神勿喷,初次接触android
)。期间很感谢简美音乐播放器(作者:小巫)的许多易懂的教程,还有网上各路大神的代码,下面就展示下小达的成果。(小心被亮瞎,界面丑暴,23333)。
第一阶段完成的功能:
1.获取歌曲并显示为歌曲列表
2.点击列表自动播放
3.暂停、上一首、下一首
4.播放模式的选择
5.自动切换到下一首
第二阶段完成的功能:
1.歌词的动态显示(没有滚起来。。。)
2.我的最爱歌曲加入和删除
3.最近播放的歌曲记录
待完成的任务:
1.专辑图片的获取(搞了好长时间没弄出来)
2.来电的处理
3.歌曲分享等模块
4.创建自己的歌单
废话不多说,直接上源码。小卷毛音乐播放器下载
各位飘过的、路过的大神,代码写的有点乱,求轻喷,23333,刚接触android,求指点,我会好好的改正,谢谢咯。
小卷毛播放器的全貌,,,自己都不忍直视,这界面还是后来做到一半了改过的,没有设计师的苦逼程序猿,之前的那个UI丑的不能要
。做完这个东西最大的体会就是,UI设计还是超重要的,改起来有点麻烦(好像是个人太水了哈。。。)。有人会问,,我这素材到哪里找的?木有设计师,只能自己默默的反编译天天动听了,,偷别人设计师的,23333,会在后面一篇双手奉上我的教程。
简单说一下,主界面能用的模块有我的音乐、我的最爱、最近播放。整个播放器我先用的是好多个Activity,但是做到中间发现Activity不怎么灵活,在学习的过程中发现有个东西叫Fragment(不要翻译成碎片,不然怪怪的,23333)。这个Fragment更加灵活方便。后来花了2天的时间边学边改,真是痛苦。将整个播放器只用了一个Activity,其余的全是fragment。
先贴出activity的源代码吧。
package com.example.dada.myapplication; import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.Notification; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Color; import android.os.Bundle; import android.os.Message; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.PopupWindow; import android.widget.SeekBar; import android.widget.SimpleCursorAdapter; import android.widget.TextView; import android.widget.Toast; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import android.os.Handler; public class MainActivity extends Activity implements AppConstant, //用Fragment的时候,Activity实现Fragment的内部接口 MainFragment.OnMainFragmentInteractionListener, MyMusicFragment.OnMyMusicFragmentInteractionListener, PlayFragment.OnPlayFragmentInteractionListener, RecentlyPlayFragment.OnFragmentInteractionListener, MyFavoriteFragment.OnFragmentInteractionListener{ private String music_url; //记录音乐的路径 private boolean isPause; //记录当前播放器的是否暂停 private boolean isChangToNext; //下一首按钮 private boolean notification_previous_music; //通知栏上一首 private boolean notification_next_music; //通知栏下一首 private boolean notification_pause_music; //通知栏停止播放 private boolean notification_exit; //通知栏退出 private int index; //歌词的索引 private int play_mode; //播放模式 private int current_position; //当前进度条的位置 private int current_position_bar; private long exitTime; //记录连续按两次退出 private MsgReceiver msgReceiver; //Message的***,接收歌曲的信息 private BarReceiver barReceiver; //进度条的*** private NotificationReceiver notificationReceiver; //通知栏广播*** private Button latestPlayListButton; //最近播放 private ImageButton play_button; //播放按钮控件 private ImageButton previous_song_button; //上一首按钮 private ImageButton next_song_button; //下一首按钮控件 private ImageButton play_mode_button; //播放模式按钮 private ListView music_list; //音乐列表控件 private ListView recently_play_music_list; //最近播放音乐列表 private SeekBar seek_bar; //进度条控件 private Intent intent_to_service; //向service发送广播的intent private Intent intent_to_fragment; //向fragment发送广播的intent private Intent intent_to_changeMusic; //换歌曲 private Intent progress_change_intent_to_service; //进度条拖动 private TextView music_info_textView; //显示歌曲信息的textview private TextView singer_info_textView; //显示歌手信息的textview private FindSongs finder; //查找歌曲的类的实例 private PopupWindow popupPlayModeWindow; //播放模式下拉窗口 private MainFragment mainFragment; //主要的fragment private NotificationManager myNotificationManager; //通知栏Manager private Notification myNotification; //通知栏 private FragmentManager fragmentManager; //用于管理Fragment的相互切换 private FragmentTransaction fragmentTransaction; /*FragmentManage: FragmentManager能够实现管理activity中fragment. 通过调用activity的getFragmentManager()取得它的实例. FragmentTransaction: FragmentTransaction对fragment进行添加,移除,替换,以及执行其他动作。 从 FragmentManager 获得一个FragmentTransaction的实例 : FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); */ private MyMusicFragment myMusicFragment; //我的音乐Fragment实例 public static boolean isFavorite = false; //当前音乐是否为我的最爱 public static String[] music_array = null; //自动补全的数组 public static List<Mp3Info> mp3Infos; //歌曲列表 public static int music_position; //音乐的位置 public static MyDataBase myDataBase; //自己封装的一个数据库类实例 @Override public void onMainFragmentInteraction(int message) { //MainFragment的回调函数 switch(message){ case PlayerMsg.CHANGE_TO_MY_MUSIC_FRAGMENT: onMyMusicFragmentInteraction(PlayerMsg.CHANGE_TO_MY_MUSIC_FRAGMENT); break; case PlayerMsg.FRAGMENT_RANDOM_PLAY: changeMusic(PlayerMsg.RANDOM_MODE,AppConstant.PlayerMsg.NEXT_MUSIC,mp3Infos); Toast.makeText(this,"小卷毛为您点了一首歌",Toast.LENGTH_SHORT).show(); } } @Override public void onMyMusicFragmentInteraction(int message,int position) { //MyMusicFragment的回调函数 if(message == PlayerMsg.LIST_CLICK){ if (mp3Infos != null) { isPause = false; initService(position); } } } public void onMyMusicFragmentInteraction(int message) { //MyMusicFragment回调函数 myMusicFragment = new MyMusicFragment(); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.setCustomAnimations(R.animator.slide_in_left,R.animator.slide_out_right); switch(message){ case PlayerMsg.CHANGE_TO_MY_MUSIC_FRAGMENT: fragmentTransaction.replace(R.id.fragment_layout, myMusicFragment); fragmentTransaction.addToBackStack(null); fragmentTransaction.commit(); break; case PlayerMsg.BACK_TO_MAIN_FRAGMENT: fragmentTransaction.replace(R.id.fragment_layout, mainFragment); fragmentTransaction.addToBackStack(null); fragmentTransaction.commit(); break; } } public void onPlayFragmentInteraction(int message){ //PlayFragment的回调函数 Mp3Info mp3_Info = mp3Infos.get(music_position); switch (message){ case PlayerMsg.DISMISS_CLICK: myMusicFragment = new MyMusicFragment(); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.setCustomAnimations(R.animator.slide_in_up,R.animator.slide_out_down); fragmentTransaction.replace(R.id.fragment_layout, mainFragment); fragmentTransaction.addToBackStack(null); fragmentTransaction.commit(); break; case PlayerMsg.ADD_TO_F***ORITE: isFavorite = true; mp3_Info.setFavorite(isFavorite); myDataBase.AddData( MyDataBase.TABLE_NAME_F***ORITE, (int)mp3_Info.getId(), mp3_Info.getTitle(), mp3_Info.getArtist(), mp3_Info.getDuration(), mp3_Info.getUrl(), (int)mp3_Info.getAlbum_id() ); break; case PlayerMsg.DELETE_FROM_F***ORITE: isFavorite = false; mp3Infos.get(music_position).setFavorite(isFavorite); myDataBase.DeleteData( MyDataBase.TABLE_NAME_F***ORITE, (int)mp3_Info.getId()); break; } } public void onFragmentInteraction(){} public void onFragmentInteraction(int message,int position){ // if(message == PlayerMsg.LIST_CLICK){ // if (mp3Infos != null) { // isPause = false; // initService(position); // } // } } public void onFavoriteFragmentInteraction(){ } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.activity_main); mainFragment = new MainFragment(); fragmentManager = getFragmentManager(); fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.fragment_layout, mainFragment).commit(); index = 0; exitTime = 0; music_position = 0; current_position = 0; play_mode = AppConstant.PlayerMsg.LOOP_MODE; isPause = true; finder = new FindSongs(); msgReceiver = new MsgReceiver(); barReceiver = new BarReceiver(); notificationReceiver = new NotificationReceiver(); View play_mode_window = this.getLayoutInflater().inflate(R.layout.popup_window_layout,null); popupPlayModeWindow = new PopupWindow(play_mode_window,280,360); music_list = (ListView)findViewById(R.id.music_list); recently_play_music_list = (ListView)findViewById(R.id.recently_play_music_list); play_button = (ImageButton)findViewById(R.id.play_button); seek_bar = (SeekBar)findViewById(R.id.process_bar); play_mode_button = (ImageButton)findViewById(R.id.play_mode_button); play_mode_button.setImageResource(R.drawable.play_mode_photo); next_song_button = (ImageButton)findViewById(R.id.next_song_button); latestPlayListButton = (Button)findViewById(R.id.latestPlayListButton); previous_song_button = (ImageButton)findViewById(R.id.previous_song_button); intent_to_service = new Intent("com.example.communication.PLAY"); intent_to_fragment = new Intent("com.example.communication.MUSIC_LIST_SELECTOR"); intent_to_changeMusic = new Intent("com.example.communication.ChANGE_MUSIC"); progress_change_intent_to_service = new Intent("com.example.communication.PROGRESS_BAR"); myDataBase = new MyDataBase(getApplicationContext(),recently_play_music_list); //实例化数据库类 myDataBase.CreateDataBase(); play_button.setImageResource(R.drawable.play_photo); play_button.setOnClickListener(new View.OnClickListener() { //播放按钮的监听器 @Override public void onClick(View v) { if(isPause){ isPause = false; play_button.setImageResource(R.drawable.pause_photo); } else{ isPause = true; play_button.setImageResource(R.drawable.play_photo); } intent_to_service.putExtra("position",current_position); intent_to_service.putExtra("isPause",isPause); sendBroadcast(intent_to_service); } }); mp3Infos = finder.getMp3Infos(getContentResolver()); next_song_button.setOnClickListener(new View.OnClickListener() { //下一首按钮的监听器 @Override public void onClick(View v) { changeMusic(play_mode,AppConstant.PlayerMsg.NEXT_MUSIC,mp3Infos); } }); previous_song_button.setOnClickListener(new View.OnClickListener() { //上一首按钮监听 @Override public void onClick(View v) { changeMusic(play_mode,AppConstant.PlayerMsg.PREVIOUS_MUSIC,mp3Infos); } }); seek_bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { //进度条拖动响应 @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {} @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) { current_position_bar = seekBar.getProgress(); progress_change_intent_to_service.putExtra("current_position",current_position_bar); sendBroadcast(progress_change_intent_to_service); } }); play_mode_button.setOnClickListener(new View.OnClickListener() { //播放模式按钮监听器 @Override public void onClick(View v) { if(popupPlayModeWindow.isShowing()){ popupPlayModeWindow.dismiss(); } else{ if(play_mode == AppConstant.PlayerMsg.LOOP_MODE) Toast.makeText(getApplicationContext(), "当前模式为循环播放模式", Toast.LENGTH_SHORT).show(); if(play_mode == AppConstant.PlayerMsg.RANDOM_MODE) Toast.makeText(getApplicationContext(),"当前模式为随机播放模式",Toast.LENGTH_SHORT).show(); popupPlayModeWindow.showAsDropDown(v); } } }); music_array = getMusicList(); } @Override protected void onStart() { IntentFilter intentMsgFilter = new IntentFilter(); IntentFilter intentBarFilter = new IntentFilter(); IntentFilter intentNotificationFilter = new IntentFilter(); intentMsgFilter.addAction("com.example.communication.RECEIVER"); //注册歌曲信息的广播*** registerReceiver(msgReceiver,intentMsgFilter); intentBarFilter.addAction("com.example.communication.BAR"); //注册进度条的广播*** registerReceiver(barReceiver,intentBarFilter); intentNotificationFilter.addAction("com.example.communication.NOTIFICATION_TO_ACTIVITY"); registerReceiver(notificationReceiver,intentNotificationFilter); super.onStart(); } /*为了防止按返回键杀掉Activity,对返回键的按钮响应做了重写, * 连续按两次,检测间隔时间,达到时间要求直接返回桌面, * 和按home键效果类似。 */ public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN){ if((System.currentTimeMillis()-exitTime) > 2000){ Toast.makeText(this, "再按一次返回桌面", Toast.LENGTH_SHORT).show(); exitTime = System.currentTimeMillis(); } else { Intent i = new Intent(Intent.ACTION_MAIN); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.addCategory(Intent.CATEGORY_HOME); startActivity(i); } return true; } return super.onKeyDown(keyCode, event); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } /*在Activity界面按手机的更多选项按钮,弹出的选择框 * * */ @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } if(id == R.id.action_exit){ System.exit(0); } return super.onOptionsItemSelected(item); } /* * 接受从service发过来的广播 * 包含歌曲名和艺术家名 */ private class MsgReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { music_info_textView = (TextView)findViewById(R.id.music_info_textView); singer_info_textView = (TextView)findViewById(R.id.singer_info_textView); music_info_textView.setText(intent.getStringExtra("title")); singer_info_textView.setText(intent.getStringExtra("artist")); } } /* * 接受从service发过来的广播 * 更新进度条的当前位置 */ private class BarReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { if(seek_bar.getMax() - current_position <= 1100 ){ changeMusic(play_mode,AppConstant.PlayerMsg.NEXT_MUSIC,mp3Infos); } else{ current_position = intent.getIntExtra("position",0); seek_bar.setProgress(current_position); } } } /* * 接受从service发过来的广播 * 通知栏的相关信息 */ private class NotificationReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { notification_previous_music = intent.getBooleanExtra("notification_previous_music",false); notification_next_music = intent.getBooleanExtra("notification_next_music",false); notification_pause_music = intent.getBooleanExtra("notification_pause_music",false); notification_exit = intent.getBooleanExtra("notification_exit",false); if(notification_previous_music){ changeMusic(play_mode,AppConstant.PlayerMsg.PREVIOUS_MUSIC,mp3Infos); } if(notification_next_music){ changeMusic(play_mode,AppConstant.PlayerMsg.NEXT_MUSIC,mp3Infos); } if(notification_pause_music){ play_button.setImageResource(R.drawable.play_photo); } else{ play_button.setImageResource(R.drawable.pause_photo); } if(notification_exit){ System.exit(0); } } } /* * 自定义函数 * 用来切歌 * 传入的参数包含:当前播放模式(mode)、上一首还是下一首(msg),歌曲的列表(List<Mp3Info> mp3Infos) */ private void changeMusic(int mode,int msg,List<Mp3Info> mp3Infos){ isChangToNext = true; isPause = false; current_position = 0; play_button.setImageResource(R.drawable.pause_photo); switch (mode){ case AppConstant.PlayerMsg.LOOP_MODE: switch (msg){ case AppConstant.PlayerMsg.NEXT_MUSIC: if(music_position < mp3Infos.size() - 1 ) music_position ++; else music_position = 0; break; case AppConstant.PlayerMsg.PREVIOUS_MUSIC: if(music_position >= 1 ) music_position --; else music_position = mp3Infos.size() - 1; break; } break; case AppConstant.PlayerMsg.RANDOM_MODE: music_position = (int)(Math.random() * (mp3Infos.size() - 1)); break; } try{ initService(music_position); Mp3Info mp3_Info = mp3Infos.get(music_position); isFavorite = mp3_Info.getFavorite(); intent_to_fragment.putExtra("selector_position",music_position); //发广播,改变listView_selector的位置 sendBroadcast(intent_to_fragment); music_url = mp3_Info.getUrl(); music_info_textView.setText(mp3_Info.getTitle()); singer_info_textView.setText(mp3_Info.getArtist()); seek_bar.setMax((int)mp3_Info.getDuration()); intent_to_service.putExtra("isPause",isPause); intent_to_changeMusic.putExtra("music_title",mp3_Info.getTitle()); intent_to_changeMusic.putExtra("music_artist",mp3_Info.getArtist()); intent_to_changeMusic.putExtra("music_url",music_url); intent_to_changeMusic.putExtra("isChangeToNext",isChangToNext); sendBroadcast(intent_to_service); sendBroadcast(intent_to_changeMusic); isChangToNext = false; // SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss"); // Date curDate = new Date(); long time = System.currentTimeMillis(); if(myDataBase.IsExistData(MyDataBase.TABLE_NAME,(int)mp3_Info.getId())){ //最近播放存在该歌曲就更新播放时间,不存在就加入数据库 myDataBase.UpdateData(MyDataBase.TABLE_NAME,(int)mp3_Info.getId() ,time); } else{ myDataBase.AddData( MyDataBase.TABLE_NAME, (int)mp3_Info.getId(), mp3_Info.getTitle(), mp3_Info.getArtist(), mp3_Info.getDuration(), mp3_Info.getUrl(), (int)mp3_Info.getAlbum_id(), time ); } } catch(Exception e){ e.printStackTrace(); } } /* * 播放模式的popupwindow里按键监听器 * 在布局文件里面注册的监听器 */ public void loop_play_mode_listener(View v){ Toast.makeText(getApplicationContext(),"更改为循环播放模式",Toast.LENGTH_SHORT).show(); play_mode = AppConstant.PlayerMsg.LOOP_MODE; play_mode_button.setImageResource(R.drawable.play_mode_photo); popupPlayModeWindow.dismiss(); } /* * 播放模式的popupwindow里按键监听器 * 在布局文件里面注册的监听器 */ public void random_play_mode_listener(View v){ Toast.makeText(getApplicationContext(),"更改为随机播放模式",Toast.LENGTH_SHORT).show(); play_mode = AppConstant.PlayerMsg.RANDOM_MODE; play_mode_button.setImageResource(R.drawable.random_play_mode); popupPlayModeWindow.dismiss(); } /* * Activity下方控制台的点击监听 * 在布局文件里面注册的监听器 * 点击后会切换至另外一个Fragment,专门用来显示歌词的playFragment */ public void main_activity_bottom_layout_listener(View v){ String current_music_url = mp3Infos.get(music_position).getUrl(); PlayFragment playFragment = PlayFragment.newInstance(current_music_url); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.setCustomAnimations(R.animator.slide_in_up,R.animator.slide_out_down); fragmentTransaction.replace(R.id.fragment_layout, playFragment); fragmentTransaction.addToBackStack(null); fragmentTransaction.commit(); } /* * 初始化一个service * 用于在后台播放歌曲 * 独立于activity */ private void initService(int position) { music_position = position; Mp3Info mp3Info = mp3Infos.get(position); Intent intent = new Intent("com.example.communication.MSG_ACTION"); play_button.setImageResource(R.drawable.pause_photo); intent.putExtra("url", mp3Info.getUrl()); intent.putExtra("title", mp3Info.getTitle()); intent.putExtra("artist", mp3Info.getArtist()); intent.putExtra("album", mp3Info.getAlbum()); intent.putExtra("album_id", mp3Info.getAlbum_id()); intent.putExtra("MSG", AppConstant.PlayerMsg.PLAY_MSG); intent.setClass(MainActivity.this, PlayerService.class); seek_bar.setMax((int) (mp3Info.getDuration())); startService(intent); } /* * 这个是把所有的歌曲标题获取,存放到一个String类型数组里 * 用于AutoCompleTextView的自动补全 */ public static String[] getMusicList(){ String[] str = new String[mp3Infos.size() - 1]; for(int i = 0;i < mp3Infos.size() - 1;i++){ str[i] = mp3Infos.get(i).getTitle(); } return str; } /* * 这个可以忽略 * 用于实现我的最爱和最近播放列表的点击播放 * 没有弄好 */ public static int findPosition(String url){ int position = 0; while(position < mp3Infos.size()){ if(url.equals(mp3Infos.get(position).getUrl())){ return position; } else{ position++; } } return -1; } }
今天就先写这么多啦,小达会再抽时间继续奉上我的(辣鸡)教程的,23333。有什么问题可以给小达留言,QQ:2319821734。我能解决的尽量回答,不行的话团队还有各路大神,帮忙解答的~~~~~。
相关文章推荐
- Android 音乐播放器的开发教程(五)本地音乐的获取及显示 ----- 小达
- Android 音乐播放器的开发教程(十)通知栏Notification的使用 ----- 小达
- Android 音乐播放器的开发教程(六)service的运用及音乐列表点击播放 ----- 小达
- Android 音乐播放器的开发教程(八)歌曲的切换和进度条的拖动 ----- 小达
- Android 音乐播放器的开发教程(十二)SQLite的使用及我的最爱歌曲的实现 ----- 小达
- Android 音乐播放器的开发教程(三) 小卷毛播放器的主界面开发 ---- 小达
- Android 音乐播放器的开发教程(九) 歌词的显示----- 小达
- Android 音乐播放器的开发教程(十一)SQLite的使用及最近播放的实现 ----- 小达
- Android 音乐播放器的开发教程(七)运用Broadcast实现service与activity的通信 ----- 小达
- Android 音乐播放器的开发教程(四)Activity和Fragment的通信以及Fragment的切换 ----- 小达
- Android 音乐播放器的开发教程(二)反编译apk ----- 小达
- Android高手进阶教程(八)之 ----Android Widget开发案例
- Android高手进阶教程(八)之----Android Widget开发案例(世界杯倒计时!)
- Android 3D游戏开发教程
- Android系列之Android开发教程代码实例
- android开发教程--学习记录1
- Android高手进阶教程(八)之----Android Widget开发案例(世界杯倒计时!)
- Google 手机操作系统 Android 开发教程 转载
- Windows7部署Android开发环境傻瓜式教程(Eclipse+ADT)
- Android高手进阶教程(十九)之---Android开发中,使用线程应该注意的问题!