0


手把手教你完成Android期末大作业(多功能应用型APP)

前言

Android期末作业,估摸着也花了整整5天。里面可能会缺少某些细节,如果跟着做有不会的评论就行,每天都会看,尽力解答。

功能

  • 待办
  • 专注计时
  • 音乐
  • 天气

实现步骤

一、底部菜单栏切换页

1.添加依赖

  1. dependencies {
  2. implementation 'com.google.android.material:material:1.2.1'
  3. }

2.在res资源文件夹下新建一个menu文件夹,创建底部导航的菜单布局文件

  • 创建对应数量的item,为每个菜单栏选项
  • 给每个item定义title(标题),icon(图标)
  1. <?xml version="1.0" encoding="utf-8"?><menuxmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:id="@+id/menu_task"android:icon="@drawable/menu_task"android:title="事项"/><itemandroid:id="@+id/menu_accounts"android:icon="@drawable/menu_task"android:title="专注"/><itemandroid:id="@+id/menu_absorbed"android:icon="@drawable/menu_task"android:title="音乐"/><itemandroid:id="@+id/menu_weather"android:icon="@drawable/menu_task"android:title="每日先知"/></menu>

3.在activity_main布局页面引入 com.google.android.material.bottomnavigation.BottomNavigationView 控件

控件属性:

  • app:labelVisibilityMode="labeled"取消定义三个以上按钮文字不显示的效果
  • app:itemBackground="@null" 取消水波纹的效果
  • app:itemIconTint设置图标的颜色
  • app:itemTextColor设置字体的颜色
  • app:menu="@menu/bottom_navi_menu"将menu引入
  1. <?xml version="1.0" encoding="utf-8"?><RelativeLayoutxmlns: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"xmlns:app="http://schemas.android.com/apk/res-auto"tools:context=".MainActivity"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="60dp"/><com.google.android.material.bottomnavigation.BottomNavigationViewandroid:layout_width="match_parent"android:layout_height="60dp"android:layout_alignParentBottom="true"app:labelVisibilityMode="labeled"app:itemBackground="@null"app:menu="@menu/bottom_navi_menu"/></RelativeLayout>

4.依次创建每个页面的Fragment类及布局文件,如Task页面

  1. <!-- task_fragment.xml --><?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:text="Task PAGE"android:textSize="40dp"android:gravity="center"/></LinearLayout>
  1. // TaskFragment.javapublicclassTaskFragmentextendsFragment{//重写onCreateView, fragment绑定布局文件@Nullable@OverridepublicViewonCreateView(@NonNullLayoutInflater inflater,@NullableViewGroup container,@NullableBundle savedInstanceState){View view = inflater.inflate(R.layout.task_fragment, container,false);return view;}}

5.在MainActivity.java中进行设置BottomNavigation选择监听事件对fragment进行管理。

  1. publicList<Fragment> fragmentList =newArrayList<>();privateFragmentManager fragmentManager;// 底部导航栏模块publicvoidInitBottomNavigation(){// 添加五个fragment实例到fragmentList,以便管理
  2. fragmentList.add(newTaskFragment());
  3. fragmentList.add(newAbsorbedFragment());
  4. fragmentList.add(newMusicFragment());
  5. fragmentList.add(newWeatherFragment());//建立fragment管理器
  6. fragmentManager =getSupportFragmentManager();//管理器开启事务,将fragment实例加入管理器
  7. fragmentManager.beginTransaction().add(R.id.FragmentLayout, fragmentList.get(0),"TASK").add(R.id.FragmentLayout, fragmentList.get(1),"ABSOTBED").add(R.id.FragmentLayout, fragmentList.get(2),"MUSIC").add(R.id.FragmentLayout, fragmentList.get(3),"WEATHER").commit();//设置fragment显示初始状态
  8. fragmentManager.beginTransaction().show(fragmentList.get(1)).hide(fragmentList.get(0)).hide(fragmentList.get(2)).hide(fragmentList.get(3)).commit();//设置底部导航栏点击选择监听事件BottomNavigationView bottomNavigationView =findViewById(R.id.BottomNavigation);
  9. bottomNavigationView.setOnNavigationItemSelectedListener(newBottomNavigationView.OnNavigationItemSelectedListener(){@SuppressLint("NonConstantResourceId")@OverridepublicbooleanonNavigationItemSelected(@NonNullMenuItem item){// return true : show selected style// return false: do not showswitch(item.getItemId()){caseR.id.menu_task:ShowFragment(0);returntrue;caseR.id.menu_accounts:ShowFragment(1);returntrue;caseR.id.menu_absorbed:ShowFragment(2);returntrue;caseR.id.menu_weather:ShowFragment(3);returntrue;default:Log.i(TAG,"onNavigationItemSelected: Error");break;}returnfalse;}});}publicvoidShowFragment(int index){
  10. fragmentManager.beginTransaction().show(fragmentList.get(index)).hide(fragmentList.get((index +1)%4)).hide(fragmentList.get((index +2)%4)).hide(fragmentList.get((index +3)%4)).commit();}

二、天气显示界面

1、添加依赖(用于获取和解析天气数据)

  1. implementation 'com.google.code.gson:gson:2.8.6'
  2. implementation 'com.squareup.okhttp3:okhttp:4.9.0'

2、获取天气API接口,这里以临海市为例。使用OkHttp请求天气数据,使用Log打印测试是否能成功获取

  1. publicvoidRefreshWeatherData(){OkHttpClient client =newOkHttpClient();Request request =newRequest.Builder().url(weatherUrl).build();
  2. client.newCall(request).enqueue(newCallback(){@OverridepublicvoidonFailure(@NonNullCall call,@NonNullIOException e){
  3. e.printStackTrace();}@OverridepublicvoidonResponse(@NonNullCall call,@NonNullResponse response)throwsIOException{String weatherJson = response.body().string();Weather weather =newGson().fromJson(weatherJson,Weather.class);Log.i(TAG,"onResponse: "+weatherJson);}});}

3、Json数据获取成功后,根据Json数据的结构建立Weather类用于解析Json数据。

  1. // class WeatherpublicclassWeather{privateString city;//城市名privateString update_time;//更新时间privateList<DayData> data;//每天的天气数据列表,data.get(0)为当天数据/*
  2. getter and setter
  3. */}// class DayDatapublicclassDayData{privateString wea;//天气状况privateString tem;//当前温度privateString tem1;//最高温privateString tem2;//最低温privateString humidity;//湿度privateString air_level;//空气质量等级privateString air_tips;//空气质量小提示/*
  4. getter and setter
  5. */}

