效果图:
最近因工作需要做一个定时器,一看需求,深思极恐:
1.定时发送开关指令;
2.可设置周期循环;
这不就是一个标准的闹钟吗?
哎呀,烧脑~
还好有GITHUB,
拥有git爸,走到哪里都不怕!
下面看代码,看看这个闹钟是怎么实现的!
编号1:是处理弹出提示窗口的一个Activity;
编号2:Main类,设置时间周期等操作
编号3:核心类,负责计算周期时间,然后将时间通过AlarmManager发送定时广播;
编号4:广播类,负责处理3发送的广播类型,弹出1;
编号5:设置的时间信息的存取类;
编号6:配合5的一个SharedPreferenceUtil类;
编号7:设置时间的工具类;
编号8:设置星期的工具类;
面向对象编程的概念是:你办事,我放心!
从这个程序里,可以体现一二,大家都是各司其职!
先从编号2开始看:
核心代码:
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.switch_in://开始时间开关
if(v.isSelected()) {
alarmsSetting.setInEnble(false);
v.setSelected(false);
AlarmOpreation.cancelAlert(AlarmMainActivity.this, AlarmsSetting.ALARM_SETTING_TYPE_IN);
}else {
alarmsSetting.setInEnble(true);
v.setSelected(true);
AlarmOpreation.enableAlert(AlarmMainActivity.this, AlarmsSetting.ALARM_SETTING_TYPE_IN, alarmsSetting);
}
break;
case R.id.switch_out://结束时间开关
if(v.isSelected()) {
alarmsSetting.setOutEnble(false);
v.setSelected(false);
AlarmOpreation.cancelAlert(AlarmMainActivity.this, AlarmsSetting.ALARM_SETTING_TYPE_OUT);
}else {
alarmsSetting.setOutEnble(true);
v.setSelected(true);
AlarmOpreation.enableAlert(AlarmMainActivity.this, AlarmsSetting.ALARM_SETTING_TYPE_OUT, alarmsSetting);
}
break;
case R.id.set_in_time:
showTimePickerDialog(AlarmsSetting.ALARM_SETTING_TYPE_IN); //设置开始时间
break;
case R.id.set_out_time:
showTimePickerDialog(AlarmsSetting.ALARM_SETTING_TYPE_OUT);//设置结束时间
break;
}
}
是的,此君是BOS,核心内容就是几个指挥操作!
开始结束时间开关:
可以看到是将不同的ALARM_SETTING_TYPE
值发送给了AlarmOpreation
的cancelAlert
方法;
/×
×将AlarmManager注销
×/
public static void cancelAlert(Context context, int type) {
// Log.e("<<<<<<<<<<<<<<<<<", "cancelAlert");
AlarmManager mAlarmManager = (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(AlarmsSetting.ALARM_ALERT_ACTION);
intent.putExtra("type", type);
intent.setClass(context, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(context, type, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
mAlarmManager.cancel(pi);
}
也就是分别开启和关闭AlarmManager并发送对应的广播,关闭的好理解,仔细看看开启:
/×
×启动AlarmManager
×/
public static void enableAlert(Context context, int type, AlarmsSetting alarmsSetting) {
// Log.e("<<<<<<<<<<<<<<<<<", "enableAlert");
if(type==AlarmsSetting.ALARM_SETTING_TYPE_IN && !alarmsSetting.isInEnble()){
return ;
}else if(type==AlarmsSetting.ALARM_SETTING_TYPE_OUT && !alarmsSetting.isOutEnble()){
return;
}
AlarmManager mAlarmManager = (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE);
int hours = 0,minute=0,dayOfweek=0;
if(type==AlarmsSetting.ALARM_SETTING_TYPE_IN){
hours = alarmsSetting.getInHour();
minute=alarmsSetting.getInMinutes();
dayOfweek = alarmsSetting.getInDays();
}else if(type==AlarmsSetting.ALARM_SETTING_TYPE_OUT){
hours = alarmsSetting.getOutHour();
minute=alarmsSetting.getOutMinutes();
dayOfweek=alarmsSetting.getOutDays();
}
Calendar mCalendar = cacluteNextAlarm(hours, minute, dayOfweek);
// Log.e("<<<<<<<<<<<<<<<<<", "alarmsSetting" + alarmsSetting.getInHour() + "-" + alarmsSetting.getInMinutes());
// Log.e("<<<<<<<<<<<<<<<<<", " mCalendar" + mCalendar.get(Calendar.DAY_OF_WEEK));
if (mCalendar.getTimeInMillis() < System.currentTimeMillis()) {
Log.e("!!!!!!!!!!!","setAlarm FAIL:设置时间不能小于当前系统时间,本?"+mCalendar.getTimeInMillis()+"闹钟无效");
return;
}
Log.i("md", "type "+type);
Intent intent = new Intent(AlarmsSetting.ALARM_ALERT_ACTION);
intent.putExtra("type", type);
intent.setClass(context, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(context, type, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.set(AlarmManager.RTC_WAKEUP, mCalendar.getTimeInMillis(), pi);
alarmsSetting.setNextAlarm(mCalendar.getTimeInMillis());
SimpleDateFormat formatter = new SimpleDateFormat("yyyy年MM月dd日??HH时mm分ss秒SSS毫秒");
Log.e("###########闹钟时间#######", "alarmsSetting.getNextAlarm()" + formatter.format(new Date(alarmsSetting.getNextAlarm())));
}
一行一行看下去就知道,主要是将存储好的时间设置信息(小时,分钟,星期),通过cacluteNextAlarm
方法设置成一个特殊的Calendar
值用于定时,然后将对应的type
和Action
组成一个通过广播pi
!通过AlarmManager
的set
方法定时,mAlarmManager.set(AlarmManager.RTC_WAKEUP, mCalendar.getTimeInMillis(), pi);
,定时将pi
中的内容发送出去!
核心就是这样!
知道了核心,首先需要知道大神是怎么将时间处理成周期信息的! cacluteNextAlarm
方法:
Calendar mCalendar = Calendar.getInstance();
mCalendar.setTimeInMillis(System.currentTimeMillis());
mCalendar.set(Calendar.HOUR_OF_DAY,hour);
mCalendar.set(Calendar.MINUTE, minute);
int differDays = getNextAlarmDifferDays(dayOfweek,mCalendar.get(Calendar.DAY_OF_WEEK), mCalendar.getTimeInMillis());
int nextYear = getNextAlarmYear(mCalendar.get(Calendar.YEAR), mCalendar.get(Calendar.DAY_OF_YEAR), mCalendar.getActualMaximum(Calendar.DAY_OF_YEAR), differDays);
int nextMonth = getNextAlarmMonth(mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH), mCalendar.getActualMaximum(Calendar.DATE), differDays);
int nextDay = getNextAlarmDay(mCalendar.get(Calendar.DAY_OF_MONTH), mCalendar.getActualMaximum(Calendar.DATE), differDays);
mCalendar.set(Calendar.YEAR,nextYear);
mCalendar.set(Calendar.MONTH, nextMonth % 12);//月份
mCalendar.set(Calendar.DAY_OF_MONTH, nextDay);
mCalendar.set(Calendar.SECOND, 0);
mCalendar.set(Calendar.MILLISECOND, 0);
return mCalendar;
}
//获取下次闹钟相差的天?
private static int getNextAlarmDifferDays(int data, int currentDayOfWeek,long timeInMills){
int nextDayOfWeek = getNextDayOfWeek(data, currentDayOfWeek,timeInMills);
return currentDayOfWeek<=nextDayOfWeek?(nextDayOfWeek-currentDayOfWeek):(7 - currentDayOfWeek + nextDayOfWeek);
}
//考虑年进位的情况
private static int getNextAlarmYear(int year,int dayOfYears, int actualMaximum, int differDays) {
int temp = actualMaximum-dayOfYears-differDays;
return temp >= 0?year:year+1;
}
//考虑月进位的情况
private static int getNextAlarmMonth(int month,int dayOfMonth,int actualMaximum, int differDays) {
int temp = actualMaximum-dayOfMonth-differDays;
return temp >= 0?month:month+1;
}
//获取下次闹钟的day
private static int getNextAlarmDay(int thisDayOfMonth, int actualMaximum, int differDays) {
int temp = actualMaximum - thisDayOfMonth-differDays;
if (temp<0){
return thisDayOfMonth + differDays - actualMaximum;
}
return thisDayOfMonth + differDays;
}
//获取下次显示是星期几
private static int getNextDayOfWeek(int data, int cWeek,long timeInMillis) {
int tempBack = data >> cWeek - 1;
int tempFront = data ;
if(tempBack%2==1){
if(System.currentTimeMillis()<timeInMillis) return cWeek;
}
tempBack = tempBack>>1;
int m=1,n=0;
while (tempBack != 0) {
if (tempBack % 2 == 1 ) return cWeek + m;
tempBack = tempBack / 2;
m++;
}
while(n<cWeek){
if (tempFront % 2 == 1) return n+1;
tempFront =tempFront/2;
n++;
}
return 0;
}
大神写的计算的时间周期等方法,理解好了就好!
编号5和6是两个互相配合存储数据的基友,一个实现具体操作,一个提供存取方法!
编号5:
private SharedPreferenceUtil spUtil;
public boolean isShake() {
return spUtil.getBoolean(ALARM_SETTING_SHAKE , true);
}
编号6:
/**
* 读取boolean类型值,默认为false;
*
* @param key
* @return
*/
public boolean getBoolean(String key, boolean deafultValue) {
return sharedPreferences.getBoolean(key, deafultValue);
}
看到这,然后回到编号2类中:
刚刚是从开始结束时间开关一直往下看,就基本打通的这个程序主心轴,其他的基本都是簇拥在这个轴心旁的东西:
设置时间按钮:
public void showTimePickerDialog(final int type){
TimePickerFragment timePicker = new TimePickerFragment();
if(type==AlarmsSetting.ALARM_SETTING_TYPE_IN) {
timePicker.setTime(alarmsSetting.getInHour(),alarmsSetting.getInMinutes());
}else{
timePicker.setTime(alarmsSetting.getOutHour(), alarmsSetting.getOutMinutes());
}
timePicker.show(getFragmentManager(),"timePicker" );
timePicker.setOnSelectListener(new TimePickerFragment.OnSelectListener() {
@Override
public void getValue(int hourOfDay, int minute) {
if(type==AlarmsSetting.ALARM_SETTING_TYPE_IN) {
alarmsSetting.setInHour(hourOfDay);
alarmsSetting.setInMinutes(minute);
}else{
alarmsSetting.setOutHour(hourOfDay);
alarmsSetting.setOutMinutes(minute);
}
setTime(hourOfDay, minute, type);
AlarmOpreation.cancelAlert(AlarmMainActivity.this,type);
AlarmOpreation.enableAlert(AlarmMainActivity.this,type,alarmsSetting);
}
});
}
弹出一个TimePickerFragment
窗口,并将获得的小时和分钟存起来 setInHour``setInMinutes
而这个时间弹出窗口就是编号7:
此类实质上就是继承至DialogFragment
调用TimePickerDialog
向外提供获取小时和分钟的接口!
而星期的周期复杂些,此行星期选项列表是一排GridView
,编号8就是它的Adapter
,在构造方法的GetView
中,可以看出,大神将周一至周日,组成一个二进制数据:
if(v.isSelected()){
selected = selected - (int)(1 << position);
if(selected <= 0) {
selected = selected + (int)(1 << position);
return ;
}
v.setSelected(false);
}else{
selected = selected + (int)(1 << position);
v.setSelected(true);
}
并将最终组成的数据发送给了AlarmOpreation
AlarmOpreation.cancelAlert(context,type);
AlarmOpreation.enableAlert(context, type, alarmsSetting);
这样一路看下来,从设置时间到组成时间到发送时间,基本就差不多了,下面就是编号4AlarmReceiver
接受定时发送过来的广播内容了:
if(intent.getAction().equals(AlarmsSetting.ALARM_ALERT_ACTION) && type !=0) {
...
intent.setClass(context, AlarmAlertActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
判断 ATION
和type
,启动编号1AlarmAlertActivity
,显示对应的开始和结束操作就可以了;
今天感冒了流鼻涕打喷嚏,整理得比较凌乱,看官们将就一下,有什么问题,或者需要源码的可以留言!