前言
Android期末作業,估摸著也花了整整5天,里面可能會缺少某些細節,如果跟著做有不會的評論就行,每天都會看,盡力解答,
功能
- 待辦
- 專注計時
- 音樂
- 天氣
實作步驟
一、底部選單欄切換頁
1.添加依賴
dependencies {
implementation 'com.google.android.material:material:1.2.1'
}
2.在res資源檔案夾下新建一個menu檔案夾,創建底部導航的選單布局檔案
- 創建對應數量的item,為每個選單欄選項
- 給每個item定義title(標題),icon(圖示)
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_task"
android:icon="@drawable/menu_task"
android:title="事項"/>
<item
android:id="@+id/menu_accounts"
android:icon="@drawable/menu_task"
android:title="專注"/>
<item
android:id="@+id/menu_absorbed"
android:icon="@drawable/menu_task"
android:title="音樂"/>
<item
android: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引入
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="60dp"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android: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頁面
<!-- task_fragment.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Task PAGE"
android:textSize="40dp"
android:gravity="center"
/>
</LinearLayout>
// TaskFragment.java
public class TaskFragment extends Fragment {
//重寫onCreateView, fragment系結布局檔案
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.task_fragment, container, false);
return view;
}
}
5.在MainActivity.java中進行設定BottomNavigation選擇監聽事件對fragment進行管理,
public List<Fragment> fragmentList = new ArrayList<>();
private FragmentManager fragmentManager;
// 底部導航欄模塊
public void InitBottomNavigation() {
// 添加五個fragment實體到fragmentList,以便管理
fragmentList.add(new TaskFragment());
fragmentList.add(new AbsorbedFragment());
fragmentList.add(new MusicFragment());
fragmentList.add(new WeatherFragment());
//建立fragment管理器
fragmentManager = getSupportFragmentManager();
//管理器開啟事務,將fragment實體加入管理器
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顯示初始狀態
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);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@SuppressLint("NonConstantResourceId")
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
// return true : show selected style
// return false: do not show
switch (item.getItemId()) {
case R.id.menu_task:
ShowFragment(0);
return true;
case R.id.menu_accounts:
ShowFragment(1);
return true;
case R.id.menu_absorbed:
ShowFragment(2);
return true;
case R.id.menu_weather:
ShowFragment(3);
return true;
default:
Log.i(TAG, "onNavigationItemSelected: Error");
break;
}
return false;
}
});
}
public void ShowFragment(int index) {
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、添加依賴(用于獲取和決議天氣資料)
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
2、獲取天氣API介面,這里以臨海市為例,使用OkHttp請求天氣資料,使用Log列印測驗是否能成功獲取
public void RefreshWeatherData() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(weatherUrl).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
String weatherJson = response.body().string();
Weather weather = new Gson().fromJson(weatherJson, Weather.class);
Log.i(TAG, "onResponse: "+weatherJson);
}
});
}
3、Json資料獲取成功后,根據Json資料的結構建立Weather類用于決議Json資料,
// class Weather
public class Weather {
private String city; //城市名
private String update_time; //更新時間
private List<DayData> data; //每天的天氣資料串列,data.get(0)為當天資料
/*
getter and setter
*/
}
// class DayData
public class DayData {
private String wea; //天氣狀況
private String tem; //當前溫度
private String tem1; //最高溫
private String tem2; //最低溫
private String humidity; //濕度
private String air_level; //空氣質量等級
private String air_tips; //空氣質量小提示
/*
getter and setter
*/
}
4、由于OkHttp的請求是在子執行緒中進行的,需要使用Handler訊息佇列機制將決議出來的Weather實體發送到主執行緒用以顯示在界面上,
//訊息處理類
public class MyHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//what == 1 天氣訊息
if (msg.what == 1)
ShowWeatherInfo((Weather) msg.obj);
}
}
public void ShowWeatherInfo(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請求完成時發送訊息
public void RefreshWeatherData() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(weatherUrl).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
String weatherJson = response.body().string();
Weather weather = new Gson().fromJson(weatherJson, Weather.class);
Message message = new Message();
message.what = 1;
message.obj = weather;
myHandler.sendMessage(message);
}
});
}
6、優化xml布局
三、待辦事項界面
這里由于ListView是放在Fragment中的,所以直接在MainAcitivity.java中設定配接器可能會出現資料沒法顯示的bug,所以我直接把從資料庫獲取資料,Adapter的定義,ListView設定配接器的模塊搬到了TaskFragment.java中,
1.在task.xml中添加ListView,先不用設定UI樣式,先把資料拿到并顯示在界面上
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/taskText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="事項"/>
<ListView
android:id="@+id/taskListView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
2.創建task_item.xml布局檔案(這里注意線性布局的方向及寬高,以保證task_item能放在ListView中)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android: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類,存放事項資料
package com.example.daily.tasks;
public class TaskItem {
private int id;
private String content;
private String type;
private int status;
public TaskItem(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資料庫并獲取待辦事項的資料
public class TaskFragment extends Fragment {
private static final String TAG = TaskFragment.class.getName();
private List<TaskItem> taskList = new ArrayList<>();
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle 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;
}
//讀取資料庫并將資料存到taskList
public void ReadTaskDataFromSQL(){
MySQLiteOpenHelper openHelper = new MySQLiteOpenHelper(getActivity());
SQLiteDatabase readDatabase = openHelper.getReadableDatabase();
Cursor cursor = readDatabase.query(
"task",
new String[]{"id", "type", "content", "status"},
null,null,null,null,null
);
while(cursor.moveToNext()){
TaskItem task = new TaskItem(
cursor.getInt(0),
cursor.getString(1),
cursor.getString(2),
cursor.getInt(3)
);
taskList.add(task);
}
}
//創建SQLite資料庫
public class MySQLiteOpenHelper extends SQLiteOpenHelper{
public MySQLiteOpenHelper(@Nullable Context context) {
super(context, "Daily.db", null, 1);
}
@Override
public void onCreate(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);";
db.execSQL(create_sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
}
5.資料獲取正常以后,建立ListView配接器,這里涉及到快取convertView的使用,使用convertView可以防止每創建一個item時就決議一個布局,這樣效率肯定不高,convertView是Android提供的用于快取的View,在第一次渲染item時,將將決議出來的View放入快取convertView,在下一次渲染item的時候,判斷convertView是否為空即可,
public class TaskAdapter extends BaseAdapter{
@Override
public int getCount() {
//測驗getCount回傳值是否正常
Log.i(TAG, "getCount: "+taskList.size());
return taskList.size();
}
@Override
public Object getItem(int position) {
return taskList.get(position);
}
@Override
public long getItemId(int position) {
return taskList.get(position).getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//測驗getView是否執行
Log.i(TAG, "getView: "+position);
ViewHolder viewHolder;
TaskItem task = (TaskItem) getItem(position);
if(convertView == null){
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(getActivity()).inflate(R.layout.task_item, null);
viewHolder.taskItemTextView = convertView.findViewById(R.id.task_content);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.taskItemTextView.setText(task.getId()+" "+task.getContent());
return convertView;
}
}
public class ViewHolder{
TextView taskItemTextView;
}
6.在onCreateView中設定ListView的配接器
private List<TaskItem> taskList = new ArrayList<>();
private TaskAdapter taskAdapter;
private ListView taskListView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.task, container, false);
taskListView = view.findViewById(R.id.taskListView);
taskAdapter = new TaskAdapter();
taskListView.setAdapter(taskAdapter);
ReadTaskDataFromSQL();
return view;
}
7.設計每一條待辦事項的布局樣式,如圖所示,布局設計就不放原碼了,使用多個線性布局的嵌套,gravity,margin屬性即可實作,
img:task-2.jpg
8.根據待辦事項的狀態顯示不同按鈕,并標記待辦事項的重要程度,
public void ShowTaskContent(View convertView, TaskItem task){
//顯示事項內容
TextView content = ((ViewHolder) convertView.getTag()).taskContent;
int status = task.getStatus();
content.setText(task.getContent());
//事項已完成 中劃線 灰色
if(status == 1){
content.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG);
content.setTextColor(getResources().getColor(R.color.GRAY, null));
}
//事項未完成 無中劃線 黑色
if(status == 0){
content.getPaint().setFlags(0);
content.setTextColor(getResources().getColor(R.color.black, null));
}
//事項失敗 無中劃線 灰色
if(status == -1){
content.getPaint().setFlags(0);
content.setTextColor(getResources().getColor(R.color.GRAY, null));
}
}
public void ShowTaskLevel(View convertView, int level){
// 顯示事項重要級別 level : 0~3 四個優先級 Ⅰ Ⅱ Ⅲ Ⅳ
TextView levelText = ((ViewHolder) convertView.getTag()).taskLevel;
if(level == 0){
levelText.setText("Ⅰ");
levelText.setTextColor(getResources().getColor(R.color.level_0, null));
}
if(level == 1){
levelText.setText("Ⅱ");
levelText.setTextColor(getResources().getColor(R.color.level_1, null));
}
if(level == 2){
levelText.setText("Ⅲ");
levelText.setTextColor(getResources().getColor(R.color.level_2, null));
}
if(level == 3){
levelText.setText("Ⅳ");
levelText.setTextColor(getResources().getColor(R.color.level_3, null));
}
}
9.在頂部添加五個TextView作為分類查看事項選單,點擊某一分類即可查看該分類下的所有事項,并修改被點擊TextView 的樣式,
/** 選單欄模塊 **/
public void SetTypeMenuOnClick(View view){
typeMenuList.add((TextView) view.findViewById(R.id.TypeMenu_default));
typeMenuList.add((TextView) view.findViewById(R.id.TypeMenu_work));
typeMenuList.add((TextView) view.findViewById(R.id.TypeMenu_study));
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; //分類索引值
typeMenuList.get(i).setOnClickListener(v -> {
// 點擊分類的一項后設定樣式
typeMenuList.get(finalI).setTextColor(Color.BLACK);
typeMenuList.get(finalI).setBackgroundColor(Color.WHITE);
typeMenuList.get((finalI+1) % 4).setBackgroundColor(color[(finalI+1) % 4]);
typeMenuList.get((finalI+1) % 4).setTextColor(Color.WHITE);
typeMenuList.get((finalI+2) % 4).setBackgroundColor(color[(finalI+2) % 4]);
typeMenuList.get((finalI+2) % 4).setTextColor(Color.WHITE);
typeMenuList.get((finalI+3) % 4).setBackgroundColor(color[(finalI+3) % 4]);
typeMenuList.get((finalI+3) % 4).setTextColor(Color.WHITE);
// 顯示某一類待辦資料,這里篩選taskList即可
List<TaskItem> typeTaskList = new ArrayList<>();
String[] types = {"全部", "作業","學習","生活"};
/* 分類索引值
0 全部
1 作業
2 學習
3 生活
*/
// 點擊作業 學習 生活時分類
// TypeNow 是一個全域變數,表示當前的分類
TypeNow = types[finalI];
Log.i(TAG, "SetTypeMenuOnClick: "+TypeNow);
ReadTaskFromDatabase();
});
}
}
10.task.xml布局右上角加入一個switch控制元件用以隱藏已完成事項,
//隱藏已完成Switch
Switch hideCompletedTaskSwitch = view.findViewById(R.id.HideCompletedTaskView);
hideCompletedTaskSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked) isHideCompleted = true;
else isHideCompleted = false;
// isHideCompleted 是一個全域變數,表示當前是否隱藏已完成事項
ReadTaskFromDatabase();
}
});
完成9,10步之后就需要修改讀取資料庫的模塊,加入TypeNow和isHideCompleted變數加以控制,
public void ReadTaskFromDatabase(){
if (taskList.size()!=0) {
taskList.clear();
}
Cursor cursor = readDatabase.query(
"task",
new String[]{"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 = new TaskItem(
cursor.getInt(0),
cursor.getString(1),
cursor.getInt(2),
cursor.getString(3),
cursor.getString(4),
cursor.getInt(5)
);
taskList.add(task);
}
}
}
//不隱藏,有分類
if(!isHideCompleted && !TypeNow.equals("全部")){
while(cursor.moveToNext()){
if(cursor.getString(1).equals(TypeNow)){
TaskItem task = new TaskItem(
cursor.getInt(0),
cursor.getString(1),
cursor.getInt(2),
cursor.getString(3),
cursor.getString(4),
cursor.getInt(5)
);
taskList.add(task);
}
}
}
//隱藏,不分類
if(isHideCompleted && TypeNow.equals("全部")){
while(cursor.moveToNext()){
if(cursor.getInt(5) == 0){
TaskItem task = new TaskItem(
cursor.getInt(0),
cursor.getString(1),
cursor.getInt(2),
cursor.getString(3),
cursor.getString(4),
cursor.getInt(5)
);
taskList.add(task);
}
}
}
else{
while(cursor.moveToNext()){
TaskItem task = new TaskItem(
cursor.getInt(0),
cursor.getString(1),
cursor.getInt(2),
cursor.getString(3),
cursor.getString(4),
cursor.getInt(5)
);
taskList.add(task);
}
}
// 別忘了通知ListView配接器資料變化
taskAdapter.notifyDataSetChanged();
}
11、添加事項,這里使用的是在整個RelativeLayout布局中添加一個ImageView作為添加事項的按鈕,并定義點擊事件,點擊時彈出對話框,在對話框中輸入添加事項的資訊,
自定義對話框需要先設計一個layout布局檔案add_task_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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">
<TextView
android:text="添加事項"
android:textColor="@color/black"
android:textSize="25dp"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="50dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:orientation="horizontal">
<TextView
android: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="事項" />
<EditText
android: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>
<RelativeLayout
android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/textView2"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@color/black"
android:text="事項分類" />
<RadioGroup
android:id="@+id/typeRadioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/radioButton8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/workColor"
android:text="作業" />
<RadioButton
android:id="@+id/radioButton7"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/studyColor"
android:text="學習" />
<RadioButton
android:id="@+id/radioButton6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/lifeColor"
android:text="生活" />
<RadioButton
android:id="@+id/radioButton5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/defaultColor"
android:text="不分類" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:orientation="vertical">
<TextView
android:id="@+id/textView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@color/black"
android:text="重要級別" />
<RadioGroup
android:id="@+id/levelRadioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/radioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/level_0"
android:text="0 重要且緊急" />
<RadioButton
android:id="@+id/radioButton2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/level_1"
android:text="1 重要但不緊急" />
<RadioButton
android:id="@+id/radioButton3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/level_2"
android:text="2 不重要但緊急" />
<RadioButton
android:id="@+id/radioButton4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/level_3"
android:text="3 不重要且不緊急" />
</RadioGroup>
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:orientation="horizontal">
<TextView
android: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="備注" />
<EditText
android:id="@+id/addTaskInfoEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:text="" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<Button
android:id="@+id/cancelAddButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="100dp"
android:text="取消" />
<Button
android:id="@+id/confirmAddButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="確定" />
</LinearLayout>
</LinearLayout>
12、定義一個方法,實作彈出添加事項界面的對話框,并設定確認和取消按鈕的點擊事件,確認按鈕即添加該事項到資料庫并顯示
public void ShowAddTaskDialog(){
//獲取添加事項布局實體
View addView = getLayoutInflater().inflate(R.layout.add_task_dialog, null);
// 將該布局添加到對話框
final AlertDialog addDialog = new AlertDialog.Builder(getActivity()).setView(addView).create();
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);
typeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
}
});
levelGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
}
});
//確定按鈕
confirmButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(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(
new TaskItem(addType, addLevel, addContent, addInfo, 0)
);
addDialog.dismiss();
}
});
// 取消按鈕
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addDialog.dismiss();
}
});
}
13、然后在添加事項的點擊事件中呼叫ShowAddTaskDialog()即可
//添加事項的按鈕
ImageView addTaskImage = (ImageView) view.findViewById(R.id.addTaskImage);
addTaskImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ShowAddTaskDialog();
}
});
14.長按某條事項彈出對話框,顯示事項資訊,可以修改,洗掉,標記失敗,和添加事項的對話框實作原理相同,這里不詳細說明,給出代碼供參考
<!-- task_info_dialog.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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">
<TextView
android:text="事項資訊"
android:textColor="@color/black"
android:textSize="25dp"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="50dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:orientation="horizontal">
<TextView
android: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="事項" />
<EditText
android: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>
<RelativeLayout
android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/textView2"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@color/black"
android:text="事項分類" />
<RadioGroup
android:id="@+id/typeRadioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/workButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/workColor"
android:text="作業" />
<RadioButton
android:id="@+id/studyButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/studyColor"
android:text="學習" />
<RadioButton
android:id="@+id/lifeButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/lifeColor"
android:text="生活" />
<RadioButton
android:id="@+id/defaultButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/defaultColor"
android:text="全部" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:orientation="vertical">
<TextView
android:id="@+id/textView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@color/black"
android:text="重要級別" />
<RadioGroup
android:id="@+id/levelRadioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/level0Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/level_0"
android:text="0 重要且緊急" />
<RadioButton
android:id="@+id/level1Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/level_1"
android:text="1 重要但不緊急" />
<RadioButton
android:id="@+id/level2Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/level_2"
android:text="2 不重要但緊急" />
<RadioButton
android:id="@+id/level3Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/level_3"
android:text="3 不重要且不緊急" />
</RadioGroup>
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:orientation="horizontal">
<TextView
android: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="備注" />
<EditText
android:id="@+id/addTaskInfoEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:text="" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_marginRight="60dp"
android:orientation="vertical"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/deleteTaskButton"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:src="@drawable/delete_icon"
/>
<TextView
android: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>
<LinearLayout
android:layout_width="wrap_content"
android:orientation="vertical"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/failTaskButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:src="@drawable/fail_icon"
/>
<TextView
android:layout_width="40dp"
android:layout_height="wrap_content"
android:textColor="#d81e06"
android:gravity="center"
android:textSize="15dp"
android:layout_marginTop="5dp"
android:text="失敗"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_marginLeft="60dp"
android:orientation="vertical"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/modifyTaskButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:src="@drawable/modify_icon"
android:text="修改" />
<TextView
android: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>
public void ShowTaskInfoDialog(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);
final AlertDialog infoDialog = new AlertDialog.Builder(getActivity()).setView(infoView).create();
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事項資訊
contentEdit.setText(content);
infoEdit.setText(info);
SetTypeRadioGroupSelected(typeGroup, type);
SetLevelRadioGroupSelected(levelGroup, level);
//洗掉按鈕
deleteImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DeleteTaskToDatabase(task);
infoDialog.dismiss();
}
});
//失敗按鈕
failImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
task.setStatus(-1);
UpDateTaskToDatabase(task);
//別忘記關閉對話框
infoDialog.dismiss();
}
});
//修改按鈕
modifyImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(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));
task.setContent(modifyContent);
task.setInfo(modifyInfo);
task.setType(modifyType);
task.setLevel(modifyLevel);
UpDateTaskToDatabase(task);
//別忘記關閉對話框
infoDialog.dismiss();
}
});
}
//在配接器的getView中,設定每條事項的長按事件:呼叫ShowTaskInfoDialog彈出對話框顯示事項的內容
convertView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ShowTaskInfoDialog(task);
return false;
}
});
四、專注計時界面
計時的原理是使用Android四大組件之一的Service開啟計時執行緒,并每隔一秒鐘發送一次本地廣播通知主界面更新布局,
1、創建服務類TimeService,繼承自Service,這里在Service類里面定義了一個TimeThread自定義執行緒類,用以方便執行緒的掛起和恢復,
public class TimeService extends Service {
private static final String TAG = TimeService.class.getName();
//計時秒數
private int second = 0;
public int getSecond() {
return second;
}
public void setSecond(int second) {
this.second = second;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new LocalBinder();
}
@Override
public void onCreate() {
Log.i(TAG, "TimeService onCreate: ");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "TimeService onStartCommand: ");
//創建計時執行緒實體
timeThread = new TimeThread();
timeThread.start();
isRunning = true;
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "TimeService onDestroy: ");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "TimeService onUnbind: ");
return super.onUnbind(intent);
}
//用于回傳本地服務
public class LocalBinder extends Binder{
public TimeService getService(){
return TimeService.this;
}
}
public class TimeThread extends Thread{
private final Object lock = new Object();
private boolean pause = false;
/**
* 呼叫該方法實作執行緒的暫停
*/
void pauseThread(){
Log.i(TAG, "pauseTimeThread: ");
pause = true;
}
/*
呼叫該方法實作恢復執行緒的運行
*/
void resumeThread(){
Log.i(TAG, "resumeTimeThread: ");
pause = false;
synchronized (lock){
lock.notify();
}
}
/**
* 這個方法只能在run 方法中實作,不然會阻塞主執行緒,導致頁面無回應
*/
void onPause() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
super.run();
try {
while(true){
//當pause為true時,呼叫onPause掛起該執行緒
TimeUnit.SECONDS.sleep(1);
while(pause) {
onPause();
}
second++;
SendSecondBroadcast();
Log.i(TAG, "run: "+second);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、在AndroidManifast注冊TimeService類
3、在AbsorbedFragment中系結服務,運行測驗service是否連接成功
public void BindTimeService(){
Intent intent = new Intent(getActivity(), TimeService.class);
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
localBinder = (TimeService.LocalBinder) service;
if(localBinder.getService() != null){
Log.i(TAG, "onServiceConnected: time service connected");
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: ");
}
};
getActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
4、給開始計時按鈕添加點擊事件,運行測驗TimeThread是否每隔一秒列印一次
Intent intent = new Intent();
intent.setClass(getActivity(), TimeService.class);
getActivity().startService(intent);
5、運行成功后,添加暫停,繼續,取消按鈕,運行測驗觀察列印資訊是否正常
- 暫停點擊事件:localBinder.getService().PauseTime();
- 繼續點擊事件:localBinder.getService().ResumeTime();
- 取消點擊事件:localBinder.getService().CancelTime();
//TimeService中用于在MainActivity呼叫的方法
public void PauseTime(){
timeThread.pauseThread();
isRunning = false;
}
public void ResumeTime(){
timeThread.resumeThread();
isRunning = true;
}
public void CancelTime(){
timeThread.pauseThread();
second = 0;
}
6、創建本地廣播,用以接收TimeThread發送的秒數,并更新布局界面
//注冊接收計時秒數的本地廣播
IntentFilter timeIntentFilter = new IntentFilter();
timeIntentFilter.addAction("SECONDS_CHANGED");
BroadcastReceiver timeBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int second = localBinder.getService().getSecond();
ShowTimeSecond(second);
}
};
LocalBroadcastManager.getInstance(getActivity())
.registerReceiver(timeBroadcastReceiver, timeIntentFilter);
7、在TimeThread的run方法中每一秒發送一次本地廣播,運行測驗是否正常
@Override
public void run() {
super.run();
try {
while(true){
//當pause為true時,呼叫onPause掛起該執行緒
TimeUnit.SECONDS.sleep(1);
while(pause) {
onPause();
}
second++;
SendSecondBroadcast();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
8、顯示專注計時的記錄,使用SQLite資料庫實作,和待辦事項界面一樣,添加完成專注計時的按鈕,點擊事件為添加計時資訊的字串到資料庫,
五、音樂界面
實作原理,使用Service組件和MediaPlayer,點擊音樂串列的某條音樂時,在服務中開啟MediaPlayer播放音樂,并每隔一秒種發送一次本地廣播(內容為當前已播放的秒數),設定界面中的進度條,并給進度條設定拖動的事件,將對應的播放進度傳給MediaPlayer跳轉至對應的進度,
1、定義Music類,包含音樂名,檔案
public class Music {
private String name;
private File file;
// getter and setter
}
2、 獲取本地音樂檔案
由于API 29以后getExternalStorageDirectory()被廢棄,所以直接采用指定的路徑獲取MP3音樂檔案,
public void ShowMusicList(){
File musicStorage = new File("/storage/11E9-360F/Music");
File[] musicFiles = musicStorage.listFiles(new FilenameFilter(){
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".mp3");
}
});
for(int i=0; i<musicFiles.length; i++){
Music music = new Music();
music.setName(musicFiles[i].getName());
music.setFile(musicFiles[i]);
musicList.add(music);
}
}
3、將音樂名使用ListView串列顯示
public 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 position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView musicNameText;
Music music = (Music) getItem(position);
if(convertView == null){
convertView = getLayoutInflater().inflate(R.layout.music_item, null);
musicNameText = (TextView) convertView.findViewById(R.id.musicNameText);
convertView.setTag(musicNameText);
}else{
musicNameText = (TextView) convertView.getTag();
}
musicNameText.setText(music.getName());
return convertView;
}
}
musicListView = (ListView) view.findViewById(R.id.musicListView);
musicAdapter = new MusicAdapter();
musicListView.setAdapter(musicAdapter);
4、這里我為了方便,播放音樂直接放在了TimeService中,并把這個服務名改為了MyService,
先系結服務,獲取localBinder
//系結服務
Intent intent = new Intent(getActivity(), MyService.class);
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
localBinder = (MyService.LocalBinder) service;
Log.i(TAG, "onServiceConnected: ");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: ");
}
};
getActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE);
5、在MyService中寫入播放音樂的方法
public void servicePlayMusic(Music music) {
try {
if(mediaPlayer == null){
mediaPlayer = new MediaPlayer();
}
mediaPlayer.stop();
mediaPlayer.reset(); // 避免點擊第二首音樂后同時播放
mediaPlayer.setDataSource(music.getFile().getAbsolutePath());
// 保持prepare和start同步執行
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
musicTimeThread = new MusicTimeThread();
musicTimeThread.start();
}
});
}catch (IOException e){
e.printStackTrace();
}
}
6、給ListView的每一個item布局添加點擊事件,實作音樂播放,測驗是否能正常播放
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
localBinder.getService().servicePlayMusic(music);
}
});
7、在MyService中創建一個新的執行緒類,用于每隔一秒鐘獲取一次音樂的播放進度,原理和專注頁面的計時執行緒相同,
public class MusicTimeThread extends Thread{
private final Object lock = new Object();
private boolean pause = false;
/**
* 呼叫該方法實作執行緒的暫停
*/
void pauseThread(){
Log.i(TAG, "pauseTimeThread: ");
pause = true;
}
/*
呼叫該方法實作恢復執行緒的運行
*/
void resumeThread(){
Log.i(TAG, "resumeTimeThread: ");
pause = false;
synchronized (lock){
lock.notify();
}
}
/**
* 這個方法只能在run 方法中實作,不然會阻塞主執行緒,導致頁面無回應
*/
void onPause() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
super.run();
try {
while(true){
//當pause為true時,呼叫onPause掛起該執行緒
TimeUnit.SECONDS.sleep(1);
while(pause) {
onPause();
}
Log.i(TAG, "run: "+mediaPlayer.getCurrentPosition());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
8、在MusicFragment中注冊一個用于接收音樂播放進度和播放總時長的本地廣播,在MusicTimeThread中每隔一秒發送一次播放進度和總時長
public void RegisterProgressLocalBroadcast(){
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("PROGRESS");
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(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方法中每一秒鐘發送一次,觀察列印臺資訊,測驗是否能夠正常發送和接收廣播,
public void serviceSendProgressBroadcast(){
// 發送當前進度的本地廣播
Intent intent = new Intent();
intent.setAction("PROGRESS");
// 總時長 ms
intent.putExtra("duration", mediaPlayer.getDuration());
// 當前播放進度 ms
intent.putExtra("current", mediaPlayer.getCurrentPosition());
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
// MusicTimeThread類中的run()
@Override
public void run() {
super.run();
try {
while(true){
//當pause為true時,呼叫onPause掛起該執行緒
TimeUnit.SECONDS.sleep(1);
while(pause) {
onPause();
}
serviceSendProgressBroadcast();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
10、在MusicFragment對應的布局中加入進度條ProgressBar,并在左邊顯示當前播放時間,在右端顯示總時長,然后在接收到本地廣播的時候將播放進度current和總時長duration顯示出來,
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/musicListView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/currentText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="00:00" />
<ProgressBar
android:id="@+id/musicProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/durationText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="00:00" />
</LinearLayout>
</RelativeLayout>
public void RegisterProgressLocalBroadcast(){
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("PROGRESS");
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(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);
}
public void ShowMusicProgress(int duration, int current){
currentText.setText(""+current);
durationText.setText(""+duration);
progressBar.setMax(duration);
progressBar.setProgress(current);
}
11、顯示的時長是毫秒數,我們需要定義一個方法將其轉換成 00:00 的時間格式,由于ProgressBar組件不能拖動進度,這里換成了SeekBar,
public String handleMusicTime(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){
mm = "0"+mm;
}
if(sec<10){
ss = "0"+ss;
}
return mm+":"+ss;
}
public void ShowMusicProgress(int duration, int current){
currentText.setText(handleMusicTime(current));
durationText.setText(handleMusicTime(duration));
musicSeekBar.setMax(duration);
musicSeekBar.setProgress(current);
}
12、在MyService中寫入方法,用于改變播放進度
public void setMediaPlayerProgress(int current){
Log.i(TAG, "setMediaPlayerProgress: ");
mediaPlayer.seekTo(current);
}
13、設定musicSeekBar的停止拖動事件,停止拖動時將進度傳遞給MyService改變播放進度
public void SetMusicSeekBarChangedListener(){
musicSeekBar.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) {
localBinder.getService().setMediaPlayerProgress(seekBar.getProgress());
}
});
}
14、實作自動播放下一首,將servicePlayMusic方法的引數改為音樂串列和第一首音樂的位置,MediaPlayer中有一個完成播放時的監聽事件setOnCompletionListener,在該事件中呼叫傳入的音樂串列的下一首就可以了,(注意對串列長度取余,否則會報超出范圍的例外),
另外,在每一首播放結束時,應該先暫停計時執行緒,在下一首播放時恢復計時執行緒,考慮到第一首播放時計時執行緒還未創建,應該做一個非空判斷,
public void servicePlayMusic(List<Music> musicList, int start) {
try {
int size = musicList.size();
if(mediaPlayer == null){
mediaPlayer = new MediaPlayer();
}
mediaPlayer.stop();
mediaPlayer.reset();
mediaPlayer.setDataSource(musicList.get(start).getFile().getAbsolutePath());
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
if(musicTimeThread == null){
musicTimeThread = new MusicTimeThread();
musicTimeThread.start();
}else{
musicTimeThread.resumeThread();
}
}
});
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
servicePlayMusic(musicList, (start+1)%size );
musicTimeThread.pauseThread();
}
});
}catch (IOException e){
e.printStackTrace();
}
}
15、添加暫停和繼續按鈕(同一個按鈕),實作對播放的暫停和繼續
在MyService中寫入方法,用以暫停和繼續播放(注意非空判斷)
public void servicePauseMusic(){
if(mediaPlayer != null && mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
public void serviceResumeMusic(){
if(mediaPlayer!=null){
mediaPlayer.start();
}
}
給按鈕設定點擊事件
public void SetPauseResumeImageOnClick(){
musicPauseResumeImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(localBinder.getService().musicIsPlaying()){
localBinder.getService().servicePauseMusic();
musicPauseResumeImage.setImageResource(R.drawable.resume_time);
}else{
localBinder.getService().serviceResumeMusic();
musicPauseResumeImage.setImageResource(R.drawable.pause_time);
}
}
});
}
16、同樣的,實作取消播放按鈕事件,呼叫mediaPlayer.stop()
17、優化布局,完成!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/390661.html
標籤:其他
上一篇:IPv6與VoIP(第二部分)
