(干货)Android入门完整项目:一个有定时提醒功能的备忘录

栏目: 数据库 · 发布时间: 5年前

内容简介:本文原创地址,记事本中是包含一个实体类,

(干货)Android入门完整项目:一个有定时提醒功能的备忘录

本文原创地址, 我的博客https://jsbintask.cn/2019/02/23/android/android-memo/ (食用效果最佳),转载请注明出处!

介绍

今天给大家分享一个以前学习 android 时做的小项目,一个 带有定时提醒功能的备忘录 ,主要用到 RecycleView , sqlite , butterknife ,效果如下:

(干货)Android入门完整项目:一个有定时提醒功能的备忘录 apk地址

详细功能实现

建立db,编写db helper类

  1. 新建一个常量类,包含所有操作db的语句, ColumnContacts :

    public class ColumnContacts {
        public static final String EVENT_TABLE_NAME = "event";
        public static final String EVENT_TITLE_COLUMN = "title";
        public static final String EVENT_CONTENT_COLUMN = "content";
        public static final String EVENT_CREATED_TIME_COLUMN = "created_time";
        public static final String EVENT_UPDATED_TIME_COLUMN = "updated_time";
        public static final String EVENT_REMIND_TIME_COLUMN = "remind_time";
        public static final String EVENT_IS_IMPORTANT_COLUMN = "is_important";
        public static final String EVENT_IS_CLOCKED = "is_clocked";
    }
    
  2. 新建一个 DBTemplate ,此处用到 设计模式 模板方法,所以还包含一个回调接口 DBCallbackk

    public class DBTemplate<T> {
        private DBOpenHelper dbHelper;
    
        public DBTemplate() {
            dbHelper = new DBOpenHelper();
        }
    
        public T queryOne(String sql, DBCallback<T> callback, String...args) {
            T t = null;
            SQLiteDatabase db = dbHelper.getReadableDatabase();
            Cursor cursor = db.rawQuery(sql, args);
            if (cursor != null && cursor.moveToNext()) {
                t = callback.cursorToInstance(cursor);
                cursor.close();
            }
    
            return t;
        }
    
        public List<T> query(String sql, DBCallback<T> callback, String... args) {
            List<T> list = new ArrayList<>();
            SQLiteDatabase db = dbHelper.getReadableDatabase();
            Cursor cursor = db.rawQuery(sql, args);
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    T t = callback.cursorToInstance(cursor);
                    list.add(t);
                }
                cursor.close();
            }
    
            return list;
        }
    
        public long create(String table, ContentValues values) {
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            return db.insert(table, null, values);
        }
    
        public int remove(String table, String whereConditions, String... args) {
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            return db.delete(table, whereConditions, args);
        }
    
        public int getLatestId(String table) {
            SQLiteDatabase db = dbHelper.getReadableDatabase();
            String sql = "SELECT MAX(" + BaseColumns._ID + ") FROM " + table;
            Cursor cursor = db.rawQuery(sql, new String[]{});
            int result = -1;
            if (cursor != null && cursor.moveToNext()) {
                result = cursor.getInt(0);
                cursor.close();
            }
            return result;
        }
    
        public int update(String table, ContentValues contentValues, String whereConditions, String... args) {
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            return db.update(table, contentValues, whereConditions, args);
        }
    }
    
public interface DBCallback <T> {
    /**
     * Gets a instance of T by cursor
     */
    T cursorToInstance(Cursor cursor);
}
  1. 新建一个类继承 SQLiteOpenHelperDBOpenHelper ,用于启动app时创建数据库,并且初始化数据,加入一条记录:
    public class DBOpenHelper extends SQLiteOpenHelper {
        private static final String TAG = DBOpenHelper.class.getSimpleName();
        private static final int VERSION = 1;
        private static final String DB_NAME = "memo.db";
    
        public DBOpenHelper() {
            super(MemoApplication.getContext(), DB_NAME, null, VERSION);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            /*第一次初始化app,创建表结构 */
            db.execSQL("CREATE TABLE IF NOT EXISTS " + ColumnContacts.EVENT_TABLE_NAME + "( "
                        + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                        + ColumnContacts.EVENT_TITLE_COLUMN + " text, "
                        + ColumnContacts.EVENT_CONTENT_COLUMN + " text, "
                        + ColumnContacts.EVENT_CREATED_TIME_COLUMN + " datetime, "
                        + ColumnContacts.EVENT_UPDATED_TIME_COLUMN + " datetime, "
                        + ColumnContacts.EVENT_REMIND_TIME_COLUMN + " datetime, "
                        + ColumnContacts.EVENT_IS_IMPORTANT_COLUMN + " INTEGER, "
                        + ColumnContacts.EVENT_IS_CLOCKED + " INTEGER"
            + ")");
    
            String sql = "INSERT INTO " + ColumnContacts.EVENT_TABLE_NAME + " VALUES(NULL, ?, ?, ?, ?, ?, ?, ?)";
            db.beginTransaction();
            db.execSQL(sql, new Object[]{"jsbintask->memo",
                    "Memo是一个小巧方便带有闹铃功能的记事本app,主要使用butterknife和recycleview,clockmanager构建\n" +
                            "git地址:https://github.com/jsbintask22/memo.git",
                    "2018-04-25 17:28:23",
                    "2018-04-25 17:28",
                    "2018-04-25 17:28",
                    0, 0});
            db.setTransactionSuccessful();
            db.endTransaction();
        }
    
        /*版本更新时会执行该方法,如版本变更 => 2  */
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            //Nothing to do
        }
    }
    

