手把手教你完成Android期末大作業(多功能應用型APP)
前言
Android期末作業,估摸著也花了整整5天。裡面可能會缺少某些細節,如果跟著做有不會的評論就行,每天都會看,盡力解答。
功能
待辦
專注計時
音樂
天氣
實現步驟
一、底部選單欄切換頁
1.新增依賴
dependencies {
implementation 'com.google.android.material:material:1.2.1'
}
1
2
3
2.在res資原始檔夾下新建一個menu資料夾,建立底部導航的選單佈局檔案
建立對應數量的item,為每個選單欄選項
給每個item定義title(標題),icon(圖示)
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns: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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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="
xmlns:tools="
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="
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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
4.依次建立每個頁面的Fragment類及佈局檔案,如Task頁面
<!-- task_fragment.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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>
1
2
3
4
5
6
7
8
9
10
11
12
13
// 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;
}
}
1
2
3
4
5
6
7
8
9
10
11
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
二、天氣顯示介面
1、新增依賴(用於獲取和解析天氣資料)
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
1
2
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);
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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就行
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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="
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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2.建立task_item.xml佈局檔案(這裡注意線性佈局的方向及寬高,以保證task_item能放在ListView中)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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>
1
2
3
4
5
6
7
8
9
10
11
12
13
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方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) {}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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();
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
完成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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
11、新增事項,這裡使用的是在整個RelativeLayout佈局中新增一個ImageView作為新增事項的按鈕,並定義點選事件,點選時彈出對話方塊,在對話方塊中輸入新增事項的資訊。
自定義對話方塊需要先設計一個layout佈局檔案add_task_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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();
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
13、然後在新增事項的點選事件中呼叫ShowAddTaskDialog()即可
//新增事項的按鈕
ImageView addTaskImage = (ImageView) view.findViewById(R.id.addTaskImage);
addTaskImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ShowAddTaskDialog();
}
});
1
2
3
4
5
6
7
8
14.長按某條事項彈出對話方塊,顯示事項資訊,可以修改,刪除,標記失敗。和新增事項的對話方塊實現原理相同,這裡不詳細說明,給出程式碼供參考
<!-- task_info_dialog.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
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();
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//在介面卡的getView中,設定每條事項的長按事件:呼叫ShowTaskInfoDialog彈出對話方塊顯示事項的內容
convertView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ShowTaskInfoDialog(task);
return false;
}
});
1
2
3
4
5
6
7
8
四、專注計時介面
計時的原理是使用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();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4、給開始計時按鈕新增點選事件,執行測試TimeThread是否每隔一秒列印一次
Intent intent = new Intent();
intent.setClass(getActivity(), TimeService.class);
getActivity().startService(intent);
1
2
3
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
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);
1
2
3
4
5
6
7
8
9
10
11
12
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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
8、顯示專注計時的記錄,使用SQLite資料庫實現,和待辦事項介面一樣,新增完成專注計時的按鈕,點選事件為新增計時資訊的字串到資料庫。
五、音樂介面
實現原理,使用Service元件和MediaPlayer。點選音樂列表的某條音樂時,在服務中開啟MediaPlayer播放音樂,並每隔一秒種傳送一次本地廣播(內容為當前已播放的秒數),設定介面中的進度條。並給進度條設定拖動的事件,將對應的播放進度傳給MediaPlayer跳轉至對應的進度。
1、定義Music類,包含音樂名,檔案
public class Music {
private String name;
private File file;
// getter and setter
}
1
2
3
4
5
6
7
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
musicListView = (ListView) view.findViewById(R.id.musicListView);
musicAdapter = new MusicAdapter();
musicListView.setAdapter(musicAdapter);
1
2
3
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
6、給ListView的每一個item佈局新增點選事件,實現音樂播放。測試是否能正常播放
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
localBinder.getService().servicePlayMusic(music);
}
});
1
2
3
4
5
6
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();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
10、在MusicFragment對應的佈局中加入進度條ProgressBar,並在左邊顯示當前播放時間,在右端顯示總時長。然後在接收到本地廣播的時候將播放進度current和總時長duration顯示出來。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns: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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public void ShowMusicProgress(int duration, int current){
currentText.setText(handleMusicTime(current));
durationText.setText(handleMusicTime(duration));
musicSeekBar.setMax(duration);
musicSeekBar.setProgress(current);
}
1
2
3
4
5
6
12、在MyService中寫入方法,用於改變播放進度
public void setMediaPlayerProgress(int current){
Log.i(TAG, "setMediaPlayerProgress: ");
mediaPlayer.seekTo(current);
}
1
2
3
4
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());
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
15、新增暫停和繼續按鈕(同一個按鈕),實現對播放的暫停和繼續
在MyService中寫入方法,用以暫停和繼續播放(注意非空判斷)
public void servicePauseMusic(){
if(mediaPlayer != null && mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
public void serviceResumeMusic(){
if(mediaPlayer!=null){
mediaPlayer.start();
}
}
1
2
3
4
5
6
7
8
9
10
給按鈕設定點選事件
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);
}
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
16、同樣的,實現取消播放按鈕事件,呼叫mediaPlayer.stop()
17、最佳化佈局,完成!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70026759/viewspace-2936117/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Python期末大作業Python
- 手把手教你寫蛇蛇大作戰
- 教你如何在快應用中跳轉到Android的appAndroidAPP
- 期末前端web大作業——用前端語言寫一個小遊戲前端Web遊戲
- [譯] 手把手教你用 Playground 建立 App FrameworkAPPFramework
- 期末大作業關於利用hadoop來統計單詞數目Hadoop
- Android App應用啟動流程(一)AndroidAPP
- 計網期末複習-應用層
- 手把手教你 vue-cli 單頁到多頁應用Vue
- PWA入門:手把手教你製作一個PWA應用
- 手把手教你玩轉HarmonyOS版地圖應用開發地圖
- Android 教你如何發現 APP 卡頓AndroidAPP
- 手把手教你接入華為分析的Android SDKAndroid
- 手把手教你完成數字動態變化顯示效果
- 宜信開源|手把手教你建立第一個Davinci應用
- 從0開始,手把手教你用Vue開發一個答題AppVueAPP
- 手把手教你用Charles抓包
- 手把手教你實現Android編譯期註解Android編譯
- 淺談企業開發APP應用常見的型別有哪些APP型別
- 宜信開源|手把手教你安裝第一個LAIN應用AI
- 手把手教你快應用接入微信H5網頁支付H5網頁
- 手把手教你學Python之基本資料型別Python資料型別
- 小白指南:手把手教你用低程式碼開發一個應用頁面
- 手把手教你用一個簡單的2DCNN完成MNIST數字識別任務!CNN
- 手把手教你實現Android真機遠端截圖Android
- 記錄一次物理專業程式設計大作業完成過程程式設計
- 2024.12.3 期末作業
- iOS--手把手教你一步一步完成搖骰子動畫iOS動畫
- 手把手教你用vue搭建個人站Vue
- 手把手教你用Spuernova生成flutter程式碼Flutter
- 手把手教你在Android專案中接入Flutter,在Flutter中使用Android佈局AndroidFlutter
- 大作業
- 手把手教你將 ChatGPT 接入企業微信ChatGPT
- 手把手教你撰寫本科畢業論文
- 一步一步完成Flutter應用開發-掘金App首頁FlutterAPP
- 【新手指南】Android Studio中應用App的相關配置AndroidAPP
- uni-app&H5&Android混合開發二 || 使用Android Studio打包應用APKAPPH5AndroidAPK
- Android效能優化:手把手教你如何讓App更快、更穩、更省(含記憶體、佈局優化等)Android優化APP記憶體