4、由于OkHttp的请求是在子线程中进行的,需要使用Handler消息队列机制将解析出来的Weather实例发送到主线程用以显示在界面上。

  1. //消息处理类publicclassMyHandlerextendsHandler{@OverridepublicvoidhandleMessage(@NonNullMessage msg){super.handleMessage(msg);//what == 1 天气消息if(msg.what ==1)ShowWeatherInfo((Weather) msg.obj);}}publicvoidShowWeatherInfo(Weather weather){String city = weather.getCity();String wea = weather.getData().get(0).getWea();String maxTem = weather.getData().get(0).getTem1();String minTem = weather.getData().get(0).getTem2();String tem = weather.getData().get(0).getTem();String humidity ="湿度 "+ weather.getData().get(0).getHumidity();String air_level ="空气指数 "+ weather.getData().get(0).getAir_level();// tem tem1 tem2 city wea rain pm image((TextView)findViewById(R.id.cityView)).setText(city);((TextView)findViewById(R.id.weaView)).setText(wea);((TextView)findViewById(R.id.mmtemView)).setText(String.format("%s° / %s°", minTem.substring(0, minTem.length()-1), maxTem.substring(0, maxTem.length()-1)));((TextView)findViewById(R.id.temView)).setText(tem.substring(0, tem.length()-1)+"°");((TextView)findViewById(R.id.humidityView)).setText(humidity);((TextView)findViewById(R.id.levelView)).setText(air_level);ShowWeatherImage(wea);//根据天气状况wea显示对应的天气图片,这里不详细说明,使用switch就行}

5、别忘了在OkHttp请求完成时发送消息

  1. publicvoidRefreshWeatherData(){OkHttpClient client =newOkHttpClient();Request request =newRequest.Builder().url(weatherUrl).build();
  2. client.newCall(request).enqueue(newCallback(){@OverridepublicvoidonFailure(@NonNullCall call,@NonNullIOException e){
  3. e.printStackTrace();}@OverridepublicvoidonResponse(@NonNullCall call,@NonNullResponse response)throwsIOException{String weatherJson = response.body().string();Weather weather =newGson().fromJson(weatherJson,Weather.class);Message message =newMessage();
  4. message.what =1;
  5. message.obj = weather;
  6. myHandler.sendMessage(message);}});}

6、优化xml布局

三、待办事项界面

这里由于ListView是放在Fragment中的,所以直接在MainAcitivity.java中设置适配器可能会出现数据没法显示的bug。所以我直接把从数据库获取数据,Adapter的定义,ListView设置适配器的模块搬到了TaskFragment.java中。

1.在task.xml中添加ListView,先不用设置UI样式,先把数据拿到并显示在界面上

  1. <?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/taskText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="事项"/><ListViewandroid:id="@+id/taskListView"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>

2.创建task_item.xml布局文件(这里注意线性布局的方向及宽高,以保证task_item能放在ListView中)

  1. <?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/task_content"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/black"android:textSize="30dp"android:text="TextView"/></LinearLayout>

3.新建TaskItem类,存放事项数据

  1. packagecom.example.daily.tasks;publicclassTaskItem{privateint id;privateString content;privateString type;privateint status;publicTaskItem(int id,String type,String content,int status){this.id = id;this.type = type;this.content = content;this.status = status;}// 自行添加Get和Set方法}

4.在TaskFragment.java中创建SQLite数据库并获取待办事项的数据

  1. publicclassTaskFragmentextendsFragment{privatestaticfinalString TAG =TaskFragment.class.getName();privateList<TaskItem> taskList =newArrayList<>();@Nullable@OverridepublicViewonCreateView(@NonNullLayoutInflater inflater,@NullableViewGroup container,@NullableBundle savedInstanceState){View view = inflater.inflate(R.layout.task, container,false);ReadTaskDataFromSQL();//测试数据获取是否正常for(TaskItem item : taskList){Log.i(TAG,"taskList "+item.getId()+" "+item.getContent());}return view;}//读取数据库并将数据存到taskListpublicvoidReadTaskDataFromSQL(){MySQLiteOpenHelper openHelper =newMySQLiteOpenHelper(getActivity());SQLiteDatabase readDatabase = openHelper.getReadableDatabase();Cursor cursor = readDatabase.query("task",newString[]{"id","type","content","status"},null,null,null,null,null);while(cursor.moveToNext()){TaskItem task =newTaskItem(
  2. cursor.getInt(0),
  3. cursor.getString(1),
  4. cursor.getString(2),
  5. cursor.getInt(3));
  6. taskList.add(task);}}//创建SQLite数据库publicclassMySQLiteOpenHelperextendsSQLiteOpenHelper{publicMySQLiteOpenHelper(@NullableContext context){super(context,"Daily.db",null,1);}@OverridepublicvoidonCreate(SQLiteDatabase db){Log.i(TAG,"onCreate: sqlite");//创建待办事项数据表String create_sql ="create table task("+"id INTEGER PRIMARY KEY AUTOINCREMENT, "+"content varchar(50), "+"type varchar(50), "+"status int);";
  7. db.execSQL(create_sql);}@OverridepublicvoidonUpgrade(SQLiteDatabase db,int oldVersion,int newVersion){}}}

5.数据获取正常以后,建立ListView适配器。这里涉及到缓存convertView的使用,使用convertView可以防止每创建一个item时就解析一个布局,这样效率肯定不高。convertView是Android提供的用于缓存的View,在第一次渲染item时,将将解析出来的View放入缓存convertView,在下一次渲染item的时候,判断convertView是否为空即可。

  1. publicclassTaskAdapterextendsBaseAdapter{@OverridepublicintgetCount(){//测试getCount返回值是否正常Log.i(TAG,"getCount: "+taskList.size());return taskList.size();}@OverridepublicObjectgetItem(int position){return taskList.get(position);}@OverridepubliclonggetItemId(int position){return taskList.get(position).getId();}@OverridepublicViewgetView(int position,View convertView,ViewGroup parent){//测试getView是否执行Log.i(TAG,"getView: "+position);ViewHolder viewHolder;TaskItem task =(TaskItem)getItem(position);if(convertView ==null){
  2. viewHolder =newViewHolder();
  3. convertView =LayoutInflater.from(getActivity()).inflate(R.layout.task_item,null);
  4. viewHolder.taskItemTextView = convertView.findViewById(R.id.task_content);
  5. convertView.setTag(viewHolder);}else{
  6. viewHolder =(ViewHolder) convertView.getTag();}
  7. viewHolder.taskItemTextView.setText(task.getId()+" "+task.getContent());return convertView;}}publicclassViewHolder{TextView taskItemTextView;}