编写dao层

记事本中是包含一个实体类, Event ,编写实体类和dao层代码对该表进行增删改查

Event :

public class Event implements BaseColumns, Parcelable{
    //id
    private Integer mId;
    //事件
    private String mTitle;
    //事件内容
    private String mContent;
    //创建时间
    private String mCreatedTime;

    public Integer getmIsClocked() {
        return mIsClocked;
    }

    public void setmIsClocked(Integer mIsClocked) {
        this.mIsClocked = mIsClocked;
    }

    //更新时间
    private String mUpdatedTime;
    //闹钟表示位:该事件是否已经响过铃了,默认没有
    private Integer mIsClocked = 0;

    public Event(){}

    protected Event(Parcel in) {
        if (in.readByte() == 0) {
            mId = null;
        } else {
            mId = in.readInt();
        }
        mTitle = in.readString();
        mContent = in.readString();
        mCreatedTime = in.readString();
        mUpdatedTime = in.readString();
        if (in.readByte() == 0) {
            mIsClocked = null;
        } else {
            mIsClocked = in.readInt();
        }
        if (in.readByte() == 0) {
            mIsImportant = null;
        } else {
            mIsImportant = in.readInt();
        }
        mRemindTime = in.readString();
    }

    public static final Creator<Event> CREATOR = new Creator<Event>() {
        @Override
        public Event createFromParcel(Parcel in) {
            return new Event(in);
        }

        @Override
        public Event[] newArray(int size) {
            return new Event[size];
        }
    };

    public Integer getmIsImportant() {
        return mIsImportant;
    }

    public void setmIsImportant(Integer mIsImportant) {
        this.mIsImportant = mIsImportant;
    }

    private Integer mIsImportant;

    public Integer getmId() {
        return mId;
    }

    public void setmId(Integer mId) {
        this.mId = mId;
    }

    public String getmTitle() {
        return mTitle;
    }

    public void setmTitle(String mTitle) {
        this.mTitle = mTitle;
    }

    public String getmContent() {
        return mContent;
    }

    public void setmContent(String mContent) {
        this.mContent = mContent;
    }

    public String getmCreatedTime() {
        return mCreatedTime;
    }

    public void setmCreatedTime(String mCreatedTime) {
        this.mCreatedTime = mCreatedTime;
    }

    public String getmUpdatedTime() {
        return mUpdatedTime;
    }

    public void setmUpdatedTime(String mUpdatedTime) {
        this.mUpdatedTime = mUpdatedTime;
    }

    public String getmRemindTime() {
        return mRemindTime;
    }

    public void setmRemindTime(String mRemindTime) {
        this.mRemindTime = mRemindTime;
    }

    private String mRemindTime;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        if (mId == null) {
            dest.writeByte((byte) 0);
        } else {
            dest.writeByte((byte) 1);
            dest.writeInt(mId);
        }
        dest.writeString(mTitle);
        dest.writeString(mContent);
        dest.writeString(mCreatedTime);
        dest.writeString(mUpdatedTime);
        if (mIsClocked == null) {
            dest.writeByte((byte) 0);
        } else {
            dest.writeByte((byte) 1);
            dest.writeInt(mIsClocked);
        }
        if (mIsImportant == null) {
            dest.writeByte((byte) 0);
        } else {
            dest.writeByte((byte) 1);
            dest.writeInt(mIsImportant);
        }
        dest.writeString(mRemindTime);
    }
}

值得注意的是,为了让其在Activity之间传递数据,需要继承 Parcelable 接口,接下编写 EventDao ,因为用到了模板方法,所以加入回调类 EventCallback

public class EventDao {
    //使用模板模式进行DB操作
    private DBTemplate<Event> mTemplate = new DBTemplate<>();
    private EventCallback mCallback = new EventCallback();
    //设置为单例模式,其他类均是此方法
    private static EventDao mEventDao = new EventDao();

    private EventDao() {
    }

    public static EventDao getInstance() {
        return mEventDao;
    }

    public List<Event> findAll() {
        String sql = "SELECT * FROM " + ColumnContacts.EVENT_TABLE_NAME + " ORDER BY " + ColumnContacts.EVENT_IS_IMPORTANT_COLUMN + " DESC, " + ColumnContacts.EVENT_CREATED_TIME_COLUMN + " DESC";
        return mTemplate.query(sql, mCallback);
    }

