我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

版本要求

模拟器 API 23及以上

功能

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

实现步骤

一、底部菜单栏切换页

添加依赖

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

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

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

在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引入
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    xmlns:app="http://schemas.android.com/apk/res-auto"    tools:context=".MainActivity">    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginBottom="60dp"        />    <com.google.android.material.bottomnavigation.BottomNavigationView        android:layout_width="match_parent"        android:layout_height="60dp"        android:layout_alignParentBottom="true"        app:labelVisibilityMode="labeled"        app:itemBackground="@null"        app:menu="@menu/bottom_navi_menu"        />RelativeLayout>

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView        android:layout_width="match_parent"        android:layout_height="match_parent"        android:text="Task PAGE"        android:textSize="40dp"        android:gravity="center"        />LinearLayout>
// TaskFragment.javapublic 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;    }}

在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();}

二、天气显示界面

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

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

获取天气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);                }            });        }

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

// class Weatherpublic class Weather {    private String city;//城市名    private String update_time;//更新时间    private List<DayData> data;//每天的天气数据列表,data.get(0)为当天数据    }// class DayDatapublic 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;//空气质量小提示    }

由于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就行    }

别忘了在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);        }    });}

优化xml布局

三、待办事项界面

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

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <TextView        android:id="@+id/taskText"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="事项"/>    <ListView        android:id="@+id/taskListView"        android:layout_width="match_parent"        android:layout_height="match_parent"/>LinearLayout>

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content">    <TextView        android:id="@+id/task_content"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:textColor="@color/black"        android:textSize="30dp"        android:text="TextView" />LinearLayout>

新建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方法}

在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) {}    }}

数据获取正常以后,建立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;}

在onCreateView中设置ListView的适配器

private List<TaskItem> taskList = new ArrayList<>();private TaskAdapter taskAdapter;private ListView taskListView;@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {    View view = inflater.inflate(R.layout.task, container, false);    taskListView = view.findViewById(R.id.taskListView);    taskAdapter = new TaskAdapter();    taskListView.setAdapter(taskAdapter);    ReadTaskDataFromSQL();    return view;}

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

img:task-2.jpg

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

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

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

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

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

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

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

public void ReadTaskFromDatabase(){    if (taskList.size()!=0) {        taskList.clear();    }    Cursor cursor = readDatabase.query(        "task",        new String[]{"id", "type", "level","content", "info", "status"},        null,        null,        null,        null,        null    );    //隐藏,有分类    if(isHideCompleted && !TypeNow.equals("全部")){        //只获取未完成事项        while(cursor.moveToNext()){            if((cursor.getInt(5) == 0 ) && (cursor.getString(1).equals(TypeNow))){                TaskItem task = new TaskItem(                    cursor.getInt(0),                    cursor.getString(1),                    cursor.getInt(2),                    cursor.getString(3),                    cursor.getString(4),                    cursor.getInt(5)                );                taskList.add(task);            }        }    }    //不隐藏,有分类    if(!isHideCompleted && !TypeNow.equals("全部")){        while(cursor.moveToNext()){            if(cursor.getString(1).equals(TypeNow)){                TaskItem task = new TaskItem(                    cursor.getInt(0),                    cursor.getString(1),                    cursor.getInt(2),                    cursor.getString(3),                    cursor.getString(4),                    cursor.getInt(5)                );                taskList.add(task);            }        }    }    //隐藏,不分类    if(isHideCompleted && TypeNow.equals("全部")){        while(cursor.moveToNext()){            if(cursor.getInt(5) == 0){                TaskItem task = new TaskItem(                    cursor.getInt(0),                    cursor.getString(1),                    cursor.getInt(2),                    cursor.getString(3),                    cursor.getString(4),                    cursor.getInt(5)                );                taskList.add(task);            }        }    }    else{        while(cursor.moveToNext()){            TaskItem task = new TaskItem(                cursor.getInt(0),                cursor.getString(1),                cursor.getInt(2),                cursor.getString(3),                cursor.getString(4),                cursor.getInt(5)            );            taskList.add(task);        }    }// 别忘了通知ListView适配器数据变化    taskAdapter.notifyDataSetChanged();}

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

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

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

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

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();        }    });}

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

//添加事项的按钮ImageView addTaskImage = (ImageView) view.findViewById(R.id.addTaskImage);addTaskImage.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        ShowAddTaskDialog();    }});

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

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

四、专注计时界面

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

创建服务类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();            }        }                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();            }        }    }}

在AndroidManifast注册TimeService类

在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);}

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

