安卓开发(一)时间管理应用DayPlay

本篇主要讲本科时写的一个app。从应用开发的流程到最后代码落到实地,涉及设计模式、代码编写等相关知识。代码https://github.com/goodluckcwl/DayPlan

需求分析:

日常生活中,人们常常会因为拖拉而无法按时完成任务。“你有多少时间,就会花多少时间做一件事”,似乎是常人的通病。也许你开始时踌躇满志,目标明确,可是时间一长往往就会偏离目标,将时间浪费在一些无谓的事情上。缺乏适时的、足够多的提醒是主因。
当前,市场上有足够多的优秀日程管理应用,如Wunderlist等。这些应用大多提供日程记录、日程优先级、闹钟提醒、同步等功能。但这些应用的任务事项时间跨度都比较大、期限也比较长,提醒功能比较被动。这虽然有利于把握大的方向,但对于短期内效率的提高不一定有很好的效果。从另一个角度上看,养成良好的时间管理习惯,既需要着眼于长远的目标,也需要有具体的、可行的短期实践方案。所谓短期,我认为一天比较合适。
本应用并不致力于像Wunderlist一样具有十分全面的功能。而是专注于为拖拉者做好一天(甚至更短)的规划,通过主动的、多次的提醒及时检查、纠偏,让拖拉者逐渐成长为高效的成功人士。

设计理念:

一天计划,任务事项的主动、多次提醒,及时纠偏。

基本功能:

1、添加任务功能:可以添加任务事项,包括任务描述,任务属性(4类:重要紧迫、重要不紧迫、紧迫不重要、不紧迫不重要),任务标签,截止时间,图片等
2、提醒功能:包括两个模式:①普通模式:任务截止时间提醒②强迫模式:根据不同的任务属性,间隔一定的时间提醒一次
3、根据任务标签的搜索功能
4、分享:微信、QQ等

扩展功能:

提醒内容的自定义、任务完成情况总结表单等(待定)

DayPlan最终效果

DayPlan打开后,首先显示一个任务列表,如图1,任务列表左侧以不同颜色标记任务的紧迫性,同时可以看到任务的标题、日期、截止时间、是否完成等信息。
用户点击某项任务后,将进入编辑页面,在该页面可以修改任务标题、截止时间,添加提醒等。
这里写图片描述

这里写图片描述
在编辑界面点击相机按钮,进入拍照界面
这里写图片描述
在编辑界面点击图片,进入查看图片界面,用户可以放大照片查看细节在任务截止时间到了的时候,DayPlan弹出一个窗口,给出简短的提醒信息
这里写图片描述

功能详细描述

(一)规划功能:
用户可以在前一天晚上做好第二天的规划,添加必要的任务事项,包括任务描述,任务的紧迫性(重要紧迫、重要不紧迫、紧迫不重要、不紧迫不重要),截止时间,并且可以添加一张相关的图片作为提示。
(二)提醒功能
用户可以为每个事项添加提醒功能,每当截止时间到的时候,DayPlan给出一个简短的提醒。DayPlan致力于在提醒用户的同时不过分打扰用户。
(三)分享功能
DayPlan可以从某个任务生成一个描述该任务的字符串,并分享到其他应用。

系统架构设计

根据经典的MVC设计模式,我们把系统划分为三层:模型层、视图层、控制层。
(一)模型层
模型对象存储着DayPlan的应用数据和业务逻辑。模型对象不关心用户界面,它存在的唯一目的就是存储和管理应用数据。DayPlan的模型对象主要是管理图片的Photo类、管理任务事项的Plan类、进行数据存储的DayPlanJSONSerializer类、应用运行过程中保存数据的数据池单例类SinglePlanLab类等。
(二)视图层
视图对象知道如何在屏幕上绘制自己及如何响应用户的输入,DayPlan中的视图对象主要由layout文件中定义的各类组件构成。比如ZoomImageView类就是一个可以响应用户手势进行图片缩放的视图对象。
(三)控制层
控制对象包含应用的逻辑单元,是视图与模型的联系纽带。控制对象响应视图突发的各类事件。在DayPlan中,控制对象主要由Fragment类的各个子类构成。
最后,给出DayPlan应用总体框图
这里写图片描述

