天空闲话 发表于 2022-6-23 19:20:17

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

前言

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



[*]待办
[*]专注计时
[*]音乐
[*]天气
实现步骤

一、底部菜单栏切换页

1.添加依赖
dependencies {    implementation 'com.google.android.material:material:1.2.1'} 2.在res资源文件夹下新建一个menu文件夹,创建底部导航的菜单布局文件


[*]创建对应数量的item,为每个菜单栏选项
[*]给每个item定义title(标题),icon(图标)
                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引入
       4.依次创建每个页面的Fragment类及布局文件,如Task页面
    // 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;    }} 5.在MainActivity.java中进行设置BottomNavigation选择监听事件对fragment进行管理。
public List fragmentList = new ArrayList();private FragmentManager fragmentManager;// 底部导航栏模块public void InitBottomNavigation() {    // 添加五个fragment实例到fragmentList,以便管理    fragmentList.add(new TaskFragment());    fragmentList.add(new AbsorbedFragment());    fragmentList.add(new MusicFragment());    fragmentList.add(new WeatherFragment());    //建立fragment管理器    fragmentManager = getSupportFragmentManager();    //管理器开启事务,将fragment实例加入管理器    fragmentManager.beginTransaction()      .add(R.id.FragmentLayout, fragmentList.get(0), "TASK")      .add(R.id.FragmentLayout, fragmentList.get(1), "ABSOTBED")      .add(R.id.FragmentLayout, fragmentList.get(2), "MUSIC")      .add(R.id.FragmentLayout, fragmentList.get(3), "WEATHER")      .commit();    //设置fragment显示初始状态    fragmentManager.beginTransaction()      .show(fragmentList.get(1))      .hide(fragmentList.get(0))      .hide(fragmentList.get(2))      .hide(fragmentList.get(3))      .commit();    //设置底部导航栏点击选择监听事件    BottomNavigationView bottomNavigationView = findViewById(R.id.BottomNavigation);    bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {      @SuppressLint("NonConstantResourceId")      @Override      public boolean onNavigationItemSelected(@NonNull MenuItem item) {            // return true : show selected style            // return false: do not show            switch (item.getItemId()) {                case R.id.menu_task:                  ShowFragment(0);                  return true;                case R.id.menu_accounts:                  ShowFragment(1);                  return true;                case R.id.menu_absorbed:                  ShowFragment(2);                  return true;                case R.id.menu_weather:                  ShowFragment(3);                  return true;                default:                  Log.i(TAG, "onNavigationItemSelected: Error");                  break;            }            return false;      }    });}public void ShowFragment(int index) {    fragmentManager.beginTransaction()      .show(fragmentList.get(index))      .hide(fragmentList.get((index + 1) % 4))      .hide(fragmentList.get((index + 2) % 4))      .hide(fragmentList.get((index + 3) % 4))      .commit();} 二、天气显示界面

1、添加依赖(用于获取和解析天气数据)
    implementation 'com.google.code.gson:gson:2.8.6'    implementation 'com.squareup.okhttp3:okhttp:4.9.0' 2、获取天气API接口,这里以临海市为例。使用OkHttp请求天气数据,使用Log打印测试是否能成功获取