    public List<Event> findAllWithNOClocked() {
        String sql = "SELECT * FROM " + ColumnContacts.EVENT_TABLE_NAME + " WHERE " + ColumnContacts.EVENT_IS_CLOCKED + " = " + Constants.EventClockFlag.NONE;
        return mTemplate.query(sql, mCallback);
    }

    public int updateEventClocked(Integer id) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(ColumnContacts.EVENT_IS_CLOCKED, Constants.EventClockFlag.CLOCKED);
        return mTemplate.update(ColumnContacts.EVENT_TABLE_NAME, contentValues, BaseColumns._ID + " = ?", Integer.toString(id));
    }

    public Event findById(Integer id) {
        String sql = "SELECT * FROM " + ColumnContacts.EVENT_TABLE_NAME + " WHERE " + BaseColumns._ID + " = ?";
        return mTemplate.queryOne(sql, mCallback, Integer.toString(id));
    }

    public int remove(List<Integer> ids) {
        StringBuilder whereConditions = new StringBuilder(BaseColumns._ID + " IN(");
        for (Integer id : ids) {
            whereConditions.append(id).append(",");
        }
        whereConditions.deleteCharAt(whereConditions.length() - 1).append(")");
        return mTemplate.remove(ColumnContacts.EVENT_TABLE_NAME, whereConditions.toString());
    }

    public int create(Event event) {
        return (int) mTemplate.create(ColumnContacts.EVENT_TABLE_NAME, generateContentValues(event, false));
    }

    public int update(Event event) {
        return mTemplate.update(ColumnContacts.EVENT_TABLE_NAME, generateContentValues(event, true), BaseColumns._ID + "  = ?", Integer.toString(event.getmId()));
    }

    public int getLatestEventId() {
        return mTemplate.getLatestId(ColumnContacts.EVENT_TABLE_NAME);
    }

    private ContentValues generateContentValues(Event event, boolean isUpdate) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(ColumnContacts.EVENT_TITLE_COLUMN, event.getmTitle());
        contentValues.put(ColumnContacts.EVENT_CONTENT_COLUMN, event.getmContent());
        if (!isUpdate) {
            contentValues.put(ColumnContacts.EVENT_CREATED_TIME_COLUMN, DateTimeUtil.dateToStr(new Date()));
        } else {
            contentValues.put(ColumnContacts.EVENT_CREATED_TIME_COLUMN, event.getmCreatedTime());
        }
        contentValues.put(ColumnContacts.EVENT_IS_CLOCKED, event.getmIsClocked());
        contentValues.put(ColumnContacts.EVENT_UPDATED_TIME_COLUMN, DateTimeUtil.dateToStr(new Date()));
        contentValues.put(ColumnContacts.EVENT_REMIND_TIME_COLUMN, event.getmRemindTime());
        contentValues.put(ColumnContacts.EVENT_IS_IMPORTANT_COLUMN, event.getmIsImportant());
        return contentValues;
    }
}
public class EventCallback implements DBCallback<Event> {
    @Override
    public Event cursorToInstance(Cursor cursor) {
        Event event = new Event();
        event.setmId(cursor.getInt(cursor.getColumnIndexOrThrow(BaseColumns._ID)));
        event.setmTitle(cursor.getString(cursor.getColumnIndexOrThrow(ColumnContacts.EVENT_TITLE_COLUMN)));
        event.setmContent(cursor.getString(cursor.getColumnIndexOrThrow(ColumnContacts.EVENT_CONTENT_COLUMN)));
        event.setmCreatedTime(cursor.getString(cursor.getColumnIndexOrThrow(ColumnContacts.EVENT_CREATED_TIME_COLUMN)));
        event.setmUpdatedTime(cursor.getString(cursor.getColumnIndexOrThrow(ColumnContacts.EVENT_UPDATED_TIME_COLUMN)));
        event.setmRemindTime(cursor.getString(cursor.getColumnIndexOrThrow(ColumnContacts.EVENT_REMIND_TIME_COLUMN)));
        event.setmIsImportant(cursor.getInt(cursor.getColumnIndexOrThrow(ColumnContacts.EVENT_IS_IMPORTANT_COLUMN)));
        event.setmIsClocked(cursor.getInt(cursor.getColumnIndexOrThrow(ColumnContacts.EVENT_IS_CLOCKED)));
        return event;
    }
}

