Create an elegantly designed Reminder/Alarm clock application
DownloadKeywords: ListActivity SimpleCursorAdapter SQLiteDatabase AlarmManager NotificationManager IntentService BroadcastReceiver ToggleButton ViewSwitcher DatePicker TimePicker RadioGroup
Contents- Overview
- Create a new Eclipse Android project
- Define the Data model
- The Android Manifest file
- The Application class
- The Preferences screen
- The Alarm Service
- The Alarm Receiver
- The Alarm Setter
- The Main screen
- The Options Menu
- The Context Menu
- The Edit Dialog
- The New Reminder screen
- Date and Time Controls
Here is some relevant portion of the MainActivity.java file.
public class MainActivity extends ListActivity { private TextView headingText; private Typeface font; public final Calendar cal = Calendar.getInstance(); public final Date dt = new Date(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); headingText = (TextView) findViewById(R.id.heading_tv); font = Typeface.createFromAsset(getAssets(), "fonts/OpenSans.ttf"); headingText.setTypeface(font); registerForContextMenu(getListView()); } private Cursor createCursor() { long time = cal.getTimeInMillis(); Cursor c = RemindMe.db.rawQuery("SELECT * FROM Notification WHERE time BETWEEN "+ time+" AND "+(time+86400000), null); startManagingCursor(c); return c; } @Override protected void onResume() { super.onResume(); SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, R.layout.row, createCursor(), new String[]{Notification.COL_MSG, Notification.COL_DATETIME}, new int[]{R.id.msg_tv, R.id.time_tv}); adapter.setViewBinder(new ViewBinder() { @Override public boolean setViewValue(View view, Cursor cursor, int columnIndex) { if (view.getId() == R.id.msg_tv) return false; TextView tv = (TextView)view; switch(view.getId()) { case R.id.time_tv: dt.setTime(cursor.getLong(columnIndex)); tv.setText(dt.getHours()+":"+dt.getMinutes()); break; } return true; } }); setListAdapter(adapter); } }Notice how we used custom fonts in our application. For this put the particular fonts file in assets directory of the project preferably under a sub folder, ex. fonts.
Another thing to note is the use of custom layout file for list rows and the ability to bind view data selectively. The layout file row.xml simply contains a RelativeLayout with few TextViews.
We specified an onClick function for the buttons in main.xml layout file. Here is the implementation.
public void onClick(View v) { switch (v.getId()) { case R.id.imageButton1: startActivity(new Intent(this, SettingsActivity.class)); break; case R.id.imageButton2: startActivity(new Intent(this, AddAlarmActivity.class)); break; case R.id.imageButton3: cal.add(Calendar.DATE, -1); ((SimpleCursorAdapter)getListAdapter()).changeCursor(createCursor()); break; case R.id.imageButton4: cal.add(Calendar.DATE, 1); ((SimpleCursorAdapter)getListAdapter()).changeCursor(createCursor()); break; } }We have overridden onSaveInstanceState() and onRestoreInstanceState() methods of the Activity to save the current date range so that it's not lost when the Activity is recreated upon orientation change.
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putLong("cal", cal.getTimeInMillis()); } @Override protected void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); cal.setTimeInMillis(state.getLong("cal")); }
11. The Options Menu
The options menu pops out on pressing the menu button of the device and gives an option to delete all notifications listed on the screen.We just need to override few methods of the Activity to implement options menu.
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.options_menu, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { if (getListAdapter().isEmpty()) { menu.findItem(R.id.menu_delete_all).setEnabled(false); } else { menu.findItem(R.id.menu_delete_all).setEnabled(true); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_delete_all: Intent cancelAll = new Intent(this, AlarmService.class); cancelAll.putExtra("startTime", cal.getTimeInMillis()); cancelAll.putExtra("endTime", cal.getTimeInMillis()+86400000); cancelAll.setAction(AlarmService.CANCEL); startService(cancelAll); SimpleCursorAdapter adapter = (SimpleCursorAdapter) getListAdapter(); adapter.getCursor().requery(); adapter.notifyDataSetChanged(); return true; } return super.onOptionsItemSelected(item); }Only onCreateOptionsMenu() and onOptionsItemSelected() methods are required but we have overridden onPrepareOptionsMenu() as well for disabling delete option when list is empty.
We need to create a xml file under res/menu directory for declaring the options. Here is the content of options_menu.xml file.
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/menu_delete_all" android:title="@string/delete_all" android:icon="@android:drawable/ic_menu_delete" /> </menu>
12. The Context Menu
Next we'll implement the context menu for ListView which allows user to take action on a specific item, for ex. edit, delete.Override the two methods onCreateContextMenu() and onContextItemSelected().
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (v.getId() == android.R.id.list) { getMenuInflater().inflate(R.menu.context_menu, menu); menu.setHeaderTitle("Choose an Option"); menu.setHeaderIcon(R.drawable.ic_dialog_menu_generic); } } @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); switch (item.getItemId()) { case R.id.menu_edit: showDialog(R.id.menu_edit); break; case R.id.menu_delete: Intent cancel = new Intent(this, AlarmService.class); cancel.putExtra("notificationId", String.valueOf(info.id)); cancel.setAction(AlarmService.CANCEL); startService(cancel); SimpleCursorAdapter adapter = (SimpleCursorAdapter) getListAdapter(); adapter.getCursor().requery(); adapter.notifyDataSetChanged(); break; } return true; }
Note there is no method corresponding to onPrepareOptionsMenu() for context menu since onCreateContextMenu() is called every time unlike onCreateOptionsMenu() in case of options menu.
We need to create context_menu.xml file under res/menu directory.<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/menu_edit" android:title="@string/edit" /> <item android:id="@+id/menu_delete" android:title="@string/delete" /> </menu>It is necessary to register a view for context menu using registerForContextMenu() method. Note that we have already done this for ListView in the onCreate() method.
The context menu pops out when a list item is long pressed but we can open it on click as well. ListActivity makes it simple by just overriding a method.
@Override protected void onListItemClick(ListView l, View v, int position, long id) { openContextMenu(v); }