Intent intent = new Intent();intent.setClass(getActivity(), TimeService.class);getActivity().startService(intent);

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

  • 暂停点击事件: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;    }

创建本地广播,用以接收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);

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

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

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

五、音乐界面

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

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

public class Music {    private String name;    private File file;    // getter and setter }

获取本地音乐文件

由于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);    }}

将音乐名使用ListView列表显示

public class MusicAdapter extends BaseAdapter{        @Override        public int getCount() {            return musicList.size();        }        @Override        public Object getItem(int position) {            return musicList.get(position);        }        @Override        public long getItemId(int position) {            return position;        }        @Override        public View getView(int position, View convertView, ViewGroup parent) {            TextView musicNameText;            Music music = (Music) getItem(position);            if(convertView == null){                convertView = getLayoutInflater().inflate(R.layout.music_item, null);                musicNameText = (TextView) convertView.findViewById(R.id.musicNameText);                convertView.setTag(musicNameText);            }else{                musicNameText = (TextView) convertView.getTag();            }            musicNameText.setText(music.getName());            return convertView;        }    }
musicListView = (ListView) view.findViewById(R.id.musicListView);musicAdapter = new MusicAdapter();musicListView.setAdapter(musicAdapter);

这里我为了方便,播放音乐直接放在了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);

在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();    }}

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

convertView.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        localBinder.getService().servicePlayMusic(music);    }});

在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();        }    }        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();        }    }}

在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);}

在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()@Overridepublic void run() {    super.run();    try {        while(true){            //当pause为true时,调用onPause挂起该线程            TimeUnit.SECONDS.sleep(1);            while(pause) {                onPause();            }            serviceSendProgressBroadcast();        }    } catch (InterruptedException e) {        e.printStackTrace();    }}

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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <ListView        android:id="@+id/musicListView"        android:layout_width="match_parent"        android:layout_height="match_parent"/>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:gravity="center"        android:orientation="horizontal">        <TextView            android:id="@+id/currentText"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textSize="20dp"            android:text="00:00" />        <ProgressBar            android:id="@+id/musicProgressBar"            style="?android:attr/progressBarStyleHorizontal"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_weight="1" />        <TextView            android:id="@+id/durationText"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textSize="20dp"            android:text="00:00" />    LinearLayout>RelativeLayout>
public void RegisterProgressLocalBroadcast(){    IntentFilter intentFilter = new IntentFilter();    intentFilter.addAction("PROGRESS");    BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            int duration = intent.getIntExtra("duration", 0);            int current  = intent.getIntExtra("current", 0);            Log.i(TAG, "onReceive: "+duration+"  "+current);            //在广播接收事件时显示布局            ShowMusicProgress(duration, current);        }    };    LocalBroadcastManager.getInstance(getActivity()).registerReceiver(broadcastReceiver, intentFilter);}public void ShowMusicProgress(int duration, int current){    currentText.setText(""+current);    durationText.setText(""+duration);    progressBar.setMax(duration);    progressBar.setProgress(current);}

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

    public String handleMusicTime(int ms){        int min = (ms/1000) / 60;        int sec = (ms/1000) % 60;        String mm = String.valueOf(min);        String ss = String.valueOf(sec);        if(min<10){            mm = "0"+mm;        }        if(sec<10){            ss = "0"+ss;        }        return mm+":"+ss;    }
    public void ShowMusicProgress(int duration, int current){        currentText.setText(handleMusicTime(current));        durationText.setText(handleMusicTime(duration));        musicSeekBar.setMax(duration);        musicSeekBar.setProgress(current);    }

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

public void setMediaPlayerProgress(int current){    Log.i(TAG, "setMediaPlayerProgress: ");    mediaPlayer.seekTo(current);}

设置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());        }    });}

实现自动播放下一首。将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();    }}

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

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

public void servicePauseMusic(){    if(mediaPlayer != null && mediaPlayer.isPlaying()){        mediaPlayer.pause();    }}public void serviceResumeMusic(){    if(mediaPlayer!=null){        mediaPlayer.start();    }}

给按钮设置点击事件

public void SetPauseResumeImageOnClick(){    musicPauseResumeImage.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            if(localBinder.getService().musicIsPlaying()){                localBinder.getService().servicePauseMusic();                musicPauseResumeImage.setImageResource(R.drawable.resume_time);            }else{                localBinder.getService().serviceResumeMusic();                musicPauseResumeImage.setImageResource(R.drawable.pause_time);            }        }    });}

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

优化布局,完成!

项目源码在这:Daily

来源地址:https://blog.csdn.net/Dae_Lzh/article/details/122082326

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录