各子模块介绍

(一)模型层子模块
模型层主要的模块是数据处理模块、数据存储模块。
1、数据处理模块
主要由类Photo、类Plan、类SinglePlanLab处理。分别介绍如下:
①Photo类:保存图片路径的类。
②Plan类:保存任务事项的类。包含任务标题、任务截止时间等成员变量。
以上两个类均提供了从JSON对象获取数据的构造器及转化为JSON对象的方法,以方便数据存储。
③SinglePlanLab类:保存应用中所有Plan对象的单例类。为了保证数据的同步,应该把整个应用中的所有Plan对象集中保存在一个单例类里,这样数据就只有一份。其他对象从SinglePlanLab对象获取需要的Plan对象的引用。因为其他对象获取的只是SinglePlanLab对象中Plan池中某个Plan的引用,所以当其他对象修改Plan时,实际上是对SinglePlanLab中的Plan对象进行操作,所以可以保证SinglePlanLab中的Plan将会得到同步更新。
这里贴一下SinglePlanLab.java的代码:

package cn.edu.zju.isee.www.dayplan;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.UUID;

import android.content.Context;
import android.util.Log;

public class SinglePlanLab {
    private static final String         TAG         ="SinglePlanLab";
    private static final String         FILENAME    ="plans.json";

    private static SinglePlanLab        sPlanLab;
    private ArrayList<Plan>             mPlans;
    private DayPlanJSONSerializer       mSerializer;
    private Context                     mContext;



    private SinglePlanLab(Context context){
        mContext=context;
//      mPlans=new ArrayList<Plan>();
        mSerializer=new DayPlanJSONSerializer(mContext, FILENAME);
//      mPlans.add(new Plan("","今天要去跑步"));
//      mPlans.add(new Plan("","信息论与编码还有复习"));
//      mPlans.add(new Plan("","嵌入式系统设计课程作业"));
//      mPlans.add(new Plan("","网络安全课程大作业期末..........."));
        try{
            mPlans=mSerializer.loadPlans();
//创建1000条信息用于测试
            Log.d(TAG,"load plans");
        }catch(Exception e){
            Log.d(TAG,"plans.json doesn't exist,create empty planLab");
            mPlans=new ArrayList<Plan>();
        }
    }


    public static SinglePlanLab getInstance(Context context){
        if(sPlanLab==null){
            sPlanLab=new SinglePlanLab(context);
        }

        return sPlanLab;
    }

    public void addPlan(Plan plan){
        mPlans.add(plan);
    }

    public void deletePlan(Plan plan){
        mPlans.remove(plan);
    }

    public Plan getPlan(int index){
        Plan p;
        try{
            p=mPlans.get(index);
        }catch(Exception e){
            Log.d(TAG,"getPlan out of range,return null");
            return null;
        }
        return p;
    }

    public Plan getPlan(UUID planId){
        for(Plan p:mPlans){
            if(p.getId().equals(planId)){
                return p;
            }
        }
        return null;
    }

    public ArrayList<Plan>getPlans(){
        return mPlans;
    }
    public int getLength(){
        int result=0;
        for(Plan plan:mPlans){
            result++;
        }
        return result;
    }

    public ArrayList<Plan> getPlanByLabel(String label){
        ArrayList<Plan>result=new ArrayList<Plan>();
        for(Plan plan:mPlans){
            if(plan.getLabel().equals(label)){
                result.add(plan);
            }
        }
        return result;
    }

    public ArrayList <Plan> getPlanByType(String type){
        ArrayList <Plan>result=new ArrayList<Plan>();
        for(Plan plan:mPlans){
            if(plan.getType().equals(type)){
                result.add(plan);
            }
        }
        return result;
    }