6.在onCreateView中设置ListView的适配器

  1. privateList<TaskItem> taskList =newArrayList<>();privateTaskAdapter taskAdapter;privateListView taskListView;@Nullable@OverridepublicViewonCreateView(@NonNullLayoutInflater inflater,@NullableViewGroup container,@NullableBundle savedInstanceState){View view = inflater.inflate(R.layout.task, container,false);
  2. taskListView = view.findViewById(R.id.taskListView);
  3. taskAdapter =newTaskAdapter();
  4. taskListView.setAdapter(taskAdapter);ReadTaskDataFromSQL();return view;}

7.设计每一条待办事项的布局样式,如图所示,布局设计就不放原码了,使用多个线性布局的嵌套,gravity,margin属性即可实现。

img:task-2.jpg

8.根据待办事项的状态显示不同按钮,并标记待办事项的重要程度。

  1. publicvoidShowTaskContent(View convertView,TaskItem task){//显示事项内容TextView content =((ViewHolder) convertView.getTag()).taskContent;int status = task.getStatus();
  2. content.setText(task.getContent());//事项已完成 中划线 灰色if(status ==1){
  3. content.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG);
  4. content.setTextColor(getResources().getColor(R.color.GRAY,null));}//事项未完成 无中划线 黑色if(status ==0){
  5. content.getPaint().setFlags(0);
  6. content.setTextColor(getResources().getColor(R.color.black,null));}//事项失败 无中划线 灰色if(status ==-1){
  7. content.getPaint().setFlags(0);
  8. content.setTextColor(getResources().getColor(R.color.GRAY,null));}}publicvoidShowTaskLevel(View convertView,int level){// 显示事项重要级别 level : 0~3 四个优先级 Ⅰ Ⅱ Ⅲ ⅣTextView levelText =((ViewHolder) convertView.getTag()).taskLevel;if(level ==0){
  9. levelText.setText("Ⅰ");
  10. levelText.setTextColor(getResources().getColor(R.color.level_0,null));}if(level ==1){
  11. levelText.setText("Ⅱ");
  12. levelText.setTextColor(getResources().getColor(R.color.level_1,null));}if(level ==2){
  13. levelText.setText("Ⅲ");
  14. levelText.setTextColor(getResources().getColor(R.color.level_2,null));}if(level ==3){
  15. levelText.setText("Ⅳ");
  16. levelText.setTextColor(getResources().getColor(R.color.level_3,null));}}

9.在顶部添加五个TextView作为分类查看事项菜单,点击某一分类即可查看该分类下的所有事项,并修改被点击TextView 的样式。

  1. /** 菜单栏模块 **/publicvoidSetTypeMenuOnClick(View view){
  2. typeMenuList.add((TextView) view.findViewById(R.id.TypeMenu_default));
  3. typeMenuList.add((TextView) view.findViewById(R.id.TypeMenu_work));
  4. typeMenuList.add((TextView) view.findViewById(R.id.TypeMenu_study));
  5. typeMenuList.add((TextView) view.findViewById(R.id.TypeMenu_life));int[] color ={getResources().getColor(R.color.defaultColor,null),getResources().getColor(R.color.workColor,null),getResources().getColor(R.color.studyColor,null),getResources().getColor(R.color.lifeColor,null),};for(int i=0; i<4;i++){int finalI = i;//分类索引值
  6. typeMenuList.get(i).setOnClickListener(v ->{// 点击分类的一项后设置样式
  7. typeMenuList.get(finalI).setTextColor(Color.BLACK);
  8. typeMenuList.get(finalI).setBackgroundColor(Color.WHITE);
  9. typeMenuList.get((finalI+1)%4).setBackgroundColor(color[(finalI+1)%4]);
  10. typeMenuList.get((finalI+1)%4).setTextColor(Color.WHITE);
  11. typeMenuList.get((finalI+2)%4).setBackgroundColor(color[(finalI+2)%4]);
  12. typeMenuList.get((finalI+2)%4).setTextColor(Color.WHITE);
  13. typeMenuList.get((finalI+3)%4).setBackgroundColor(color[(finalI+3)%4]);
  14. typeMenuList.get((finalI+3)%4).setTextColor(Color.WHITE);// 显示某一类待办数据,这里筛选taskList即可List<TaskItem> typeTaskList =newArrayList<>();String[] types ={"全部","工作","学习","生活"};/* 分类索引值
  15. 0 全部
  16. 1 工作
  17. 2 学习
  18. 3 生活
  19. */// 点击工作 学习 生活时分类// TypeNow 是一个全局变量,表示当前的分类TypeNow= types[finalI];Log.i(TAG,"SetTypeMenuOnClick: "+TypeNow);ReadTaskFromDatabase();});}}

10.task.xml布局右上角加入一个switch控件用以隐藏已完成事项。

  1. //隐藏已完成SwitchSwitch hideCompletedTaskSwitch = view.findViewById(R.id.HideCompletedTaskView);
  2. hideCompletedTaskSwitch.setOnCheckedChangeListener(newCompoundButton.OnCheckedChangeListener(){@OverridepublicvoidonCheckedChanged(CompoundButton buttonView,boolean isChecked){if(isChecked) isHideCompleted =true;else isHideCompleted =false;// isHideCompleted 是一个全局变量,表示当前是否隐藏已完成事项ReadTaskFromDatabase();}});

完成9,10步之后就需要修改读取数据库的模块,加入TypeNow和isHideCompleted变量加以控制。

  1. publicvoidReadTaskFromDatabase(){if(taskList.size()!=0){
  2. taskList.clear();}Cursor cursor = readDatabase.query("task",newString[]{"id","type","level","content","info","status"},null,null,null,null,null);//隐藏,有分类if(isHideCompleted &&!TypeNow.equals("全部")){//只获取未完成事项while(cursor.moveToNext()){if((cursor.getInt(5)==0)&&(cursor.getString(1).equals(TypeNow))){TaskItem task =newTaskItem(
  3. cursor.getInt(0),
  4. cursor.getString(1),
  5. cursor.getInt(2),
  6. cursor.getString(3),
  7. cursor.getString(4),
  8. cursor.getInt(5));
  9. taskList.add(task);}}}//不隐藏,有分类if(!isHideCompleted &&!TypeNow.equals("全部")){while(cursor.moveToNext()){if(cursor.getString(1).equals(TypeNow)){TaskItem task =newTaskItem(
  10. cursor.getInt(0),
  11. cursor.getString(1),
  12. cursor.getInt(2),
  13. cursor.getString(3),
  14. cursor.getString(4),
  15. cursor.getInt(5));
  16. taskList.add(task);}}}//隐藏,不分类if(isHideCompleted &&TypeNow.equals("全部")){while(cursor.moveToNext()){if(cursor.getInt(5)==0){TaskItem task =newTaskItem(
  17. cursor.getInt(0),
  18. cursor.getString(1),
  19. cursor.getInt(2),
  20. cursor.getString(3),
  21. cursor.getString(4),
  22. cursor.getInt(5));
  23. taskList.add(task);}}}else{while(cursor.moveToNext()){TaskItem task =newTaskItem(
  24. cursor.getInt(0),
  25. cursor.getString(1),
  26. cursor.getInt(2),
  27. cursor.getString(3),
  28. cursor.getString(4),
  29. cursor.getInt(5));
  30. taskList.add(task);}}// 别忘了通知ListView适配器数据变化
  31. taskAdapter.notifyDataSetChanged();}

