我以前写了个复杂闹钟的demo,参见Android闹钟【复杂版】【大明进化十五】 .但是里面的bug有一些,好多人留言,所以我就看看源码,找找原因?顺便把源码代码整理出来,弄成一个完整的可以运行的apk,今天上午就整理了一下,才发现,源码处理的逻辑多一些,考虑的情况多,比如开机后接收一个广播,然后从数据库中取时间和当前时间对比,设置闹钟,当时区改变了,也会发送广播,对闹钟进行设置的。
转载请标明出处:http://blog.csdn.net/wdaming1986/article/details/7461043
有人问我:“当设置一个闹钟后,然后调系统时间超过设置的闹钟的时间,这时候闹钟响了??”
通过源码,我发现这种情况是正常的。不是bug。可以理解。
以下是我发现的几点闹钟中重要的点,分享一下:
(1)在闹钟中有AudioManager管理机制,这个机制可以申请和释放OnAudioFocusChangeListener监听。
还有mTelephonyManager对象,处理在闹钟响的时候,来电铃声的切换。
(2)广播接收闹钟,通过广播启动AlarmKlaxon这个Service,隐式启动service:
public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm";
- // Play the alarm alert and vibrate the device.
- Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION);
- playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
- context.startService(playAlarm);
在mainfest中,AlarmKlaxon这个服务的定义如下:
- <service android:name="AlarmKlaxon"
- android:description="@string/alarm_klaxon_service_desc"
- >
- <intent-filter>
- <action android:name="com.cn.daming.deskclock.ALARM_ALERT" />
- </intent-filter>
- </service>
这个service做的是允许别的Activity打断正在响铃的铃声,播放其他的铃声,例如,闹钟响的时候来电话了。
(3)在listview中包含checkbox,这时候闹钟的处理时,activity实现一个OnItemClickListener的监听,点击每一项的监听。然后在checkbox单独拿出去写一个类,继承LinearLayout,重写setPressed()这个方法,以实现“当点击checkbox的时候不触发parent的click事件”。关键代码如下:
- <span style="font-size:18px;"> @Override
- public void setPressed(boolean pressed) {
- // If the parent is pressed, do not set to pressed.
- if (pressed && ((View) getParent()).isPressed()) {
- return;
- }
- super.setPressed(pressed);
- }</span>
下面看看我的程序截图:
红色圈的图标为我的闹钟。 点击“玲闹钟”后的界面
点击新建闹钟出现的界面 设置好时间弹出的toast。
下面我把我的主要入口类的代码贴出来:
DeskClockMainActivity.java
- <span style="font-size:18px;">package com.cn.daming.deskclock;
- import java.util.Calendar;
- import android.app.Activity;
- import android.app.AlertDialog;
- import android.content.Context;
- import android.content.DialogInterface;
- import android.content.Intent;
- import android.content.SharedPreferences;
- import android.database.Cursor;
- import android.graphics.Typeface;
- import android.os.Bundle;
- import android.view.LayoutInflater;
- import android.view.Menu;
- import android.view.MenuItem;
- import android.view.View;
- import android.view.ContextMenu;
- import android.view.ContextMenu.ContextMenuInfo;
- import android.view.View.OnClickListener;
- import android.view.ViewGroup;
- import android.widget.AdapterView;
- import android.widget.AdapterView.AdapterContextMenuInfo;
- import android.widget.AdapterView.OnItemClickListener;
- import android.widget.CheckBox;
- import android.widget.CursorAdapter;
- import android.widget.ImageButton;
- import android.widget.ImageView;
- import android.widget.ListView;
- import android.widget.TextView;
- public class DeskClockMainActivity extends Activity implements OnItemClickListener{
- static final String PREFERENCES = "AlarmClock";
- /** This must be false for production. If true, turns on logging,
- test code, etc. */
- static final boolean DEBUG = false;
- private SharedPreferences mPrefs;
- private LayoutInflater mFactory;
- private ListView mAlarmsList;
- private Cursor mCursor;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //取自定义布局的LayoutInflater
- mFactory = LayoutInflater.from(this);
- //取getSharedPreferences中key==“AlarmClock”的值
- mPrefs = getSharedPreferences(PREFERENCES, 0);
- //获取闹钟的cursor
- mCursor = Alarms.getAlarmsCursor(getContentResolver());
- //更新布局界面
- updateLayout();
- }
- //加载更新界面布局
- private void updateLayout() {
- setContentView(R.layout.alarm_clock);
- mAlarmsList = (ListView) findViewById(R.id.alarms_list);
- AlarmTimeAdapter adapter = new AlarmTimeAdapter(this, mCursor);
- mAlarmsList.setAdapter(adapter);
- mAlarmsList.setVerticalScrollBarEnabled(true);
- mAlarmsList.setOnItemClickListener(this);
- mAlarmsList.setOnCreateContextMenuListener(this);
- View addAlarm = findViewById(R.id.add_alarm);
- addAlarm.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- addNewAlarm();
- }
- });
- // Make the entire view selected when focused.
- addAlarm.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- public void onFocusChange(View v, boolean hasFocus) {
- v.setSelected(hasFocus);
- }
- });
- ImageButton deskClock =
- (ImageButton) findViewById(R.id.desk_clock_button);
- deskClock.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- }
- });
- }
- private void addNewAlarm() {
- startActivity(new Intent(this, SetAlarm.class));
- }
- /**
- * listview的适配器继承CursorAdapter
- * @author wangxianming
- * 也可以使用BaseAdapter
- */
- private class AlarmTimeAdapter extends CursorAdapter {
- public AlarmTimeAdapter(Context context, Cursor cursor) {
- super(context, cursor);
- }
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View ret = mFactory.inflate(R.layout.alarm_time, parent, false);
- DigitalClock digitalClock =
- (DigitalClock) ret.findViewById(R.id.digitalClock);
- digitalClock.setLive(false);
- return ret;
- }
- //把view绑定cursor的每一项
- public void bindView(View view, Context context, Cursor cursor) {
- final Alarm alarm = new Alarm(cursor);
- View indicator = view.findViewById(R.id.indicator);
- // Set the initial resource for the bar image.
- final ImageView barOnOff =
- (ImageView) indicator.findViewById(R.id.bar_onoff);
- barOnOff.setImageResource(alarm.enabled ?
- R.drawable.ic_indicator_on : R.drawable.ic_indicator_off);
- // Set the initial state of the clock "checkbox"
- final CheckBox clockOnOff =
- (CheckBox) indicator.findViewById(R.id.clock_onoff);
- clockOnOff.setChecked(alarm.enabled);
- // Clicking outside the "checkbox" should also change the state.
- //对checkbox设置监听,使里外一致
- indicator.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- clockOnOff.toggle();
- updateIndicatorAndAlarm(clockOnOff.isChecked(),
- barOnOff, alarm);
- }
- });
- DigitalClock digitalClock =
- (DigitalClock) view.findViewById(R.id.digitalClock);
- // set the alarm text
- final Calendar c = Calendar.getInstance();
- c.set(Calendar.HOUR_OF_DAY, alarm.hour);
- c.set(Calendar.MINUTE, alarm.minutes);
- digitalClock.updateTime(c);
- digitalClock.setTypeface(Typeface.DEFAULT);
- // Set the repeat text or leave it blank if it does not repeat.
- TextView daysOfWeekView =
- (TextView) digitalClock.findViewById(R.id.daysOfWeek);
- final String daysOfWeekStr =
- alarm.daysOfWeek.toString(DeskClockMainActivity.this, false);
- if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) {
- daysOfWeekView.setText(daysOfWeekStr);
- daysOfWeekView.setVisibility(View.VISIBLE);
- } else {
- daysOfWeekView.setVisibility(View.GONE);
- }
- // Display the label
- TextView labelView =
- (TextView) view.findViewById(R.id.label);
- if (alarm.label != null && alarm.label.length() != 0) {
- labelView.setText(alarm.label);
- labelView.setVisibility(View.VISIBLE);
- } else {
- labelView.setVisibility(View.GONE);
- }
- }
- };
- //更新checkbox
- private void updateIndicatorAndAlarm(boolean enabled, ImageView bar,
- Alarm alarm) {
- bar.setImageResource(enabled ? R.drawable.ic_indicator_on
- : R.drawable.ic_indicator_off);
- Alarms.enableAlarm(this, alarm.id, enabled);
- if (enabled) {
- SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes,
- alarm.daysOfWeek);
- }
- }
- /*
- * (non-Javadoc)
- * @see android.app.Activity#onContextItemSelected(android.view.MenuItem)
- * 创建上下文菜单
- */
- @Override
- public boolean onContextItemSelected(final MenuItem item) {
- final AdapterContextMenuInfo info =
- (AdapterContextMenuInfo) item.getMenuInfo();
- final int id = (int) info.id;
- // Error check just in case.
- if (id == -1) {
- return super.onContextItemSelected(item);
- }
- switch (item.getItemId()) {
- case R.id.delete_alarm:
- // Confirm that the alarm will be deleted.
- new AlertDialog.Builder(this)
- .setTitle(getString(R.string.delete_alarm))
- .setMessage(getString(R.string.delete_alarm_confirm))
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface d,
- int w) {
- Alarms.deleteAlarm(DeskClockMainActivity.this, id);
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .show();
- return true;
- case R.id.enable_alarm:
- final Cursor c = (Cursor) mAlarmsList.getAdapter()
- .getItem(info.position);
- final Alarm alarm = new Alarm(c);
- Alarms.enableAlarm(this, alarm.id, !alarm.enabled);
- if (!alarm.enabled) {
- SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes,
- alarm.daysOfWeek);
- }
- return true;
- case R.id.edit_alarm:
- Intent intent = new Intent(this, SetAlarm.class);
- intent.putExtra(Alarms.ALARM_ID, id);
- startActivity(intent);
- return true;
- default:
- break;
- }
- return super.onContextItemSelected(item);
- }
- /*
- * (non-Javadoc)
- * @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)
- * 创建菜单
- */
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view,
- ContextMenuInfo menuInfo) {
- // Inflate the menu from xml.
- getMenuInflater().inflate(R.menu.context_menu, menu);
- // Use the current item to create a custom view for the header.
- final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
- final Cursor c =
- (Cursor) mAlarmsList.getAdapter().getItem((int) info.position);
- final Alarm alarm = new Alarm(c);
- // Construct the Calendar to compute the time.
- final Calendar cal = Calendar.getInstance();
- cal.set(Calendar.HOUR_OF_DAY, alarm.hour);
- cal.set(Calendar.MINUTE, alarm.minutes);
- final String time = Alarms.formatTime(this, cal);
- // Inflate the custom view and set each TextView's text.
- final View v = mFactory.inflate(R.layout.context_menu_header, null);
- TextView textView = (TextView) v.findViewById(R.id.header_time);
- textView.setText(time);
- textView = (TextView) v.findViewById(R.id.header_label);
- textView.setText(alarm.label);
- // Set the custom view on the menu.
- menu.setHeaderView(v);
- // Change the text based on the state of the alarm.
- if (alarm.enabled) {
- menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm);
- }
- }
- /*
- * (non-Javadoc)
- * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
- * 设置菜单的点击事件的处理
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_item_settings:
- startActivity(new Intent(this, SettingsActivity.class));
- return true;
- case R.id.menu_item_desk_clock:
- //modify by wangxianming in 2012-4-14
- // startActivity(new Intent(this, DeskClock.class));
- return true;
- case R.id.menu_item_add_alarm:
- addNewAlarm();
- return true;
- default:
- break;
- }
- return super.onOptionsItemSelected(item);
- }
- /*
- * (non-Javadoc)
- * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
- * 创建菜单
- */
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.alarm_list_menu, menu);
- return super.onCreateOptionsMenu(menu);
- }
- /*
- * (non-Javadoc)
- * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long)
- * 创建菜单的点击事件响应
- */
- public void onItemClick(AdapterView<?> adapterView, View v, int pos, long id) {
- Intent intent = new Intent(this, SetAlarm.class);
- intent.putExtra(Alarms.ALARM_ID, (int) id);
- startActivity(intent);
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- ToastMaster.cancelToast();
- mCursor.close();
- }
- }</span>