编写manager层

  1. EventManager用于管理所有的event如下:
    public class EventManager {
        public static final String TAG = "EventManager";
        private static EventManager mEventManager = new EventManager();
        private EventDao mEventDao = EventDao.getInstance();
        //保存一份数据
    
        public List<Integer> getDeletedIds() {
            return deletedIds;
        }
    
        public void setDeletedIds(List<Integer> deletedIds) {
            this.deletedIds = deletedIds;
        }
    
        private List<Event> events = new ArrayList<>();
        private List<Integer> deletedIds = new ArrayList<>();
    
        private EventManager(){
        }
    
        public static EventManager getInstance() {
            return mEventManager;
        }
    
        public List<Event> findAll() {
            events =  mEventDao.findAll();
            return events;
        }
    
        public void flushData() {
            events = mEventDao.findAll();
        }
    
        public List<Event> getEvents() {
            return events;
        }
    
        public void removeEvents(final Handler handler, final List<Integer> ids) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int result = mEventDao.remove(ids);
                        Message message = new Message();
                        message.what = Constants.HANDLER_SUCCESS;
                        message.obj = result;
                        message.setTarget(handler);
                        message.sendToTarget();
                    } catch (Exception e) {
                        Log.e(TAG, "run: ", e);
                        handler.obtainMessage(Constants.HANDLER_FAILED, new MemoException(e)).sendToTarget();
                    }
                }
            }).start();
        }
    
        public boolean removeEvent(Integer id) {
            return mEventDao.remove(Collections.singletonList(id)) != 0;
        }
    
        public boolean saveOrUpdate(Event event) {
            try {
                if (event.getmId() != null) {
                    mEventDao.update(event);
                } else {
                    mEventDao.create(event);
                }
                return true;
            } catch (Exception e) {
                Log.e(TAG, "saveOrUpdate: ", e);
                return false;
            }
        }
    
        public int getLatestEventId() {
            return mEventDao.getLatestEventId();
        }
    
        public Event getOne(Integer id) {
            return mEventDao.findById(id);
        }
    
        public boolean checkEventField(Event event) {
            if (event == null) {
                return false;
            }
            if (StringUtil.isBlank(event.getmTitle())) {
                ToastUtil.showToastShort(R.string.event_can_not_empty);
                return false;
            }
            if (StringUtil.isBlank(event.getmContent())) {
                ToastUtil.showToastShort(R.string.content_can_not_empty);
                return false;
            }
            if (StringUtil.isBlank(event.getmRemindTime())) {
                ToastUtil.showToastShort(R.string.remind_time_can_not_empty);
                return false;
            }
            if (DateTimeUtil.str2Date(event.getmRemindTime()) == null) {
                ToastUtil.showToastShort(R.string.invalid_remind_time_format);
                return false;
            }
            if (new Date().getTime() > DateTimeUtil.str2Date(event.getmRemindTime()).getTime()) {
                ToastUtil.showToastShort(R.string.remind_time_deprecated);
                return false;
            }
    
            return true;
        }
    }
    

接着编写一个 ClockManager 用于管理系统闹钟服务,用于app定时提醒:

public class ClockManager {
    private static ClockManager instance = new ClockManager();

    private ClockManager() {
    }

    public static ClockManager getInstance() {
        return instance;
    }

    /**
     * 获取系统闹钟服务
     * @return
     */
    private static AlarmManager getAlarmManager() {
        return (AlarmManager) MemoApplication.getContext().getSystemService(Context.ALARM_SERVICE);
    }

    /**
     * 取消闹钟
     * @param pendingIntent
     */
    public void cancelAlarm(PendingIntent pendingIntent) {
        getAlarmManager().cancel(pendingIntent);
    }

    /**
     * 添加闹钟
     * @param pendingIntent 执行动作
     * @param performTime  执行时间
     */
    public void addAlarm(PendingIntent pendingIntent, Date performTime) {
        cancelAlarm(pendingIntent);
        getAlarmManager().set(AlarmManager.RTC_WAKEUP, performTime.getTime(), pendingIntent);
    }
}

编写系统Service和Receiver

为了让我们的提醒服务在后台保活,我们需要编写一个 ClockService 或者 ClockReceiver 在后台运行(任意一种都行,此处用的 service :

/**
 * Service和Broadcast都行,此处选一个,service存活率更高
 */
public class ClockService extends Service {
    private static final String TAG = "ClockService";
    public static final String EXTRA_EVENT_ID = "extra.event.id";
    public static final String EXTRA_EVENT_REMIND_TIME = "extra.event.remind.time";
    public static final String EXTRA_EVENT = "extra.event";
    private EventDao mEventDao = EventDao.getInstance();
    public ClockService() {
        Log.d(TAG, "ClockService: Constructor");
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: onStartCommand");
        WakeLockUtil.wakeUpAndUnlock();
        postToClockActivity(getApplicationContext(), intent);
        return super.onStartCommand(intent, flags, startId);
    }

    private void postToClockActivity(Context context, Intent intent) {
        Intent i = new Intent();
        i.setClass(context, ClockActivity.class);
        i.putExtra(EXTRA_EVENT_ID, intent.getIntExtra(EXTRA_EVENT_ID, -1));
        Event event = mEventDao.findById(intent.getIntExtra(EXTRA_EVENT_ID, -1));
        if (event == null) {
            return;
        }
        i.putExtra(EXTRA_EVENT_REMIND_TIME, intent.getStringExtra(EXTRA_EVENT_REMIND_TIME));
        i.putExtra(EXTRA_EVENT, event);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(i);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: ");
        super.onDestroy();
    }
}

ClockReceiver 如下:

public class ClockReceiver extends BroadcastReceiver {
    private static final String TAG = "ClockReceiver";
    public static final String EXTRA_EVENT_ID = "extra.event.id";
    public static final String EXTRA_EVENT_REMIND_TIME = "extra.event.remind.time";
    public static final String EXTRA_EVENT = "extra.event";
    private EventDao mEventDao = EventDao.getInstance();

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive: " + intent.getAction());
        WakeLockUtil.wakeUpAndUnlock();
        postToClockActivity(context, intent);
    }

    private void postToClockActivity(Context context, Intent intent) {
        Intent i = new Intent();
        i.setClass(context, ClockActivity.class);
        i.putExtra(EXTRA_EVENT_ID, intent.getIntExtra(EXTRA_EVENT_ID, -1));
        Event event = mEventDao.findById(intent.getIntExtra(EXTRA_EVENT_ID, -1));
        if (event == null) {
            return;
        }
        i.putExtra(EXTRA_EVENT_REMIND_TIME, intent.getStringExtra(EXTRA_EVENT_REMIND_TIME));
        i.putExtra(EXTRA_EVENT, event);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(i);
    }

    public ClockReceiver() {
        super();
        Log.d(TAG, "ClockReceiver: Constructor");
    }
}