11、添加事项,这里使用的是在整个RelativeLayout布局中添加一个ImageView作为添加事项的按钮,并定义点击事件,点击时弹出对话框,在对话框中输入添加事项的信息。

自定义对话框需要先设计一个layout布局文件add_task_dialog.xml

  1. <?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:orientation="vertical"android:paddingLeft="15dp"android:paddingRight="15dp"android:paddingBottom="20dp"android:paddingTop="10dp"android:layout_height="wrap_content"><TextViewandroid:text="添加事项"android:textColor="@color/black"android:textSize="25dp"android:layout_width="match_parent"android:gravity="center"android:layout_height="50dp"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="15dp"android:paddingRight="15dp"android:orientation="horizontal"><TextViewandroid:id="@+id/textView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:textColor="@color/black"android:layout_marginRight="15dp"android:text="事项"/><EditTextandroid:id="@+id/addTaskContentEdit"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:ems="10"android:inputType="textPersonName"android:text=""/></LinearLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_marginTop="10dp"android:paddingLeft="15dp"android:paddingRight="15dp"android:layout_height="wrap_content"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:id="@+id/textView2"android:layout_width="160dp"android:layout_height="wrap_content"android:textSize="20dp"android:textColor="@color/black"android:text="事项分类"/><RadioGroupandroid:id="@+id/typeRadioGroup"android:layout_width="wrap_content"android:layout_height="wrap_content"><RadioButtonandroid:id="@+id/radioButton8"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/workColor"android:text="工作"/><RadioButtonandroid:id="@+id/radioButton7"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/studyColor"android:text="学习"/><RadioButtonandroid:id="@+id/radioButton6"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/lifeColor"android:text="生活"/><RadioButtonandroid:id="@+id/radioButton5"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/defaultColor"android:text="不分类"/></RadioGroup></LinearLayout><LinearLayoutandroid:layout_width="160dp"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:orientation="vertical"><TextViewandroid:id="@+id/textView3"android:layout_width="match_parent"android:layout_height="wrap_content"android:textSize="20dp"android:textColor="@color/black"android:text="重要级别"/><RadioGroupandroid:id="@+id/levelRadioGroup"android:layout_width="wrap_content"android:layout_height="wrap_content"><RadioButtonandroid:id="@+id/radioButton"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/level_0"android:text="0 重要且紧急"/><RadioButtonandroid:id="@+id/radioButton2"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/level_1"android:text="1 重要但不紧急"/><RadioButtonandroid:id="@+id/radioButton3"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/level_2"android:text="2 不重要但紧急"/><RadioButtonandroid:id="@+id/radioButton4"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/level_3"android:text="3 不重要且不紧急"/></RadioGroup></LinearLayout></RelativeLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:paddingLeft="15dp"android:paddingRight="15dp"android:orientation="horizontal"><TextViewandroid:id="@+id/textView4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:textColor="@color/black"android:layout_marginRight="15dp"android:text="备注"/><EditTextandroid:id="@+id/addTaskInfoEdit"android:layout_width="match_parent"android:layout_height="wrap_content"android:ems="10"android:inputType="textPersonName"android:text=""/></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:layout_marginTop="10dp"android:orientation="horizontal"><Buttonandroid:id="@+id/cancelAddButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginRight="100dp"android:text="取消"/><Buttonandroid:id="@+id/confirmAddButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="确定"/></LinearLayout></LinearLayout>

12、定义一个方法,实现弹出添加事项界面的对话框,并设置确认和取消按钮的点击事件,确认按钮即添加该事项到数据库并显示

  1. publicvoidShowAddTaskDialog(){//获取添加事项布局实例View addView =getLayoutInflater().inflate(R.layout.add_task_dialog,null);// 将该布局添加到对话框finalAlertDialog addDialog =newAlertDialog.Builder(getActivity()).setView(addView).create();
  2. addDialog.show();//获取对话框中的布局控件Button cancelButton =(Button) addView.findViewById(R.id.cancelAddButton);Button confirmButton =(Button) addView.findViewById(R.id.confirmAddButton);EditText contentEdit =(EditText) addView.findViewById(R.id.addTaskContentEdit);EditText infoEdit =(EditText) addView.findViewById(R.id.addTaskInfoEdit);RadioGroup typeGroup =(RadioGroup) addView.findViewById(R.id.typeRadioGroup);RadioGroup levelGroup =(RadioGroup) addView.findViewById(R.id.levelRadioGroup);
  3. typeGroup.setOnCheckedChangeListener(newRadioGroup.OnCheckedChangeListener(){@OverridepublicvoidonCheckedChanged(RadioGroup group,int checkedId){}});
  4. levelGroup.setOnCheckedChangeListener(newRadioGroup.OnCheckedChangeListener(){@OverridepublicvoidonCheckedChanged(RadioGroup group,int checkedId){}});//确定按钮
  5. confirmButton.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(View v){// 获取输入的事项内容和备注String addContent = contentEdit.getText().toString();String addInfo = infoEdit.getText().toString();//RadioGroup的选择项RadioButton typeSelectBtn =(RadioButton) addView.findViewById(typeGroup.getCheckedRadioButtonId());String addType = typeSelectBtn.getText().toString();RadioButton levelSelectBtn =(RadioButton) addView.findViewById(levelGroup.getCheckedRadioButtonId());int addLevel =Integer.parseInt(levelSelectBtn.getText().toString().substring(0,1));//插入数据库InsertTaskToDatabase(newTaskItem(addType, addLevel, addContent, addInfo,0));
  6. addDialog.dismiss();}});// 取消按钮
  7. cancelButton.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(View v){
  8. addDialog.dismiss();}});}