public void RefreshWeatherData() {            OkHttpClient client = new OkHttpClient();            Request request = new Request.Builder().url(weatherUrl).build();            client.newCall(request).enqueue(new Callback() {                @Override                public void onFailure(@NonNull Call call, @NonNull IOException e) {                  e.printStackTrace();                }                @Override                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {                  String weatherJson = response.body().string();                  Weather weather = new Gson().fromJson(weatherJson, Weather.class);                  Log.i(TAG, "onResponse: "+weatherJson);                }            });      } 3、Json数据获取成功后,根据Json数据的结构建立Weather类用于解析Json数据。
// class Weatherpublic class Weather {    private String city;                //城市名    private String update_time;        //更新时间    private List data;        //每天的天气数据列表,data.get(0)为当天数据    /*           getter and setter   */}// 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;        //空气质量小提示    /*           getter and setter   */} 4、由于OkHttp的请求是在子线程中进行的,需要使用Handler消息队列机制将解析出来的Weather实例发送到主线程用以显示在界面上。
//消息处理类public class MyHandler extends Handler {    @Override    public void handleMessage(@NonNull Message msg) {      super.handleMessage(msg);      //what == 1   天气消息      if (msg.what == 1)            ShowWeatherInfo((Weather) msg.obj);    }}public void ShowWeatherInfo(Weather weather) {    String city = weather.getCity();    String wea = weather.getData().get(0).getWea();    String maxTem = weather.getData().get(0).getTem1();    String minTem = weather.getData().get(0).getTem2();    String tem = weather.getData().get(0).getTem();    String humidity = "湿度         " + weather.getData().get(0).getHumidity();    String air_level = "空气指数   " + weather.getData().get(0).getAir_level();    // temtem1tem2citywearainpmimage    ((TextView) findViewById(R.id.cityView)).setText(city);    ((TextView) findViewById(R.id.weaView)).setText(wea);    ((TextView) findViewById(R.id.mmtemView)).setText(      String.format("%s° / %s°", minTem.substring(0, minTem.length() - 1), maxTem.substring(0, maxTem.length() - 1)));    ((TextView) findViewById(R.id.temView)).setText(tem.substring(0, tem.length() - 1) + "°");    ((TextView) findViewById(R.id.humidityView)).setText(humidity);    ((TextView) findViewById(R.id.levelView)).setText(air_level);      ShowWeatherImage(wea);        //根据天气状况wea显示对应的天气图片,这里不详细说明,使用switch就行    } 5、别忘了在OkHttp请求完成时发送消息
public void RefreshWeatherData() {    OkHttpClient client = new OkHttpClient();    Request request = new Request.Builder().url(weatherUrl).build();    client.newCall(request).enqueue(new Callback() {      @Override      public void onFailure(@NonNull Call call, @NonNull IOException e) {            e.printStackTrace();      }      @Override      public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {            String weatherJson = response.body().string();            Weather weather = new Gson().fromJson(weatherJson, Weather.class);            Message message = new Message();            message.what = 1;            message.obj = weather;            myHandler.sendMessage(message);      }    });} 6、优化xml布局
三、待办事项界面

这里由于ListView是放在Fragment中的,所以直接在MainAcitivity.java中设置适配器可能会出现数据没法显示的bug。所以我直接把从数据库获取数据,Adapter的定义,ListView设置适配器的模块搬到了TaskFragment.java中。
1.在task.xml中添加ListView,先不用设置UI样式,先把数据拿到并显示在界面上
       2.创建task_item.xml布局文件(这里注意线性布局的方向及宽高,以保证task_item能放在ListView中)
    3.新建TaskItem类,存放事项数据