Activity

接着编写Activity,首先,写一个主界面,用于展示事件清单,因为有多个 Activity ,所以我们加入 BaseActivity :

public abstract class BaseActivity extends AppCompatActivity {
    /*Butterknife绑定器,该activity销毁时要取消绑定,避免内存泄漏  */
    private Unbinder mUnbinder;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getContentView());
        mUnbinder = ButterKnife.bind(this);
        /*Base activity中的公共的方法,因为每个类都需要初始化,所以在基类中定义 */
        initView();
        initData();
        setListener();
    }

    protected abstract void setListener();

    protected abstract void initView();

    protected abstract void initData();

    public abstract int getContentView();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mUnbinder.unbind();
    }
}

MainActivity 如下:

public class MainActivity extends BaseActivity implements BaseHandler.HandlerResultCallBack {
    @BindView(R.id.recycler_view)
    RecyclerView mRecyclerView;
    @BindView(R.id.search_view)
    SearchView mSearchView;
    private EventRecyclerViewAdapter mAdapter;
    private EventManager mEventManger = EventManager.getInstance();
    private ClockManager mClockManager = ClockManager.getInstance();
    private BaseHandler mBaseHandler = new BaseHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void initView() {
        mAdapter = new EventRecyclerViewAdapter(this);
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mSearchView.getLayoutParams();
        //将文字内容略微下移,SearchView  bug
        params.bottomMargin = -3;
        mSearchView.setLayoutParams(params);
        mSearchView.onActionViewExpanded();
        initSearchView();
    }

    private void initSearchView() {
        //一处searchView进入屏幕时候的焦点
        mSearchView.clearFocus();
        Class<? extends SearchView> aClass = mSearchView.getClass();
        try {
            //去掉SearchView自带的下划线
            Field mSearchPlate = aClass.getDeclaredField("mSearchPlate");
            mSearchPlate.setAccessible(true);
            View o = (View) mSearchPlate.get(mSearchView);
            o.setBackgroundColor(getColor(R.color.transparent));
        } catch (Exception e) {
            e.printStackTrace();
        }
        //隐藏键盘
        AppUtil.hideSoftInput(this, mSearchView);
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void initData() {
        //设置数据源,适配器等等
        mAdapter.setDatabases(mEventManger.findAll());
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
    }

    @Override
    protected void setListener() {
       mAdapter.setOnItemClickListener(mOnItemClickListener);
       mSearchView.setOnQueryTextListener(mQueryListener);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main_activity, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        //判断是点击了那个按钮,删除,添加
        if (item.getItemId() == R.id.menu_add) {
            Intent intent = new Intent();
            intent.setClass(this, EventDetailActivity.class);
            intent.putExtra(EventDetailActivity.EXTRA_IS_ADD_EVENT, true);
            startActivity(intent);
        } else if (item.getItemId() == R.id.menu_delete) {
            if (mAdapter.getIsDeleteMode()) {
                //删除数据
                if (mAdapter.getSelectedEventIds().size() == 0) {
                    ToastUtil.showToastShort(R.string.no_event_selected_msg);
                } else {
                    int msg = mAdapter.getSelectedEventIds().size() == 1 ? R.string.delete_event_msg : R.string.delete_events_msg;
                    AlertDialogUtil.showDialog(this, msg, mConfirmListener);
                }
            } else {
                mAdapter.setDeleteMode(true);
            }
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public int getContentView() {
        return R.layout.activity_main;
    }

    private EventRecyclerViewAdapter.OnItemClickListener mOnItemClickListener = new EventRecyclerViewAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(View view, int position) {
            if (!mAdapter.getIsDeleteMode()) {
                //跳屏,此时为查看详情,不是编辑状态
                Intent intent = new Intent();
                intent.setClass(MainActivity.this, EventDetailActivity.class);
                intent.putExtra(EventDetailActivity.EXTRA_IS_EDIT_EVENT, false);
                intent.putExtra(EventDetailActivity.EXTRA_EVENT_DATA, mAdapter.getDatabases().get(position));
                startActivity(intent);
            }
        }

        @Override
        public void onItemLongClick(View view, int position) {
            ToastUtil.showToastShort("Long clicked");
            mAdapter.setDeleteMode(true);
        }
    };

    private DialogInterface.OnClickListener mConfirmListener = new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            mEventManger.setDeletedIds(mAdapter.getSelectedEventIds());
            mEventManger.removeEvents(mBaseHandler, mEventManger.getDeletedIds());
        }
    };