13、然后在添加事项的点击事件中调用ShowAddTaskDialog()即可

  1. //添加事项的按钮ImageView addTaskImage =(ImageView) view.findViewById(R.id.addTaskImage);
  2. addTaskImage.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(View v){ShowAddTaskDialog();}});

14.长按某条事项弹出对话框,显示事项信息,可以修改,删除,标记失败。和添加事项的对话框实现原理相同,这里不详细说明,给出代码供参考

  1. <!-- task_info_dialog.xml --><?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:orientation="vertical"android:paddingLeft="15dp"android:paddingRight="15dp"android:paddingBottom="20dp"android:paddingTop="10dp"android:layout_height="wrap_content"><TextViewandroid:text="事项信息"android:textColor="@color/black"android:textSize="25dp"android:layout_width="match_parent"android:gravity="center"android:layout_height="50dp"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="15dp"android:paddingRight="15dp"android:orientation="horizontal"><TextViewandroid:id="@+id/textView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:textColor="@color/black"android:layout_marginRight="15dp"android:text="事项"/><EditTextandroid:id="@+id/addTaskContentEdit"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:ems="10"android:inputType="textPersonName"android:text=""/></LinearLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_marginTop="10dp"android:paddingLeft="15dp"android:paddingRight="15dp"android:layout_height="wrap_content"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:id="@+id/textView2"android:layout_width="160dp"android:layout_height="wrap_content"android:textSize="20dp"android:textColor="@color/black"android:text="事项分类"/><RadioGroupandroid:id="@+id/typeRadioGroup"android:layout_width="wrap_content"android:layout_height="wrap_content"><RadioButtonandroid:id="@+id/workButton"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/workColor"android:text="工作"/><RadioButtonandroid:id="@+id/studyButton"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/studyColor"android:text="学习"/><RadioButtonandroid:id="@+id/lifeButton"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/lifeColor"android:text="生活"/><RadioButtonandroid:id="@+id/defaultButton"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/defaultColor"android:text="全部"/></RadioGroup></LinearLayout><LinearLayoutandroid:layout_width="160dp"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:orientation="vertical"><TextViewandroid:id="@+id/textView3"android:layout_width="match_parent"android:layout_height="wrap_content"android:textSize="20dp"android:textColor="@color/black"android:text="重要级别"/><RadioGroupandroid:id="@+id/levelRadioGroup"android:layout_width="wrap_content"android:layout_height="wrap_content"><RadioButtonandroid:id="@+id/level0Button"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/level_0"android:text="0 重要且紧急"/><RadioButtonandroid:id="@+id/level1Button"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/level_1"android:text="1 重要但不紧急"/><RadioButtonandroid:id="@+id/level2Button"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/level_2"android:text="2 不重要但紧急"/><RadioButtonandroid:id="@+id/level3Button"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/level_3"android:text="3 不重要且不紧急"/></RadioGroup></LinearLayout></RelativeLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:paddingLeft="15dp"android:paddingRight="15dp"android:orientation="horizontal"><TextViewandroid:id="@+id/textView4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:textColor="@color/black"android:layout_marginRight="15dp"android:text="备注"/><EditTextandroid:id="@+id/addTaskInfoEdit"android:layout_width="match_parent"android:layout_height="wrap_content"android:ems="10"android:inputType="textPersonName"android:text=""/></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:layout_marginTop="20dp"android:orientation="horizontal"><LinearLayoutandroid:layout_width="wrap_content"android:layout_marginRight="60dp"android:orientation="vertical"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/deleteTaskButton"android:layout_width="wrap_content"android:layout_height="40dp"android:adjustViewBounds="true"android:src="@drawable/delete_icon"/><TextViewandroid:layout_width="40dp"android:layout_height="wrap_content"android:textColor="@color/black"android:gravity="center"android:textSize="15dp"android:layout_marginTop="5dp"android:text="删除"/></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:orientation="vertical"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/failTaskButton"android:layout_width="40dp"android:layout_height="40dp"android:adjustViewBounds="true"android:src="@drawable/fail_icon"/><TextViewandroid:layout_width="40dp"android:layout_height="wrap_content"android:textColor="#d81e06"android:gravity="center"android:textSize="15dp"android:layout_marginTop="5dp"android:text="失败"/></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_marginLeft="60dp"android:orientation="vertical"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/modifyTaskButton"android:layout_width="40dp"android:layout_height="40dp"android:adjustViewBounds="true"android:src="@drawable/modify_icon"android:text="修改"/><TextViewandroid:layout_width="40dp"android:layout_height="wrap_content"android:textColor="@color/purple_500"android:gravity="center"android:textSize="15dp"android:layout_marginTop="5dp"android:text="修改"/></LinearLayout></LinearLayout></LinearLayout>
  1. publicvoidShowTaskInfoDialog(TaskItem task){// 获取传入的事项数据String content = task.getContent();String type = task.getType();int level = task.getLevel();String info = task.getInfo();//获取布局View infoView =getLayoutInflater().inflate(R.layout.task_info_dialog,null);finalAlertDialog infoDialog =newAlertDialog.Builder(getActivity()).setView(infoView).create();
  2. infoDialog.show();//获取对话框中的布局控件EditText contentEdit =(EditText) infoView.findViewById(R.id.addTaskContentEdit);EditText infoEdit =(EditText) infoView.findViewById(R.id.addTaskInfoEdit);RadioGroup typeGroup =(RadioGroup) infoView.findViewById(R.id.typeRadioGroup);RadioGroup levelGroup =(RadioGroup) infoView.findViewById(R.id.levelRadioGroup);ImageView deleteImage =(ImageView) infoView.findViewById(R.id.deleteTaskButton);ImageView modifyImage =(ImageView) infoView.findViewById(R.id.modifyTaskButton);ImageView failImage =(ImageView) infoView.findViewById(R.id.failTaskButton);//显示task事项信息
  3. contentEdit.setText(content);
  4. infoEdit.setText(info);SetTypeRadioGroupSelected(typeGroup, type);SetLevelRadioGroupSelected(levelGroup, level);//删除按钮
  5. deleteImage.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(View v){DeleteTaskToDatabase(task);
  6. infoDialog.dismiss();}});//失败按钮
  7. failImage.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(View v){
  8. task.setStatus(-1);UpDateTaskToDatabase(task);//别忘记关闭对话框
  9. infoDialog.dismiss();}});//修改按钮
  10. modifyImage.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(View v){// 获取输入的事项内容和备注String modifyContent = contentEdit.getText().toString();String modifyInfo = infoEdit.getText().toString();//RadioGroup的选择项RadioButton typeSelectBtn =(RadioButton) infoView.findViewById(typeGroup.getCheckedRadioButtonId());String modifyType = typeSelectBtn.getText().toString();RadioButton levelSelectBtn =(RadioButton) infoView.findViewById(levelGroup.getCheckedRadioButtonId());int modifyLevel =Integer.parseInt(levelSelectBtn.getText().toString().substring(0,1));
  11. task.setContent(modifyContent);
  12. task.setInfo(modifyInfo);
  13. task.setType(modifyType);
  14. task.setLevel(modifyLevel);UpDateTaskToDatabase(task);//别忘记关闭对话框
  15. infoDialog.dismiss();}});}
  1. //在适配器的getView中,设置每条事项的长按事件:调用ShowTaskInfoDialog弹出对话框显示事项的内容
  2. convertView.setOnLongClickListener(newView.OnLongClickListener(){@OverridepublicbooleanonLongClick(View v){ShowTaskInfoDialog(task);returnfalse;}});

