Android第四次作业
一、团队成员
姓名:李凯 学号:1600509018 班级:计算机164班 博客链接地址:https://www.cnblogs.com/JusperLee/
姓名:季轩石 学号:1600802115 班级:计算机164班 博客链接地址:http://www.cnblogs.com/midnightclad/
二、团队项目APK链接
Time APK地址:https://github.com/JusperLee/Time/blob/master/APK/Time.apk
三、团队项目代码
GitHub链接网址:https://github.com/JusperLee/Time
四、团队项目介绍
4.1 团队项目总体效果截图
App启动页
App的三个页面按钮对应的页面
添加定量计划页面
打卡计划页面展示
提醒事项页面展示
主题目前仅仅有白色和黑色
时间添加控件展示
4.2 实现的功能及其效果描述
4.2.1 添加计划功能展示
首先我们点击添加计划按钮,然后根据自己的需求我们有两个选项可以选择,一个是定量计划,一个是打卡计划,下面我们展示一下定量计划。我们先填写好自己计划的信息点击确定就可以在主页面上看到自己填写的内容了。由于截图较多,换了一种方式展示使用操作gif来展示。
4.2.2 打卡计划展示
打卡计划也是通过自定义填写内容可以直接生成一个打卡计划的卡片,然后每天可以通过该卡片生成一个打卡内容。下面时演示的gif动图。
4.2.3 添加提醒事项功能展示
首先我们选中下面的菜单栏的第二个内容,然后点击添加TODO,输入你要提醒的事项,然后选择提醒时间,就可以添加提醒了。下面是我们的gif动态演示。
4.2.4 主题的更改功能展示
我们通过更改按钮进行主题更改,可以更改为白天和黑夜,下面通过gif来展示我们的主题更改功能。
五、项目核心代码展示
5.1 添加计划代码
package com.lj.Time.page.plan; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.Toolbar; import com.lj.Time.R; import com.lj.Time.common.CustomFragmentPagerAdapter; import com.lj.Time.event.PlanSelectedEvent; import com.lj.Time.page.base.BaseActivity; import com.lj.Time.widget.NoScrollViewPager; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; /** * 添加计划界面 */ public class AddPlanActivity extends BaseActivity { @BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.view_pager) NoScrollViewPager viewPager; @Override protected int getLayoutResId() { return R.layout.activity_add_plan; } @Override protected void initView(@Nullable Bundle savedInstanceState) { ButterKnife.bind(this); EventBus.getDefault().register(this); initToolbar(toolbar, "添加计划", true); List<Fragment> fragmentList = new ArrayList<>(); fragmentList.add(new ChoosePlanTypeFragment()); fragmentList.add(new AddPlanFragment()); CustomFragmentPagerAdapter fragmentPagerAdapter = new CustomFragmentPagerAdapter(getSupportFragmentManager(), fragmentList); viewPager.setAdapter(fragmentPagerAdapter); } @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(PlanSelectedEvent event) { if(event.getPlanType() == -1) { viewPager.setCurrentItem(0); }else{ viewPager.setCurrentItem(1); } } @Override public void onBackPressed() { if(viewPager.getCurrentItem() == 1){ EventBus.getDefault().post(new PlanSelectedEvent(-1)); }else{ super.onBackPressed(); } } @Override protected void onDestroy() { EventBus.getDefault().unregister(this); super.onDestroy(); } }
5.2 修改计划功能
package com.lj.Time.presenter.plan; import android.app.Activity; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import com.lj.Time.contract.plan.IEditPlanContract; import com.lj.Time.db.ClockPlan; import com.lj.Time.db.ClockPlanDao; import com.lj.Time.db.DBManager; import com.lj.Time.db.RationPlan; import com.lj.Time.db.RationPlanDao; import com.lj.Time.event.PlanChangedEvent; import com.lj.Time.model.plan.EditPlanDataEntity; import com.lj.Time.model.plan.ShowPlanEntity; import com.lj.Time.util.DateUtils; import org.greenrobot.eventbus.EventBus; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import io.reactivex.Observer; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; /** * 修改计划 */ public class EditPlanPresenterImpl implements IEditPlanContract.Presenter { private Context context; private IEditPlanContract.View view; private long planId; /** * 0-定量计划 * 1-打卡计划 */ private int planType; private Handler mHandler = new Handler(Looper.getMainLooper()); private RationPlanDao mRationPlanDao; private ClockPlanDao mClockPlanDao; private RationPlan rationPlan; private ClockPlan clockPlan; public EditPlanPresenterImpl(Context context, IEditPlanContract.View view) { this.context = context; this.view = view; mRationPlanDao = DBManager.getInstance().getRationPlanDao(); mClockPlanDao = DBManager.getInstance().getClockPlanDao(); } @Override public void initDate(long planId, int planType) { this.planId = planId; this.planType = planType; view.showRoundProgressDialog(); if (planType == 0) { view.showRationPlan(); getRationPlanWithId(); } else { view.showClockPlan(); getClockPlanWithId(); } } private void getRationPlanWithId() { Observable.create((ObservableEmitter<Integer> e) -> { List<RationPlan> rationPlanList = mRationPlanDao.queryBuilder() .where(RationPlanDao.Properties.Id.eq(planId)) .list(); if (rationPlanList.isEmpty()) { e.onNext(0); } else { rationPlan = rationPlanList.get(0); e.onNext(1); } e.onComplete(); }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe((Integer value) -> { if (value == 0) { planDoesNotExits(); } else { mHandler.postDelayed(() -> view.fillRationPlanData(rationPlan), 500); } }); } private void getClockPlanWithId() { Observable.create((ObservableEmitter<Integer> e) -> { List<ClockPlan> clockPlanList = mClockPlanDao.queryBuilder() .where(ClockPlanDao.Properties.Id.eq(planId)) .list(); if (clockPlanList.isEmpty()) { e.onNext(0); } else { clockPlan = clockPlanList.get(0); e.onNext(1); } e.onComplete(); }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe((Integer value) -> { if (value == 0) { planDoesNotExits(); } else { mHandler.postDelayed(() -> view.fillClockPlanData(clockPlan), 500); } }); } private void planDoesNotExits() { view.showNoActionSnackbar("计划不存在啊?怎么回事"); mHandler.postDelayed(() -> ((Activity) view).finish(), 700); } @Override public void updatePlan(final EditPlanDataEntity editData) { if (planType == 0) { if (DateUtils.compareDate("yyyy-MM-dd", rationPlan.getStartDate(), editData.getFinishDate()) != -1) { view.showNoActionSnackbar("结束时间不能小于开始时间!!!"); return; } Observable.create((ObservableEmitter<Integer> e) -> { rationPlan.setName(editData.getName()); rationPlan.setTarget(editData.getTarget()); rationPlan.setCurrent(editData.getCurrent()); rationPlan.setFinishDate(editData.getFinishDate()); rationPlan.setUnit(editData.getUnit()); rationPlan.setPeriodIsOpen(editData.isPeriodIsOpen()); if(editData.isPeriodIsOpen()){ rationPlan.setPeriodPlanTarget(editData.getPeriodTarget()); rationPlan.setPeriodPlanType(editData.getPeriodPlanType()); } mRationPlanDao.insertOrReplace(rationPlan); DBManager.getInstance().clear(); e.onNext(0); e.onComplete(); }).subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(integer -> editSuccess()); } else { Observable.create((ObservableEmitter<Integer> e) -> { clockPlan.setName(editData.getName()); if (!TextUtils.isEmpty(editData.getDescription())) { clockPlan.setDescription(editData.getDescription()); } mClockPlanDao.insertOrReplace(clockPlan); DBManager.getInstance().clear(); e.onNext(0); e.onComplete(); }).subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(integer -> editSuccess()); } } @Override public void deletePlan() { if (planType == 0) { mRationPlanDao.deleteByKey(planId); } else { mClockPlanDao.deleteByKey(planId); } view.showNoActionSnackbar("搞定"); DBManager.getInstance().clear(); EventBus.getDefault().post(new PlanChangedEvent()); mHandler.postDelayed(() -> ((Activity) context).finish(), 700); } @Override public void deletePeriod() { if (rationPlan != null) { rationPlan.setPeriodIsOpen(false); mRationPlanDao.insertOrReplace(rationPlan); DBManager.getInstance().clear(); initDate(planId, planType); } } private void editSuccess() { view.showNoActionSnackbar("修改好了"); EventBus.getDefault().post(new PlanChangedEvent()); mHandler.postDelayed(() -> ((Activity) context).finish(), 700); } @Override public void onDestroy() { mRationPlanDao = null; mClockPlanDao = null; } }
5.3 提醒计划已完成功能代码
package com.lj.Time.presenter.todo; import com.lj.Time.contract.todo.ICompletedTodoContract; import com.lj.Time.db.DBManager; import com.lj.Time.db.Todo; import com.lj.Time.db.TodoDao; import com.lj.Time.model.todo.ShowTodoEntity; import java.util.ArrayList; import java.util.List; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; /** * 已完成的待办事项 */ public class CompletedTodoPresenterImpl implements ICompletedTodoContract.Presenter { private ICompletedTodoContract.View view; private TodoDao mTodoDao; private Observable<Integer> updateTodoObservable; private List<ShowTodoEntity> listData = new ArrayList<>(); public CompletedTodoPresenterImpl(ICompletedTodoContract.View view) { this.view = view; mTodoDao = DBManager.getInstance().getTodoDao(); updateTodoObservable = Observable.create(e -> { listData.clear(); List<Todo> todoList = mTodoDao.queryBuilder() .where(TodoDao.Properties.Completed.eq(true)) .orderAsc(TodoDao.Properties.Date) .list(); if (!todoList.isEmpty()) { listData.addAll(Observable.fromIterable(todoList) .map(todo -> { ShowTodoEntity showTodo = new ShowTodoEntity(); showTodo.setType(0); showTodo.setTodo(todo); return showTodo; }) .toList() .blockingGet()); } e.onNext(0); e.onComplete(); }); } @Override public void update() { updateTodoObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(integer -> view.notifyTodoChanged(listData)); } }
5.4 提醒计划展示实现代码
package com.lj.Time.presenter.todo; import android.content.Context; import com.lj.Time.contract.todo.IShowTodoContract; import com.lj.Time.db.DBManager; import com.lj.Time.db.Todo; import com.lj.Time.db.TodoDao; import com.lj.Time.model.todo.ShowTodoEntity; import com.lj.Time.util.DateUtils; import java.util.ArrayList; import java.util.List; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; /** * 待办事项展示 */ public class ShowTodoPresenterImpl implements IShowTodoContract.Presenter { private Context context; private IShowTodoContract.View view; private TodoDao mTodoDao; private Observable<Integer> updateTodoObservable; private String dateFormat = "yyyy-MM-dd HH:mm"; private String currentDate = DateUtils.getCurrentDate(dateFormat); private List<ShowTodoEntity> listData = new ArrayList<>(); public ShowTodoPresenterImpl(Context context, IShowTodoContract.View view) { this.context = context; this.view = view; mTodoDao = DBManager.getInstance().getTodoDao(); updateTodoObservable = Observable.create(e -> { listData.clear(); List<Todo> todoList = mTodoDao.queryBuilder() .where(TodoDao.Properties.Completed.eq(false)) .orderAsc(TodoDao.Properties.Date) .list(); if (!todoList.isEmpty()) { listData.addAll(Observable.fromIterable(todoList) .map(todo -> { ShowTodoEntity showTodo = new ShowTodoEntity(); showTodo.setType(0); showTodo.setTodo(todo); showTodo.setLevel(getLevel(todo)); return showTodo; }) .toList() .blockingGet()); } ShowTodoEntity addTodoEntity = new ShowTodoEntity(); addTodoEntity.setType(1); listData.add(addTodoEntity); e.onNext(0); e.onComplete(); }); } /** * 获取待办事项紧急等级 */ private int getLevel(Todo todo) { int level = 1; int hourSpace = DateUtils.getHourSpace(dateFormat, currentDate, todo.getDate()); if (hourSpace <= 1) { level = 6; } else if (hourSpace <= 5) { level = 5; } else if (hourSpace <= 12) { level = 4; } else if (hourSpace <= 24) { level = 3; } else if (hourSpace <= 48) { level = 2; } return level; } @Override public void update() { updateTodoObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(integer -> view.notifyTodoChanged(listData)); } }
六、运行其他团队项目的apk,并进行评论
功能简约但实用的记单词工具
优点:朴实,功能合理而强大,打卡记录很实用,贴近学生生活
缺点:拓展性不足,页面美观度还有提升空间
改进方向:美化页面,添加联网查询功能,完善词汇库,攻略内容可设置接口,从数据库或其他地方动态获取,添加更多辅助背单词的功能,而不仅仅是一个单词查询工具。
第四名:马秀莲组的快递查询应用
满足在校学生查取快递的需要、能反馈意见、和其他同学进行交流。是比较实用的应用。
优点:功能齐全,实用性高,贴近学生生活
缺点:界面不够美观,页面逻辑还有改进空间
改进方向:重新设计页面逻辑,美化页面,添加更多的用户之间的互动,完善用户类型,让快递站也能有账号,方便交流。
第三名:李钊组的大话乒乓
十分专业的介绍了乒乓球这一比赛的方方面面,如球员和比赛,还有视频可以查看,很用心。
优点:专业,详细,功能丰富
缺点:有些部分还是有bug,页面不够美观,内容过于单一,只介绍了乒乓球
改进方向:美化页面,重新设计页面逻辑,增添其他体育运动的版块,添加与用户的互动,如收藏点赞之类的
第二名:洪居兴组的旅游app
功能十分丰富完善的旅游相关的应用。实现了景区、酒店、美食、路线的查询,个人信息的修改等
优点:功能丰富,信息完善,灵活运用了课堂上学过的知识
缺点:每次打开时就要重新登录一次,界面配色不好看,处理个人信息外的数据仅供展示,没有更改的接口
改进方向:美化页面,添加联网查询功能,实时更新数据
第一名:盖星辰组的神经质大逃亡
有趣的游戏,正常可玩且在我的测试中无bug
优点:操作简便易上手,素材生动,玩法完善,功能齐全
缺点:玩法单一,重复游玩度不高,UI不够神经质
改进方向:改进UI与一部分素材,更贴切游戏气质,改善手感,现在的操作还是略显僵硬,添加道具和随机效果来增添游戏过程中的变数,添加更丰富的玩法,如新地图
七、项目遇到的问题
李凯,1600509018,在做项目的过程中遇到了很多的问题:
(1)由于我想到对我们的这个项目使用greenrobot的一个开源项目EventBus,来对项目提供高效的发布和订阅事件,来对不同线程之间传递数据。我在引入GrennDao时出现了,项目混淆日志报错,Could not init DAOConfig。 通过在国外论坛发现了给问题的解决方法:在proguard-rules.pro中加入
-keepclassmembers class * extends de.greenrobot.dao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
(2)同时在将项目进行运行的时候,在虚拟机安装app的时候报了一个错误:Android Studio配置androidannotations 出现 Error:Execution failed for task ':app:compileDebugJavaWithJavac'。我的问题的解决方式是通过更新 BuildTools,然后clean项目后重新进行Build。问题就解决了。
(3)同时在安装apk的时候也出现了一个这个错误。error Unknown failure (at android.os.Binder.execTransact(Binder.java:565)) Error while Installing APKs。通过Stackoverflow查询网友们的评论,我也找到了解决方案。这是由于 我在移动项目的时候没有重新生成项目,当我移动项目文件夹时,就我而言,Build-> Clean Project解决了这个问题。
还有其他的一些问题,是由于我的代码出现的问题我认为那种代码出现的问题只属于个人问题,也容易解决,通过查看log就可以解决这些问题,而我在文章中提出的这些问题都是比较棘手的,给大家提供个解决方案用来参考。
季轩石 1600802115 遇到的问题:
解决方法:最后也没有解决,使用自己的手机安装
数据库调试时常有问题,不能正确插入数据
解决方法:数据库sql语句语法不同,改变了写法
页面布局在不同机型上显示与preview比例不符
解决方法:使用相对布局
八、项目分工
姓名 | 分工 | 工作比例 | 分数(10分) |
李凯 | UI,功能实现,数据库 | 60% | 10 |
季轩石 | UI,部分功能实现 | 40% | 10 |