    /**
     * 从编辑屏幕回来时调用该方法,做数据更新
     */
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        mAdapter.setDatabases(mEventManger.getEvents());
    }

    private SearchView.OnQueryTextListener mQueryListener = new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            return false;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            //做本地查询
            if (!StringUtil.isBlank(newText)) {
                List<Event> events = new ArrayList<>();
                for (Event event : mAdapter.getDatabases()) {
                    if (event.getmTitle().contains(newText)) {
                        events.add(event);
                    }
                }
                mAdapter.setDatabases(events);
            } else {
                mAdapter.setDatabases(mEventManger.getEvents());
            }
            return true;
        }
    };

    /**
     * handler处理成功的回调函数
     * @link com.jsbintask@gmail.com.memo.base.BaseHandler
     */
    @Override
    public void handlerSuccess(Message msg) {
        ToastUtil.showToastShort(R.string.delete_successful);
        for (PendingIntent pendingIntent : buildIntent(mEventManger.getDeletedIds())) {
            mClockManager.cancelAlarm(pendingIntent);
        }
        mAdapter.setDatabases(mEventManger.findAll());
    }
    /**
     * 处理失败的回调函数
     */
    @Override
    public void handlerFailed(MemoException e) {
        ToastUtil.showToastShort(R.string.delete_failed);
        mAdapter.setDeleteMode(false);
    }

    private List<PendingIntent> buildIntent(List<Integer> ids) {
        List<PendingIntent> pendingIntents = new ArrayList<>();
        for (Integer id : ids) {
            Intent intent = new Intent();
            intent.putExtra(ClockService.EXTRA_EVENT_ID, id);
            intent.setClass(this, ClockService.class);

            pendingIntents.add(PendingIntent.getService(this, 0x001, intent, PendingIntent.FLAG_UPDATE_CURRENT));
        }
        return pendingIntents;
    }
}

接着,编写个用于展示事件详情的 EventDetailActivity :

public class EventDetailActivity extends BaseActivity {
    public static final String EXTRA_IS_EDIT_EVENT = "extra.is.edit.event";
    public static final String EXTRA_EVENT_DATA = "extra.event.data";
    public static final String EXTRA_IS_ADD_EVENT = "extra.is.create.event";
    //从主屏进来的操作是不是编辑操作
    private boolean isEditEvent;
    //从主屏进来的操作是不是添加操作
    private boolean isAddEvent;
    private EventManager mEventManager = EventManager.getInstance();
    private ClockManager mClockManager = ClockManager.getInstance();
    @BindView(R.id.ll_update_time)
    LinearLayout llUpdateTime;
    @BindView(R.id.ed_title)
    EditText edTitle;
    @BindView(R.id.tv_remind_time_picker)
    EditText tvRemindTime;
    @BindView(R.id.ed_content)
    EditText edContent;
    @BindView(R.id.tv_last_edit_time)
    TextView tvUpdateTime;
    @BindView(R.id.iv_back)
    ImageView ivBack;
    @BindView(R.id.tv_confirm)
    TextView tvConfirm;
    @BindView(R.id.iv_delete)
    ImageView ivDelete;
    @BindView(R.id.iv_edit)
    ImageView ivEdit;
    @BindView(R.id.chb_is_important)
    CheckBox chbIsImportant;
    @BindView(R.id.scroll_view)
    ScrollView scrollView;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void setListener() {
    }

    @Override
    protected void initView() {
        isEditEvent = getIntent().getBooleanExtra(EXTRA_IS_EDIT_EVENT, false);
        isAddEvent = getIntent().getBooleanExtra(EXTRA_IS_ADD_EVENT, false);
        judgeOperate();
    }

    private void judgeOperate() {
        //是否显示上方上次编辑时间
        llUpdateTime.setVisibility(isAddEvent ? View.GONE : View.VISIBLE);
        //是否能够编辑标题
        setEditTextReadOnly(edTitle, !isEditEvent && !isAddEvent);
        //是否能够编辑内容
        setEditTextReadOnly(edContent, !isEditEvent && !isAddEvent);
        //设置提醒时间不能手动输入
        setEditTextReadOnly(tvRemindTime, true);
        //设置提醒时间是否能够点击:弹出时间选择器
        tvRemindTime.setClickable(isEditEvent || isAddEvent);
        //设置右上角确定按钮是否可见
        tvConfirm.setVisibility(isEditEvent || isAddEvent ? View.VISIBLE : View.GONE);
        //设置右下角编辑按钮是否可见
        ivEdit.setVisibility(!isEditEvent && !isAddEvent ? View.VISIBLE : View.GONE);
        //设置左下角删除按钮是否可见
        ivDelete.setVisibility(!isAddEvent ? View.VISIBLE : View.GONE);
        //设置checkbox能不能点击
        chbIsImportant.setClickable(isEditEvent || isAddEvent);
    }