package com.example.daily.tasks;public class TaskItem {    private int id;    private String content;    private String type;    private int status;    public TaskItem(int id, String type, String content, int status){      this.id = id;      this.type = type;      this.content = content;      this.status = status;    }    // 自行添加Get和Set方法} 4.在TaskFragment.java中创建SQLite数据库并获取待办事项的数据
public class TaskFragment extends Fragment {    private static final String TAG = TaskFragment.class.getName();    private List taskList = new ArrayList();    @Nullable    @Override    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {      View view = inflater.inflate(R.layout.task, container, false);                ReadTaskDataFromSQL();          //测试数据获取是否正常      for(TaskItem item : taskList){            Log.i(TAG, "taskList "+item.getId()+" "+item.getContent());      }                   return view;    }    //读取数据库并将数据存到taskList    public void ReadTaskDataFromSQL(){      MySQLiteOpenHelper openHelper = new MySQLiteOpenHelper(getActivity());      SQLiteDatabase readDatabase = openHelper.getReadableDatabase();                Cursor cursor = readDatabase.query(                "task",                    new String[]{"id", "type", "content", "status"},                    null,null,null,null,null      );                while(cursor.moveToNext()){            TaskItem task = new TaskItem(                  cursor.getInt(0),                     cursor.getString(1),                     cursor.getString(2),                     cursor.getInt(3)            );            taskList.add(task);      }            }      //创建SQLite数据库    public class MySQLiteOpenHelper extends SQLiteOpenHelper{      public MySQLiteOpenHelper(@Nullable Context context) {            super(context, "Daily.db", null, 1);      }      @Override      public void onCreate(SQLiteDatabase db) {            Log.i(TAG, "onCreate: sqlite");            //创建待办事项数据表            String create_sql =                  "create table task(" +                            "id INTEGER PRIMARY KEY AUTOINCREMENT, " +                            "content varchar(50), " +                            "type varchar(50), " +                            "status int);";            db.execSQL(create_sql);      }                @Override      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}    }} 5.数据获取正常以后,建立ListView适配器。这里涉及到缓存convertView的使用,使用convertView可以防止每创建一个item时就解析一个布局,这样效率肯定不高。convertView是Android提供的用于缓存的View,在第一次渲染item时,将将解析出来的View放入缓存convertView,在下一次渲染item的时候,判断convertView是否为空即可。
public class TaskAdapter extends BaseAdapter{    @Override    public int getCount() {      //测试getCount返回值是否正常      Log.i(TAG, "getCount: "+taskList.size());      return taskList.size();    }    @Override    public Object getItem(int position) {      return taskList.get(position);    }    @Override    public long getItemId(int position) {      return taskList.get(position).getId();    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {      //测试getView是否执行      Log.i(TAG, "getView: "+position);      ViewHolder viewHolder;      TaskItem task = (TaskItem) getItem(position);      if(convertView == null){            viewHolder = new ViewHolder();            convertView = LayoutInflater.from(getActivity()).inflate(R.layout.task_item, null);            viewHolder.taskItemTextView = convertView.findViewById(R.id.task_content);            convertView.setTag(viewHolder);      }else{            viewHolder = (ViewHolder) convertView.getTag();      }      viewHolder.taskItemTextView.setText(task.getId()+""+task.getContent());      return convertView;    }}public class ViewHolder{    TextView taskItemTextView;} 6.在onCreateView中设置ListView的适配器
private List 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 {            // 点击分类的一项后设置样式            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 typeTaskList = new ArrayList();            String[] types = {"全部", "工作","学习","生活"};            /*分类索引值                0 全部                1 工作                2 学习                3 生活               */            // 点击工作 学习 生活时分类            // TypeNow 是一个全局变量,表示当前的分类            TypeNow = types;            Log.i(TAG, "SetTypeMenuOnClick: "+TypeNow);            ReadTaskFromDatabase();      });    }} 10.task.xml布局右上角加入一个switch控件用以隐藏已完成事项。
//隐藏已完成Switch      Switch hideCompletedTaskSwitch = view.findViewById(R.id.HideCompletedTaskView);      hideCompletedTaskSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {            @Override            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {                if(isChecked)   isHideCompleted = true;                else            isHideCompleted = false;                              // isHideCompleted 是一个全局变量,表示当前是否隐藏已完成事项                ReadTaskFromDatabase();            }      }); 完成9,10步之后就需要修改读取数据库的模块,加入TypeNow和isHideCompleted变量加以控制。
public void ReadTaskFromDatabase(){    if (taskList.size()!=0) {      taskList.clear();    }    Cursor cursor = readDatabase.query(      "task",      new String[]{"id", "type", "level","content", "info", "status"},      null,      null,      null,      null,      null    );    //隐藏,有分类    if(isHideCompleted && !TypeNow.equals("全部")){      //只获取未完成事项      while(cursor.moveToNext()){            if((cursor.getInt(5) == 0 ) && (cursor.getString(1).equals(TypeNow))){                TaskItem task = new TaskItem(                  cursor.getInt(0),                  cursor.getString(1),                  cursor.getInt(2),                  cursor.getString(3),                  cursor.getString(4),                  cursor.getInt(5)                );                taskList.add(task);            }      }    }    //不隐藏,有分类    if(!isHideCompleted && !TypeNow.equals("全部")){      while(cursor.moveToNext()){            if(cursor.getString(1).equals(TypeNow)){                TaskItem task = new TaskItem(                  cursor.getInt(0),                  cursor.getString(1),                  cursor.getInt(2),                  cursor.getString(3),                  cursor.getString(4),                  cursor.getInt(5)                );                taskList.add(task);            }      }    }    //隐藏,不分类    if(isHideCompleted && TypeNow.equals("全部")){      while(cursor.moveToNext()){            if(cursor.getInt(5) == 0){                TaskItem task = new TaskItem(                  cursor.getInt(0),                  cursor.getString(1),                  cursor.getInt(2),                  cursor.getString(3),                  cursor.getString(4),                  cursor.getInt(5)                );                taskList.add(task);            }      }    }    else{      while(cursor.moveToNext()){            TaskItem task = new TaskItem(                cursor.getInt(0),                cursor.getString(1),                cursor.getInt(2),                cursor.getString(3),                cursor.getString(4),                cursor.getInt(5)            );            taskList.add(task);      }    }        // 别忘了通知ListView适配器数据变化    taskAdapter.notifyDataSetChanged();} 11、添加事项,这里使用的是在整个RelativeLayout布局中添加一个ImageView作为添加事项的按钮,并定义点击事件,点击时弹出对话框,在对话框中输入添加事项的信息。
自定义对话框需要先设计一个layout布局文件add_task_dialog.xml
                                                                                                                                                                                                                                                                                                                            12、定义一个方法,实现弹出添加事项界面的对话框,并设置确认和取消按钮的点击事件,确认按钮即添加该事项到数据库并显示
public void ShowAddTaskDialog(){    //获取添加事项布局实例    View addView = getLayoutInflater().inflate(R.layout.add_task_dialog, null);    // 将该布局添加到对话框    final AlertDialog addDialog = new                                                                                                                                 AlertDialog.Builder(getActivity()).setView(addView).create();    addDialog.show();    //获取对话框中的布局控件    Button cancelButton = (Button) addView.findViewById(R.id.cancelAddButton);    Button confirmButton = (Button) addView.findViewById(R.id.confirmAddButton);    EditText contentEdit = (EditText) addView.findViewById(R.id.addTaskContentEdit);    EditText infoEdit = (EditText) addView.findViewById(R.id.addTaskInfoEdit);    RadioGroup typeGroup = (RadioGroup) addView.findViewById(R.id.typeRadioGroup);    RadioGroup levelGroup = (RadioGroup) addView.findViewById(R.id.levelRadioGroup);    typeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {      @Override      public void onCheckedChanged(RadioGroup group, int checkedId) {      }    });    levelGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {      @Override      public void onCheckedChanged(RadioGroup group, int checkedId) {      }    });    //确定按钮    confirmButton.setOnClickListener(new View.OnClickListener() {      @Override      public void onClick(View v) {            // 获取输入的事项内容和备注            String addContent = contentEdit.getText().toString();            String addInfo = infoEdit.getText().toString();            //RadioGroup的选择项            RadioButton typeSelectBtn = (RadioButton)                                                                                       addView.findViewById(typeGroup.getCheckedRadioButtonId());            String addType = typeSelectBtn.getText().toString();            RadioButton levelSelectBtn = (RadioButton)                                                                                                         addView.findViewById(levelGroup.getCheckedRadioButtonId());            int addLevel =                                                                                                                                                                 Integer.parseInt(levelSelectBtn.getText().toString().substring(0,1));            //插入数据库            InsertTaskToDatabase(                new TaskItem(addType, addLevel, addContent, addInfo, 0)            );            addDialog.dismiss();      }    });    // 取消按钮    cancelButton.setOnClickListener(new View.OnClickListener() {      @Override      public void onClick(View v) {            addDialog.dismiss();      }    });} 13、然后在添加事项的点击事件中调用ShowAddTaskDialog()即可
//添加事项的按钮ImageView addTaskImage = (ImageView) view.findViewById(R.id.addTaskImage);addTaskImage.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {      ShowAddTaskDialog();    }}); 14.长按某条事项弹出对话框,显示事项信息,可以修改,删除,标记失败。和添加事项的对话框实现原理相同,这里不详细说明,给出代码供参考
                                                                                                                                                                                                                                                                                                                                                                                                                                     public void ShowTaskInfoDialog(TaskItem task){      // 获取传入的事项数据      String content = task.getContent();      String type = task.getType();      int level = task.getLevel();      String info = task.getInfo();      //获取布局      View infoView = getLayoutInflater().inflate(R.layout.task_info_dialog, null);      final AlertDialog infoDialog = new AlertDialog.Builder(getActivity()).setView(infoView).create();      infoDialog.show();      //获取对话框中的布局控件      EditText contentEdit = (EditText) infoView.findViewById(R.id.addTaskContentEdit);      EditText infoEdit = (EditText) infoView.findViewById(R.id.addTaskInfoEdit);      RadioGroup typeGroup = (RadioGroup) infoView.findViewById(R.id.typeRadioGroup);      RadioGroup levelGroup = (RadioGroup) infoView.findViewById(R.id.levelRadioGroup);      ImageView deleteImage = (ImageView) infoView.findViewById(R.id.deleteTaskButton);      ImageView modifyImage = (ImageView) infoView.findViewById(R.id.modifyTaskButton);      ImageView failImage = (ImageView) infoView.findViewById(R.id.failTaskButton);      //显示task事项信息      contentEdit.setText(content);      infoEdit.setText(info);      SetTypeRadioGroupSelected(typeGroup, type);      SetLevelRadioGroupSelected(levelGroup, level);      //删除按钮      deleteImage.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                DeleteTaskToDatabase(task);                infoDialog.dismiss();            }      });      //失败按钮      failImage.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                task.setStatus(-1);                UpDateTaskToDatabase(task);                //别忘记关闭对话框                infoDialog.dismiss();            }      });      //修改按钮      modifyImage.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                // 获取输入的事项内容和备注                String modifyContent = contentEdit.getText().toString();                String modifyInfo = infoEdit.getText().toString();                //RadioGroup的选择项                RadioButton typeSelectBtn = (RadioButton) infoView.findViewById(typeGroup.getCheckedRadioButtonId());                String modifyType = typeSelectBtn.getText().toString();                RadioButton levelSelectBtn = (RadioButton) infoView.findViewById(levelGroup.getCheckedRadioButtonId());                int modifyLevel = Integer.parseInt(levelSelectBtn.getText().toString().substring(0,1));                task.setContent(modifyContent);                task.setInfo(modifyInfo);                task.setType(modifyType);                task.setLevel(modifyLevel);                UpDateTaskToDatabase(task);                //别忘记关闭对话框                infoDialog.dismiss();            }      });    } //在适配器的getView中,设置每条事项的长按事件:调用ShowTaskInfoDialog弹出对话框显示事项的内容convertView.setOnLongClickListener(new View.OnLongClickListener() {    @Override    public boolean onLongClick(View v) {      ShowTaskInfoDialog(task);      return false;    }}); 四、专注计时界面

计时的原理是使用Android四大组件之一的Service开启计时线程,并每隔一秒钟发送一次本地广播通知主界面更新布局。
1、创建服务类TimeService,继承自Service。这里在Service类里面定义了一个TimeThread自定义线程类,用以方便线程的挂起和恢复。
public class TimeService extends Service {    private static final String TAG = TimeService.class.getName();    //计时秒数    private int second = 0;    public int getSecond() {      return second;    }    public void setSecond(int second) {      this.second = second;    }    @Nullable    @Override    public IBinder onBind(Intent intent) {      return new LocalBinder();    }    @Override    public void onCreate() {      Log.i(TAG, "TimeService onCreate: ");      super.onCreate();    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {      Log.i(TAG, "TimeService onStartCommand: ");      //创建计时线程实例      timeThread = new TimeThread();      timeThread.start();      isRunning = true;      return super.onStartCommand(intent, flags, startId);    }    @Override    public void onDestroy() {      Log.i(TAG, "TimeService onDestroy: ");      super.onDestroy();    }    @Override    public boolean onUnbind(Intent intent) {      Log.i(TAG, "TimeService onUnbind: ");      return super.onUnbind(intent);    }    //用于返回本地服务    public class LocalBinder extends Binder{      public TimeService getService(){            return TimeService.this;      }    }      public class TimeThread extends Thread{      private final Object lock = new Object();      private boolean pause = false;      /**         * 调用该方法实现线程的暂停         */      void pauseThread(){            Log.i(TAG, "pauseTimeThread: ");            pause = true;      }      /*      调用该方法实现恢复线程的运行         */      void resumeThread(){            Log.i(TAG, "resumeTimeThread: ");            pause = false;            synchronized (lock){                lock.notify();            }      }      /**         * 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应         */      void onPause() {            synchronized (lock) {                try {                  lock.wait();                } catch (InterruptedException e) {                  e.printStackTrace();                }            }      }      @Override      public void run() {            super.run();            try {                while(true){                  //当pause为true时,调用onPause挂起该线程                  TimeUnit.SECONDS.sleep(1);                  while(pause) {                        onPause();                  }                  second++;                  SendSecondBroadcast();                  Log.i(TAG, "run: "+second);                }            } catch (InterruptedException e) {                e.printStackTrace();            }      }    }} 2、在AndroidManifast注册TimeService类
3、在AbsorbedFragment中绑定服务,运行测试service是否连接成功
public void BindTimeService(){    Intent intent = new Intent(getActivity(), TimeService.class);    ServiceConnection connection = new ServiceConnection() {      @Override      public void onServiceConnected(ComponentName name, IBinder service) {            localBinder = (TimeService.LocalBinder) service;            if(localBinder.getService() != null){                Log.i(TAG, "onServiceConnected: time service connected");            }      }      @Override      public void onServiceDisconnected(ComponentName name) {            Log.i(TAG, "onServiceDisconnected: ");      }    };    getActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE);} 4、给开始计时按钮添加点击事件,运行测试TimeThread是否每隔一秒打印一次
Intent intent = new Intent();intent.setClass(getActivity(), TimeService.class);getActivity().startService(intent); 5、运行成功后,添加暂停,继续,取消按钮,运行测试观察打印信息是否正常


[*]暂停点击事件:localBinder.getService().PauseTime();
[*]继续点击事件:localBinder.getService().ResumeTime();
[*]取消点击事件:localBinder.getService().CancelTime();
    //TimeService中用于在MainActivity调用的方法    public void PauseTime(){      timeThread.pauseThread();      isRunning = false;    }    public void ResumeTime(){      timeThread.resumeThread();      isRunning = true;    }    public void CancelTime(){      timeThread.pauseThread();      second = 0;    } 6、创建本地广播,用以接收TimeThread发送的秒数,并更新布局界面
//注册接收计时秒数的本地广播IntentFilter timeIntentFilter = new IntentFilter();timeIntentFilter.addAction("SECONDS_CHANGED");BroadcastReceiver timeBroadcastReceiver = new BroadcastReceiver() {    @Override    public void onReceive(Context context, Intent intent) {      int second = localBinder.getService().getSecond();      ShowTimeSecond(second);    }};LocalBroadcastManager.getInstance(getActivity())    .registerReceiver(timeBroadcastReceiver, timeIntentFilter); 7、在TimeThread的run方法中每一秒发送一次本地广播,运行测试是否正常
@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();    }} 8、显示专注计时的记录,使用SQLite数据库实现,和待办事项界面一样,添加完成专注计时的按钮,点击事件为添加计时信息的字符串到数据库。
五、音乐界面

实现原理,使用Service组件和MediaPlayer。点击音乐列表的某条音乐时,在服务中开启MediaPlayer播放音乐,并每隔一秒种发送一次本地广播(内容为当前已播放的秒数),设置界面中的进度条。并给进度条设置拖动的事件,将对应的播放进度传给MediaPlayer跳转至对应的进度。
1、定义Music类,包含音乐名,文件
public class Music {    private String name;    private File file;    // getter and setter } 2、 获取本地音乐文件
由于API 29以后getExternalStorageDirectory()被废弃,所以直接采用指定的路径获取MP3音乐文件。
public void ShowMusicList(){    File musicStorage = new File("/storage/11E9-360F/Music");    File[] musicFiles = musicStorage.listFiles(new FilenameFilter(){      @Override      public boolean accept(File dir, String name) {            return name.endsWith(".mp3");      }    });    for(int i=0; i
页: [1]
查看完整版本: 手把手教你完成Android期末大作业(多功能应用型APP)