    public ArrayList <Plan>getPlanByDate(Date date){
        ArrayList<Plan>result=new ArrayList<Plan>();
        for(Plan plan:mPlans){
            if(plan.getDate().equals(date)){
                result.add(plan);
            }
        }
        return result;
    }

    public Boolean savePlans(){
        try{
            mSerializer.savePlans(mPlans);
            Log.d(TAG,"plans saved to file");
            return true;
        }catch(Exception e){
            Log.d(TAG,"error save plans");
            return false;
        }
    }

    public void clearPlans(){
        File f=new File(mContext.getFilesDir()+"/"+FILENAME);
        if(f.exists()){
            mContext.deleteFile(FILENAME);          
        }
        mPlans.clear();
    }
}

2、数据存储模块
DayPlan中处理数据保存的类是DayPlanJSONSerializer。在DayPlan中需要保存的数据是SinglePlanLab中的所有Plan类实例。DayPlan应用采用JSON保存数据,过程如下:
首先,Plan类包含了一个转化为JSON对象的方法、一个从JSON对象获取数据并构造Plan实例的构造函数。保存数据时首先将Plan实例转化为JSON实例。
然后,DayPlanJSONSerializer类负责将JSON对象保存到应用的内部空间。
读取数据时,DayPlanJSONSerializer从文件读取数据并构造JSON对象,然后调用Plan的构造函数构造Plan对象。
由于SinglePlabLab维护着整个应用的数据,所以数据的保存与读取由SinglePlanLab负责。SinglePlanLab类知道如何使用DayPlanJSONSerializer类读取与保存数据。

(二)控制层子模块
根据应用的功能,可将控制层划分为5个子模块:添加任务模块、删除任务模块、提醒模块、拍照模块,分享模块。
1、添加任务模块
主要由类PlanListFragment与PlanEditFragment类处理。当用户在任务列表界面点击某个任务的时候,PlanListFragment启动PlanEditFragment并把用户所点击任务的对应的Plan实例的UUID传递给PlanEditFragment。PlanEditFragment利用UUID从SinglePlanLab获取Plan对象并初始化视图。
用户修改数据后,PlanEditFragment从视图层获取数据,对Plan实例作相应的修改,完成更新数据层的功能。
2、删除任务模块
主要由PlanListFragment类处理。当用户在任务列表界面长按操作时,进入删除模式,可以选择多个任务并删除。PlanListFragment负责告知模型层哪些任务被删除了。
3、提醒模块
主要由类PlanEditFragment和AlarmActivity类处理。当用户点击“提醒我”按钮时,PlanEditFragment启动一个TimePicker,用户输入时间后,PlanEditFragment调用系统闹钟服务,在设定的时间启动AlarmActivity,AlarmActivity则根据Plan的内容弹出提醒窗口。
4、拍照模块
主要由类PlanEditFragment和PlanCameraFragment处理。当用户点击相机按钮后,PlanEditFragment启动PlanCameraActivity(管理PlanCameraFragment的Activity)。PlanCameraFragment使用系统相机API完成拍照、处理图片、保存图片的功能,并把图片路径传递给PlanEditFragment,PlanEditFragment根据图片路径生成一个Photo对象并添加到Plan实例里。最后,PlanEditFragment刷新视图层,用户将看到刚刚拍摄的照片的缩略图。
5、分享模块
主要由类PlanEditFragment处理。当用户点击“分享”按钮时,PlanEditFragment根据当前的Plan实例,生成一个包含任务标题、任务截止时间、任务紧迫情况、任务完成情况的字符串,然后将该字符串附加到一个隐式Intent里,启动其他应用,实现分享功能。