    @Override
    protected void initData() {
        if (!isAddEvent) {
            Event event = getIntent().getParcelableExtra(EXTRA_EVENT_DATA);
            //填充值
            tvUpdateTime.setText(event.getmUpdatedTime());
            edTitle.setText(event.getmTitle());
            edContent.setText(event.getmContent());
            tvRemindTime.setText(event.getmRemindTime());
            chbIsImportant.setChecked(event.getmIsImportant() == Constants.EventFlag.IMPORTANT);
        }
    }

    @OnClick(R.id.iv_back)
    public void backImageClick(View view) {
        finish();
    }

    @OnClick(R.id.iv_delete)
    public void deleteImageClick(View view) {
        if (!isAddEvent) {
            AlertDialogUtil.showDialog(this, R.string.delete_event_msg, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Event event = getIntent().getParcelableExtra(EventDetailActivity.EXTRA_EVENT_DATA);
                    if (mEventManager.removeEvent(event.getmId())) {
                        ToastUtil.showToastShort(R.string.delete_successful);
                        mClockManager.cancelAlarm(buildIntent(event.getmId()));
                        mEventManager.flushData();
                        postToMainActivity();
                    } else {
                        ToastUtil.showToastShort(R.string.delete_failed);
                    }
                }
            });
        }
    }

    /**
     * 回到主屏幕
     */
    private void postToMainActivity() {
        Intent intent = new Intent();
        intent.setClass(EventDetailActivity.this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        startActivity(intent);
        finish();
    }

    @Override
    public int getContentView() {
        return R.layout.activity_event_detail;
    }

    /**
     * 弹出时间选择器,选择闹钟执行时间
     * @param view
     */
    @OnClick(R.id.tv_remind_time_picker)
    public void datePickClick(View view) {
        if (isEditEvent || isAddEvent) {
            final Calendar calendar = Calendar.getInstance();
            DatePickerDialog dialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() {
                @Override
                public void onDateSet(DatePicker view, final int year, final int month, final int dayOfMonth) {
                    TimePickerDialog timePickerDialog = new TimePickerDialog(EventDetailActivity.this, new TimePickerDialog.OnTimeSetListener() {
                        @Override
                        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                            String time = year + "-" + StringUtil.getLocalMonth(month) + "-" + StringUtil.getMultiNumber(dayOfMonth) + " " + StringUtil.getMultiNumber(hourOfDay) + ":" + StringUtil.getMultiNumber(minute);
                            tvRemindTime.setText(time);
                        }
                    }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), false);
                    timePickerDialog.show();
                }
            }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
            dialog.getDatePicker().setMinDate(calendar.getTimeInMillis());
            dialog.show();
        }
    }

    @OnClick(R.id.iv_edit)
    public void editImageClick(View view) {
        if (!isEditEvent) {
            ToastUtil.showToastShort(R.string.enter_edit_mode);
            ivEdit.setVisibility(View.GONE);
            isEditEvent = true;
            judgeOperate();
        }
    }

    @OnClick(R.id.tv_confirm)
    public void confirmClick(View view) {
        //更新
        if (isEditEvent || isAddEvent) {
            Event event = buildEvent();
            //检查属性并且提醒
            if (!mEventManager.checkEventField(event)) {
                return;
            }
            if (mEventManager.saveOrUpdate(event)) {
                if (isEditEvent) {
                    ToastUtil.showToastShort(R.string.update_successful);
                } else if (isAddEvent) {
                    ToastUtil.showToastShort(R.string.create_successful);
                    event.setmId(mEventManager.getLatestEventId());
                }
                //添加闹钟
                mClockManager.addAlarm(buildIntent(event.getmId()), DateTimeUtil.str2Date(event.getmRemindTime()));
                mEventManager.flushData();
                postToMainActivity();
            } else {
                if (isEditEvent) {
                    ToastUtil.showToastShort(R.string.update_failed);
                } else if (isAddEvent) {
                    ToastUtil.showToastShort(R.string.create_failed);
                }
            }
        }
    }

    private PendingIntent buildIntent(int id) {
        Intent intent = new Intent();
        intent.putExtra(ClockReceiver.EXTRA_EVENT_ID, id);
        intent.setClass(this, ClockService.class);

        return PendingIntent.getService(this, 0x001, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    @OnClick(R.id.scroll_view)
    public void scrollViewClick(View view) {
        if (isAddEvent || isEditEvent) {
            //打开软键盘
            setEditTextReadOnly(edContent, false);
        }
    }

    @NonNull
    private Event buildEvent() {
        Event event = new Event();
        if (isEditEvent) {
            event.setmId(((Event) getIntent().getParcelableExtra(EXTRA_EVENT_DATA)).getmId());
            event.setmCreatedTime(((Event) getIntent().getParcelableExtra(EXTRA_EVENT_DATA)).getmCreatedTime());
        }
        event.setmRemindTime(tvRemindTime.getText().toString());
        event.setmTitle(edTitle.getText().toString());
        event.setmIsImportant(chbIsImportant.isChecked() ? Constants.EventFlag.IMPORTANT : Constants.EventFlag.NORMAL);
        event.setmContent(edContent.getText().toString());
        event.setmUpdatedTime(DateTimeUtil.dateToStr(new Date()));
        return event;
    }

    private void setEditTextReadOnly(EditText editText, boolean readOnly) {
        editText.setFocusable(!readOnly);
        editText.setFocusableInTouchMode(!readOnly);
        editText.setCursorVisible(!readOnly);
        editText.setTextColor(getColor(readOnly ? R.color.gray3 : R.color.black));
    }
}

最后,再写一个Activity用于展示闹铃提醒, ClockActivity :

public class ClockActivity extends BaseActivity {
    private static final String TAG = "ClockActivity";
    public static final String EXTRA_CLOCK_EVENT = "clock.event";
    //闹铃
    private MediaPlayer mediaPlayer;
    //震动
    private Vibrator mVibrator;
    private EventManager mEventManger = EventManager.getInstance();
    private Event event;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void setListener() {
    }

    @Override
    protected void initView() {
    }

    private void clock() {
        mediaPlayer.start();
        long[] pattern = new long[]{1500, 1000};
        mVibrator.vibrate(pattern, 0);
        //获取自定义布局
        View inflate = LayoutInflater.from(this).inflate(R.layout.dialog_alarm_layout, null);
        TextView textView = inflate.findViewById(R.id.tv_event);
        textView.setText(String.format(getString(R.string.clock_event_msg_template), event.getmTitle()));
        Button btnConfirm = inflate.findViewById(R.id.btn_confirm);
        final AlertDialog alertDialog = AlertDialogUtil.showDialog(this, inflate);
        btnConfirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mediaPlayer.stop();
                mVibrator.cancel();
                alertDialog.dismiss();
                finish();
            }
        });
        alertDialog.show();
    }

    @Override
    protected void onStart() {
        super.onStart();
        clock();
    }

    @Override
    protected void initData() {
        mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.clock);
        mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        Intent intent = getIntent();
        event = getIntent().getParcelableExtra(ClockService.EXTRA_EVENT);
        if (event == null) {
            finish();
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        clock();
    }

    @Override
    public int getContentView() {
        return R.layout.activity_clock;
    }
}