四、专注计时界面

计时的原理是使用Android四大组件之一的Service开启计时线程,并每隔一秒钟发送一次本地广播通知主界面更新布局。

1、创建服务类TimeService,继承自Service。这里在Service类里面定义了一个TimeThread自定义线程类,用以方便线程的挂起和恢复。

  1. publicclassTimeServiceextendsService{privatestaticfinalString TAG =TimeService.class.getName();//计时秒数privateint second =0;publicintgetSecond(){return second;}publicvoidsetSecond(int second){this.second = second;}@Nullable@OverridepublicIBinderonBind(Intent intent){returnnewLocalBinder();}@OverridepublicvoidonCreate(){Log.i(TAG,"TimeService onCreate: ");super.onCreate();}@OverridepublicintonStartCommand(Intent intent,int flags,int startId){Log.i(TAG,"TimeService onStartCommand: ");//创建计时线程实例
  2. timeThread =newTimeThread();
  3. timeThread.start();
  4. isRunning =true;returnsuper.onStartCommand(intent, flags, startId);}@OverridepublicvoidonDestroy(){Log.i(TAG,"TimeService onDestroy: ");super.onDestroy();}@OverridepublicbooleanonUnbind(Intent intent){Log.i(TAG,"TimeService onUnbind: ");returnsuper.onUnbind(intent);}//用于返回本地服务publicclassLocalBinderextendsBinder{publicTimeServicegetService(){returnTimeService.this;}}publicclassTimeThreadextendsThread{privatefinalObject lock =newObject();privateboolean pause =false;/**
  5. * 调用该方法实现线程的暂停
  6. */voidpauseThread(){Log.i(TAG,"pauseTimeThread: ");
  7. pause =true;}/*
  8. 调用该方法实现恢复线程的运行
  9. */voidresumeThread(){Log.i(TAG,"resumeTimeThread: ");
  10. pause =false;synchronized(lock){
  11. lock.notify();}}/**
  12. * 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应
  13. */voidonPause(){synchronized(lock){try{
  14. lock.wait();}catch(InterruptedException e){
  15. e.printStackTrace();}}}@Overridepublicvoidrun(){super.run();try{while(true){//当pause为true时,调用onPause挂起该线程TimeUnit.SECONDS.sleep(1);while(pause){onPause();}
  16. second++;SendSecondBroadcast();Log.i(TAG,"run: "+second);}}catch(InterruptedException e){
  17. e.printStackTrace();}}}}

2、在AndroidManifast注册TimeService类

3、在AbsorbedFragment中绑定服务,运行测试service是否连接成功

  1. publicvoidBindTimeService(){Intent intent =newIntent(getActivity(),TimeService.class);ServiceConnection connection =newServiceConnection(){@OverridepublicvoidonServiceConnected(ComponentName name,IBinder service){
  2. localBinder =(TimeService.LocalBinder) service;if(localBinder.getService()!=null){Log.i(TAG,"onServiceConnected: time service connected");}}@OverridepublicvoidonServiceDisconnected(ComponentName name){Log.i(TAG,"onServiceDisconnected: ");}};getActivity().bindService(intent, connection,Context.BIND_AUTO_CREATE);}

4、给开始计时按钮添加点击事件,运行测试TimeThread是否每隔一秒打印一次

  1. Intent intent =newIntent();
  2. intent.setClass(getActivity(),TimeService.class);getActivity().startService(intent);

5、运行成功后,添加暂停,继续,取消按钮,运行测试观察打印信息是否正常

  • 暂停点击事件:localBinder.getService().PauseTime();
  • 继续点击事件:localBinder.getService().ResumeTime();
  • 取消点击事件:localBinder.getService().CancelTime();
  1. //TimeService中用于在MainActivity调用的方法publicvoidPauseTime(){
  2. timeThread.pauseThread();
  3. isRunning =false;}publicvoidResumeTime(){
  4. timeThread.resumeThread();
  5. isRunning =true;}publicvoidCancelTime(){
  6. timeThread.pauseThread();
  7. second =0;}

6、创建本地广播,用以接收TimeThread发送的秒数,并更新布局界面

  1. //注册接收计时秒数的本地广播IntentFilter timeIntentFilter =newIntentFilter();
  2. timeIntentFilter.addAction("SECONDS_CHANGED");BroadcastReceiver timeBroadcastReceiver =newBroadcastReceiver(){@OverridepublicvoidonReceive(Context context,Intent intent){int second = localBinder.getService().getSecond();ShowTimeSecond(second);}};LocalBroadcastManager.getInstance(getActivity()).registerReceiver(timeBroadcastReceiver, timeIntentFilter);

7、在TimeThread的run方法中每一秒发送一次本地广播,运行测试是否正常

  1. @Overridepublicvoidrun(){super.run();try{while(true){//当pause为true时,调用onPause挂起该线程TimeUnit.SECONDS.sleep(1);while(pause){onPause();}
  2. second++;SendSecondBroadcast();}}catch(InterruptedException e){
  3. e.printStackTrace();}}