(三)视图层子模块
DayPlan的界面主要有:任务列表界面(图1),编辑界面(图2),拍照界面(图3),查看照片界面(图4)等。
1、任务列表界面
该界面需要显示多个任务,并且根据用户的上下滑动加载不同的任务并显示。使用ListView可以方便地解决这个问题。
2、编辑界面、拍照界面
编辑界面由布局文件fragment_edit_plan.xml完成。此外,为了实现左右滑动切换到不同任务的编辑界面,我们使用PlanPagerActivity(包含ViewPager成员)来管理PlanEditFragment。
拍照界面由布局文件fragment_plan_camera.xml完成。
3、查看照片界面
DayPlan自定义了一个ZoomImageView类,负责响应用户手势进行图片的缩放。ZoomImageView类继承自View类,包含一个Bitmap成员。ZoomImageView类检测用户的手势操作,根据手指的坐标计算出图片缩放的比例,通过重写View的OnDraw函数,将Bitmap按照所计算的缩放比例绘制到屏幕上。在用户看来,就像进行了图片的缩放一样。

总结

DayPlan实现了一个时间管理应用的基本功能,但尚有以下不足:
DayPlan根据一段时间的任务完成情况绘制统计图的统计功能尚未完成;DayPlan在查看照片模式中进行图片缩放的时候,流畅度与系统自带照片查看软件相比还有差距;DayPlan在拍照过程中,由于直接调用了硬件相机,实现起来相对复杂,且拍照过程中亮度无法自动调节。但直接调用相机应用的方法则无法获得全尺寸的照片,不能满足DayPlan的需求;DayPlan在拍照后处理图像的过程中由于没有使用多线程,阻塞了UI线程;DayPlan在加载图片的过程中阻塞了UI线程;DayPlan的界面有待美化。
DayPlan这些不足有待后续完善。

一个书中的Android编程范例,Android 日程管理专家 APP源码,主要功能有:添加日程日程管理日程搜索、功能设置等。创建新日程时的临时数据,只需要年月日三个数据,用来在刚刚进入新建日程界面日把年月日默认设置成当前日期:   final static int DIALOG_SET_SEARCH_RANGE=1;//设置搜索日期范围对话框   final static int DIALOG_SET_DATETIME=2;//设置日期时间对话框   final static int DIALOG_SCH_DEL_CONFIRM=3;//日程删除确认   final static int DIALOG_CHECK=4;//查看日程   final static int DIALOG_ALL_DEL_CONFIRM=5;//删除全部过期日程   final static int DIALOG_ABOUT=6;//关于对话框   final static int MENU_HELP=1;//菜单帮助   final static int MENU_ABOUT=2;//菜单关于   public static enum WhoCall   {//判断谁调用了dialogSetRange,以决定哪个控件该gone或者visible    SETTING_ALARM,//表示设置闹钟 按钮    SETTING_DATE,//表示设置日期按钮    SETTING_RANGE,//表示设置日程查找范围按钮    NEW,//表示新建日程按钮    EDIT,//表示修改日程按钮    SEARCH_RESULT//表示查找按钮   临时记录新建日程界面里的类型spinner的position,因为设置时间的对话框cancel后回到新建日程界面时会刷新所有控件,spinner中以选中的项目也会回到默认。   String[] defultType=new String[]{"会议","备忘","待办"};//软件的三个不能删除的默认类型   Dialog dialogSetRange;//日程查找时设置日期起始范围的对话框   Dialog dialogSetDatetime;//新建或修改日程时设置日期和时间的对话框   Dialog dialogSchDelConfirm;//删除日程时的确认对话框   Dialog dialogCheck;//主界面中查看日程详细内容的对话框   Dialog dialogAllDelConfirm;//删除全部过期日程时的确认对话框   Dialog dialogAbout;//关于对话框   static ArrayList alType=new ArrayList();//存储所有日程类型的arraylist   static ArrayList alSch=new ArrayList();//存储所有schedule对象的ArrayList   Schedule schTemp;//临时的schedule   ArrayList alSelectedType=new ArrayList();//记录查找界面中类型前面checkbox状态的   String rangeFrom=getNowDateString();//查找日程时设置的起始日期,默认当前日期   String rangeTo=rangeFrom;//查找日程时设置的终止日期,默认当前日期   Layout curr=null;//记录当前界面的枚举类型   WhoCall wcSetTimeOrAlarm;//用来判断调用时间日期对话框的按钮是设置时间还是设置闹钟,以便更改对话框中的一些控件该设置为visible还是gone。。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值