系统界面,服务注册清单

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="cn.jsbintask.memo">

    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />

    <application
        android:name="cn.jsbintask.memo.MemoApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_logo"
        android:label="@string/app_name"
        android:roundIcon="@drawable/ic_logo"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">
        <activity
            android:name="cn.jsbintask.memo.ui.activity.MainActivity"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="cn.jsbintask.memo.ui.activity.EventDetailActivity"
            android:theme="@style/NoActionBarTheme" />

        <!-- <service android:name=".service.ClockService" /> -->

        <activity
            android:name="cn.jsbintask.memo.ui.activity.ClockActivity"
            android:launchMode="singleTask"
            android:theme="@style/FullScreen" />

        <receiver android:name="cn.jsbintask.memo.receiver.ClockReceiver">
            <intent-filter android:priority="100">
                <action android:name="com.liuzhengwei.memo.action.CLOCK_RECEIVER" />
            </intent-filter>
        </receiver>

        <service
            android:name="cn.jsbintask.memo.service.ClockService"
            android:enabled="true"
            android:exported="true" />
    </application>

</manifest>

成果展示

闹铃提醒:

(干货)Android入门完整项目:一个有定时提醒功能的备忘录

批量删除:

(干货)Android入门完整项目:一个有定时提醒功能的备忘录

新增和编辑:

(干货)Android入门完整项目:一个有定时提醒功能的备忘录

搜索:

(干货)Android入门完整项目:一个有定时提醒功能的备忘录

项目源码地址: https://github.com/jsbintask22/memo ,欢迎fork,star。

关注我!这里只有干货!

谢谢你支持我分享知识

(干货)Android入门完整项目:一个有定时提醒功能的备忘录

扫码打赏,心意已收

(干货)Android入门完整项目:一个有定时提醒功能的备忘录

打开 微信 扫一扫,即可进行扫码打赏哦


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective STL中文版

Effective STL中文版

[美] Scott Meyers / 潘爱民、陈铭、邹开红 / 电子工业出版社 / 2013-5 / 59.00元

《Effective STL中文版:50条有效使用STL的经验》是EffectiveC++的第3卷,被评为“值得所有C++程序员阅读的C++书籍之一”。《Effective STL中文版:50条有效使用STL的经验》详细讲述了使用STL的50条指导原则,并提供了透彻的分析和深刻的实例,实用性极强,是C++程序员必备的基础书籍。C++的标准模板库(STL)是革命性的,要用好STL并不容易。《Effe......一起来看看 《Effective STL中文版》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器