8、显示专注计时的记录,使用SQLite数据库实现,和待办事项界面一样,添加完成专注计时的按钮,点击事件为添加计时信息的字符串到数据库。

五、音乐界面

实现原理,使用Service组件和MediaPlayer。点击音乐列表的某条音乐时,在服务中开启MediaPlayer播放音乐,并每隔一秒种发送一次本地广播(内容为当前已播放的秒数),设置界面中的进度条。并给进度条设置拖动的事件,将对应的播放进度传给MediaPlayer跳转至对应的进度。

1、定义Music类,包含音乐名,文件

  1. publicclassMusic{privateString name;privateFile file;// getter and setter }

2、 获取本地音乐文件

由于API 29以后getExternalStorageDirectory()被废弃,所以直接采用指定的路径获取MP3音乐文件。

  1. publicvoidShowMusicList(){File musicStorage =newFile("/storage/11E9-360F/Music");File[] musicFiles = musicStorage.listFiles(newFilenameFilter(){@Overridepublicbooleanaccept(File dir,String name){return name.endsWith(".mp3");}});for(int i=0; i<musicFiles.length; i++){Music music =newMusic();
  2. music.setName(musicFiles[i].getName());
  3. music.setFile(musicFiles[i]);
  4. musicList.add(music);}}

3、将音乐名使用ListView列表显示

  1. publicclassMusicAdapterextendsBaseAdapter{@OverridepublicintgetCount(){return musicList.size();}@OverridepublicObjectgetItem(int position){return musicList.get(position);}@OverridepubliclonggetItemId(int position){return position;}@OverridepublicViewgetView(int position,View convertView,ViewGroup parent){TextView musicNameText;Music music =(Music)getItem(position);if(convertView ==null){
  2. convertView =getLayoutInflater().inflate(R.layout.music_item,null);
  3. musicNameText =(TextView) convertView.findViewById(R.id.musicNameText);
  4. convertView.setTag(musicNameText);}else{
  5. musicNameText =(TextView) convertView.getTag();}
  6. musicNameText.setText(music.getName());return convertView;}}
  1. musicListView =(ListView) view.findViewById(R.id.musicListView);
  2. musicAdapter =newMusicAdapter();
  3. musicListView.setAdapter(musicAdapter);

4、这里我为了方便,播放音乐直接放在了TimeService中,并把这个服务名改为了MyService。

先绑定服务,获取localBinder

  1. //绑定服务Intent intent =newIntent(getActivity(),MyService.class);ServiceConnection connection =newServiceConnection(){@OverridepublicvoidonServiceConnected(ComponentName name,IBinder service){
  2. localBinder =(MyService.LocalBinder) service;Log.i(TAG,"onServiceConnected: ");}@OverridepublicvoidonServiceDisconnected(ComponentName name){Log.i(TAG,"onServiceDisconnected: ");}};getActivity().bindService(intent, connection,Context.BIND_AUTO_CREATE);

5、在MyService中写入播放音乐的方法

  1. publicvoidservicePlayMusic(Music music){try{if(mediaPlayer ==null){
  2. mediaPlayer =newMediaPlayer();}
  3. mediaPlayer.stop();
  4. mediaPlayer.reset();// 避免点击第二首音乐后同时播放
  5. mediaPlayer.setDataSource(music.getFile().getAbsolutePath());// 保持prepare和start同步执行
  6. mediaPlayer.prepareAsync();
  7. mediaPlayer.setOnPreparedListener(newMediaPlayer.OnPreparedListener(){@OverridepublicvoidonPrepared(MediaPlayer mp){
  8. mediaPlayer.start();
  9. musicTimeThread =newMusicTimeThread();
  10. musicTimeThread.start();}});}catch(IOException e){
  11. e.printStackTrace();}}

6、给ListView的每一个item布局添加点击事件,实现音乐播放。测试是否能正常播放

  1. convertView.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(View v){
  2. localBinder.getService().servicePlayMusic(music);}});

7、在MyService中创建一个新的线程类,用于每隔一秒钟获取一次音乐的播放进度,原理和专注页面的计时线程相同。

  1. publicclassMusicTimeThreadextendsThread{privatefinalObject lock =newObject();privateboolean pause =false;/**
  2. * 调用该方法实现线程的暂停
  3. */voidpauseThread(){Log.i(TAG,"pauseTimeThread: ");
  4. pause =true;}/*
  5. 调用该方法实现恢复线程的运行
  6. */voidresumeThread(){Log.i(TAG,"resumeTimeThread: ");
  7. pause =false;synchronized(lock){
  8. lock.notify();}}/**
  9. * 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应
  10. */voidonPause(){synchronized(lock){try{
  11. lock.wait();}catch(InterruptedException e){
  12. e.printStackTrace();}}}@Overridepublicvoidrun(){super.run();try{while(true){//当pause为true时,调用onPause挂起该线程TimeUnit.SECONDS.sleep(1);while(pause){onPause();}Log.i(TAG,"run: "+mediaPlayer.getCurrentPosition());}}catch(InterruptedException e){
  13. e.printStackTrace();}}}

8、在MusicFragment中注册一个用于接收音乐播放进度和播放总时长的本地广播,在MusicTimeThread中每隔一秒发送一次播放进度和总时长

  1. publicvoidRegisterProgressLocalBroadcast(){IntentFilter intentFilter =newIntentFilter();
  2. intentFilter.addAction("PROGRESS");BroadcastReceiver broadcastReceiver =newBroadcastReceiver(){@OverridepublicvoidonReceive(Context context,Intent intent){int duration = intent.getIntExtra("duration",0);int current = intent.getIntExtra("current",0);Log.i(TAG,"onReceive: "+duration+" "+current);}};LocalBroadcastManager.getInstance(getActivity()).registerReceiver(broadcastReceiver, intentFilter);}

9、在MyService中写一个方法,用于发送当前播放进度和总时长的本地广播。并在run方法中每一秒钟发送一次。观察打印台信息,测试是否能够正常发送和接收广播。

  1. publicvoidserviceSendProgressBroadcast(){// 发送当前进度的本地广播Intent intent =newIntent();
  2. intent.setAction("PROGRESS");// 总时长 ms
  3. intent.putExtra("duration", mediaPlayer.getDuration());// 当前播放进度 ms
  4. intent.putExtra("current", mediaPlayer.getCurrentPosition());LocalBroadcastManager.getInstance(this).sendBroadcast(intent);}// MusicTimeThread类中的run()@Overridepublicvoidrun(){super.run();try{while(true){//当pause为true时,调用onPause挂起该线程TimeUnit.SECONDS.sleep(1);while(pause){onPause();}serviceSendProgressBroadcast();}}catch(InterruptedException e){
  5. e.printStackTrace();}}

