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

hfhsdgzsdgsdg發表於2023-02-21

前言

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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章