10、在MusicFragment对应的布局中加入进度条ProgressBar,并在左边显示当前播放时间,在右端显示总时长。然后在接收到本地广播的时候将播放进度current和总时长duration显示出来。

  1. <?xml version="1.0" encoding="utf-8"?><RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><ListViewandroid:id="@+id/musicListView"android:layout_width="match_parent"android:layout_height="match_parent"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:gravity="center"android:orientation="horizontal"><TextViewandroid:id="@+id/currentText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:text="00:00"/><ProgressBarandroid:id="@+id/musicProgressBar"style="?android:attr/progressBarStyleHorizontal"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"/><TextViewandroid:id="@+id/durationText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:text="00:00"/></LinearLayout></RelativeLayout>
  1. publicvoidRegisterProgressLocalBroadcast(){IntentFilter intentFilter =newIntentFilter();
  2. intentFilter.addAction("PROGRESS");BroadcastReceiver broadcastReceiver =newBroadcastReceiver(){@OverridepublicvoidonReceive(Context context,Intent intent){int duration = intent.getIntExtra("duration",0);int current = intent.getIntExtra("current",0);Log.i(TAG,"onReceive: "+duration+" "+current);//在广播接收事件时显示布局ShowMusicProgress(duration, current);}};LocalBroadcastManager.getInstance(getActivity()).registerReceiver(broadcastReceiver, intentFilter);}publicvoidShowMusicProgress(int duration,int current){
  3. currentText.setText(""+current);
  4. durationText.setText(""+duration);
  5. progressBar.setMax(duration);
  6. progressBar.setProgress(current);}

11、显示的时长是毫秒数,我们需要定义一个方法将其转换成 00:00 的时间格式。由于ProgressBar组件不能拖动进度,这里换成了SeekBar。

  1. publicStringhandleMusicTime(int ms){int min =(ms/1000)/60;int sec =(ms/1000)%60;String mm =String.valueOf(min);String ss =String.valueOf(sec);if(min<10){
  2. mm ="0"+mm;}if(sec<10){
  3. ss ="0"+ss;}return mm+":"+ss;}
  1. publicvoidShowMusicProgress(int duration,int current){
  2. currentText.setText(handleMusicTime(current));
  3. durationText.setText(handleMusicTime(duration));
  4. musicSeekBar.setMax(duration);
  5. musicSeekBar.setProgress(current);}

12、在MyService中写入方法,用于改变播放进度

  1. publicvoidsetMediaPlayerProgress(int current){Log.i(TAG,"setMediaPlayerProgress: ");
  2. mediaPlayer.seekTo(current);}

13、设置musicSeekBar的停止拖动事件,停止拖动时将进度传递给MyService改变播放进度

  1. publicvoidSetMusicSeekBarChangedListener(){
  2. musicSeekBar.setOnSeekBarChangeListener(newSeekBar.OnSeekBarChangeListener(){@OverridepublicvoidonProgressChanged(SeekBar seekBar,int progress,boolean fromUser){}@OverridepublicvoidonStartTrackingTouch(SeekBar seekBar){}@OverridepublicvoidonStopTrackingTouch(SeekBar seekBar){
  3. localBinder.getService().setMediaPlayerProgress(seekBar.getProgress());}});}

14、实现自动播放下一首。将servicePlayMusic方法的参数改为音乐列表和第一首音乐的位置。MediaPlayer中有一个完成播放时的监听事件setOnCompletionListener,在该事件中调用传入的音乐列表的下一首就可以了,(注意对列表长度取余,否则会报超出范围的异常)。

另外,在每一首播放结束时,应该先暂停计时线程,在下一首播放时恢复计时线程。考虑到第一首播放时计时线程还未创建,应该做一个非空判断。

  1. publicvoidservicePlayMusic(List<Music> musicList,int start){try{int size = musicList.size();if(mediaPlayer ==null){
  2. mediaPlayer =newMediaPlayer();}
  3. mediaPlayer.stop();
  4. mediaPlayer.reset();
  5. mediaPlayer.setDataSource(musicList.get(start).getFile().getAbsolutePath());
  6. mediaPlayer.prepareAsync();
  7. mediaPlayer.setOnPreparedListener(newMediaPlayer.OnPreparedListener(){@OverridepublicvoidonPrepared(MediaPlayer mp){
  8. mediaPlayer.start();if(musicTimeThread ==null){
  9. musicTimeThread =newMusicTimeThread();
  10. musicTimeThread.start();}else{
  11. musicTimeThread.resumeThread();}}});
  12. mediaPlayer.setOnCompletionListener(newMediaPlayer.OnCompletionListener(){@OverridepublicvoidonCompletion(MediaPlayer mp){servicePlayMusic(musicList,(start+1)%size );
  13. musicTimeThread.pauseThread();}});}catch(IOException e){
  14. e.printStackTrace();}}

15、添加暂停和继续按钮(同一个按钮),实现对播放的暂停和继续

在MyService中写入方法,用以暂停和继续播放(注意非空判断)

  1. publicvoidservicePauseMusic(){if(mediaPlayer !=null&& mediaPlayer.isPlaying()){
  2. mediaPlayer.pause();}}publicvoidserviceResumeMusic(){if(mediaPlayer!=null){
  3. mediaPlayer.start();}}

给按钮设置点击事件

  1. publicvoidSetPauseResumeImageOnClick(){
  2. musicPauseResumeImage.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(View v){if(localBinder.getService().musicIsPlaying()){
  3. localBinder.getService().servicePauseMusic();
  4. musicPauseResumeImage.setImageResource(R.drawable.resume_time);}else{
  5. localBinder.getService().serviceResumeMusic();
  6. musicPauseResumeImage.setImageResource(R.drawable.pause_time);}}});}

16、同样的,实现取消播放按钮事件,调用mediaPlayer.stop()

17、优化布局,完成!

项目源码在这:https://github.com/Lzh-Axq/adnroid-curriculum-design

标签: android java

本文转载自: https://blog.csdn.net/Dae_Lzh/article/details/122082326
版权归原作者 晚风Serein 所有, 如有侵权,请联系我们删除。

“手把手教你完成Android期末大作业(多功能应用型APP)”的评论:

还没有评论