学习Android闹钟源代码(三)-AlarmClock类分析(part2)

接上一篇博文:``继续分析AlarmClock类的各个方法:

还是先从简单的开始吧:

(1)updateAlarm(),代码如下:

?
1
2
3
4
5
6
7
8
private void updateAlarm( boolean enabled,
            Alarm alarm) {
        Alarms.enableAlarm( this , alarm.id, enabled);
        if (enabled) {
            SetAlarm.popAlarmSetToast( this , alarm.hour, alarm.minutes,
                    alarm.daysOfWeek);
        }
}

更新Alarm状态。

值得注意的上,在前一篇博文也出现了Alarms.enableAlarm()方法,

但是其实这个enableAlarm()方法实际上是根据enabled的状态来更新alarm的。

并不是单纯的启用alarm的。

如果enabled参数为false的话,实际是停用些alarm,

上面的方法,在设置的alarm状态之后。如果是启用的话,弹出一条Toast来提醒用户。

这个方法在用户点击闹钟列表项的选择框时调用。是一个点击事件的回调函数,在AlarmTimeAdapter中的bindView方法中,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
View indicator = view.findViewById(R.id.indicator);
 
// 为选择框设置初始的状态
// Set the initial state of the clock "checkbox"
final CheckBox clockOnOff =
(CheckBox) indicator.findViewById(R.id.clock_onoff);
clockOnOff.setChecked(alarm.enabled);
 
// 在单选框的外部(指单选框周围的margin部分)单击也应该改变状态。
// Clicking outside the "checkbox" should also change the state.
  indicator.setOnClickListener( new OnClickListener() {
      public void onClick(View v) {
          clockOnOff.toggle(); // 切换选择框状态
          updateAlarm(clockOnOff.isChecked(), alarm); //更新alarm状态。
      }
  });

我觉得很有必要将上面的R.id.indicator所代码布局代码贴上来,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<com.android.deskclock.DontPressWithParentLayout
     android:id= "@+id/indicator"
     style= "@style/alarm_list_left_column"
     android:gravity= "center"
     android:orientation= "vertical" >
     <CheckBox
         android:id= "@+id/clock_onoff"
         android:focusable= "false"
         android:clickable= "false"
         android:duplicateParentState= "true"
         android:layout_height= "wrap_content"
         android:layout_width= "wrap_content"
         android:layout_gravity= "center" />
</com.android.deskclock.DontPressWithParentLayout>

上面的布局使用了一个自定义的布局类,

这个自定义的布局要达到的效果是,当你点击CheckBox与DontPressWithParentLayout之间的区域时,不需要引发其它的视觉效果,即不会改变整个DontPressWithParentLayout布局的背景。

代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
  * 这是一个允许其父类本身被pressed但是不需要正在被pressed的状态。
   这样一来,在alarm列表的时间可以被pressed但不需要改变indicator的背景。
===========================
  * Special class to to allow the parent to be pressed without being pressed
  * itself. This way the time in the alarm list can be pressed without changing
  * the background of the indicator.
  */
public class DontPressWithParentLayout extends LinearLayout {
 
     public DontPressWithParentLayout(Context context, AttributeSet attrs) {
         super (context, attrs);
     }
 
     @Override
     public void setPressed( boolean pressed) {
         // 如果父类类被pressed不要设置为pressed
         // If the parent is pressed, do not set to pressed.
         if (pressed && ((View) getParent()).isPressed()) {
             return ;
         }
         super .setPressed(pressed);
     }
}

(2)CursorAdapter子类AlarmTimeAdapter分析。

   CursorAdapter类的简单介绍。

相信我们都对于BaseAdapter比较了解了吧,

那么先来看看CursorAdapter的签名吧,

?
1
2
3
public abstract class CursorAdapter extends BaseAdapter implements Filterable,
         CursorFilter.CursorFilterClient {
}

 

当我们扩展BaseAdapter的时候,我们主要是要重写BaseAdapter#getView()方法。

但是在CursorAdapter中这个方法它已经帮我们实现了,但是让我们去实现另外两个方法。

先看下CursorAdapter中的getView()方法吧,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
     * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
     */
    public View getView( int position, View convertView, ViewGroup parent) {
        if (!mDataValid) {
            throw new IllegalStateException( "this should only be called when the cursor is valid" );
        }
        if (!mCursor.moveToPosition(position)) {
            throw new IllegalStateException( "couldn't move cursor to position " + position);
        }
        View v;
        if (convertView == null ) {
            v = newView(mContext, mCursor, parent);
        } else {
            v = convertView;
        }
        bindView(v, mContext, mCursor);
        return v;
    }

上面代码的逻辑也是我们通过实现getViw()的逻辑 ,先是对数据进行错误检查。

然后,如果view为空则调用newView()构造一个view,否则使用之前构造的。

然后调用bindView()将数据绑定上去。OK,如果使用过BaseAdapter的话是不是很熟悉啊!

那下面再来仔细看看,newView()和bindView()这两个方法吧。

  (2.1)public View newView(Context context,Cursor cursor,ViewGroup parent)

    代码如下:

?
1
2
3
4
5
6
7
8
9
@Override
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;
}

  首先进入我们眼中的是DigitalClock,看下DigitalClock的类型,如下:

public class DigitalClock extends RelativeLayout

哦,原来是一个RelativeLayout的子类。对于DigitalClock我们就暂时了解这么多,后面会单独分析。

 (2.2) public void bindView(View view, Context context, Cursor cursor)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    @Override
     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 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.
         indicator.setOnClickListener( new OnClickListener() {
             public void onClick(View v) {
                 clockOnOff.toggle();
                 updateAlarm(clockOnOff.isChecked(), alarm);
             }
         });
 
         // 上面部分的代码上updateAlarm()时已经有比较详细的分析了。
         DigitalClock digitalClock =
                 (DigitalClock) view.findViewById(R.id.digitalClock);
 
         // 设置闹钟显示的标签
         final Calendar c = Calendar.getInstance();
         c.set(Calendar.HOUR_OF_DAY, alarm.hour);
         c.set(Calendar.MINUTE, alarm.minutes);
         digitalClock.updateTime(c);
 
         // 设置重要闹钟的时间,如果不重要则不显示
         // 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(AlarmClock. 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);
         }
     }
};

上面的代码也比较清晰简单,无非就是,如果对应内容不为空的话,则设置并显示,否则隐藏对应控件。

下面的布局代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!-- A layout that displays the time.  Shows time, am/pm ( if 12 -hour),
        and an optional line below, used for day/days of week -->
   <com.android.deskclock.DigitalClock android:id= "@+id/digitalClock"
       android:layout_width= "wrap_content"
       android:layout_height= "match_parent"
       android:gravity= "center_vertical"
       android:layout_weight= "1"
       android:layout_gravity= "center_vertical"
       android:orientation= "vertical"
       android:paddingLeft= "16dip"
       android:paddingRight= "16dip" >
 
       <LinearLayout
           android:id= "@+id/time_wrapper"
           android:layout_width= "match_parent"
           android:layout_height= "wrap_content"
           android:baselineAligned= "true" >
 
           <com.android.deskclock.AndroidClockTextView
               android:id= "@+id/timeDisplay"
               android:layout_width= "wrap_content"
               android:layout_height= "wrap_content"
               android:paddingRight= "6dip"
               android:textAppearance= "?android:attr/textAppearanceMedium"
               useClockTypeface= "false" />
 
           <com.android.deskclock.AndroidClockTextView
               android:id= "@+id/am_pm"
               android:layout_width= "wrap_content"
               android:layout_height= "wrap_content"
               android:textAppearance= "?android:attr/textAppearanceMedium"
               android:paddingRight= "10dip"
               android:textStyle= "bold"
               useClockTypeface= "false" />
 
           <TextView android:id= "@+id/label"
               android:layout_width= "0dip"
               android:layout_height= "wrap_content"
               android:layout_weight= "1"
               android:paddingLeft= "8dip"
               android:textAppearance= "?android:attr/textAppearanceSmall"
               android:textColor= "?android:attr/textColorSecondary"
               android:textStyle= "bold"
               android:gravity= "right"
               android:singleLine= "true" />
 
       </LinearLayout>
 
       <TextView android:id= "@+id/daysOfWeek"
           android:layout_width= "match_parent"
           android:layout_height= "wrap_content"
           android:layout_below= "@id/time_wrapper"
           android:paddingTop= "2dip"
           android:textAppearance= "?android:attr/textAppearanceSmall"
           android:textColor= "?android:attr/textColorTertiary"
           />
 
   </com.android.deskclock.DigitalClock>

上面布局代码中,有用到了com.android.deskclock.AndroidClockTextView 这个扩展了TextView的子类。

而这个使用自定义子类的目的是为了使用专门的字体。。

?
1
2
3
4
/**
  * 使用特殊的AndroidClock字体来显示文本。
  */
public class AndroidClockTextView extends TextView

 

要了,到最这个类的最后时刻了,

(3)onCreate(Bundle icicle)

代码如下:

?
1
2
3
4
5
6
7
8
9
10
@Override
protected void onCreate(Bundle icicle) {
     super .onCreate(icicle);
 
     mFactory = LayoutInflater.from( this );
     mPrefs = getSharedPreferences(PREFERENCES, 0 );
     mCursor = Alarms.getAlarmsCursor(getContentResolver());
 
     updateLayout();
}

 我上网查了下icicle的意思,如下:

icicle  冰柱,冰棍儿

popsicle, ice-sucker, ice lolly, icicle

呵呵,我想我们以后命名也可以多用吃的来命名,这样程序是不是会吸引人些呢?

上面方法的代码,比较清楚。

 LayoutInflater.from(this)来获得LayoutInflater对象。

当然也可以通过getSystemService()来获得。

然后是获得当前偏好文件管理对象。getSharedPreferences(PREFERNCES,0);

0是默认的打开模式,即等于

MODE_PRIVATE

 

然后从Alarm的Dao类中,即从Alarms类中获得所有的Alarm的Cursor对象。

Alarms会在我后面的博文中详细分析。

然后就是,

(3)updateLayout()

代码,比较长,但是还是清楚明白的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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);
         if (addAlarm != null ) {
             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);
                     }
             });
         }
 
         View doneButton = findViewById(R.id.done);
         if (doneButton != null ) {
             doneButton.setOnClickListener( new View.OnClickListener() {
                 public void onClick(View v) {
                     finish();
                 }
             });
         }
 
         View settingsButton = findViewById(R.id.settings);
         if (settingsButton != null ) {
             settingsButton.setOnClickListener( new View.OnClickListener() {
                 public void onClick(View v) {
                     startActivity( new Intent(AlarmClock. this , SettingsActivity. class ));
                     finish();
                 }
             });
         }
 
         ActionBar actionBar = getActionBar();
         if (actionBar != null ) {
             actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);
         }
     }

方法第1行,设置布局资源。

第2行获得闹钟ListView对象。

第3行,构造AlarmTimeAdapter,

第4行,为listView对象设置adapter

 第5行,listView启用ListView的垂直滑动条。

第6行,设置listView中闹钟项的单击事件。

第7行,设置listView中闹钟的长按监听。

下面的代码,基本是同样的模式,

查找某一个View,如果不为空则设置点击事件。

然后有一个获得ActionBar,如果actionBar不为空的话,

 actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);

这个方法的特点是,前面所指定的选项呢,是将要设置的,但是如果前面第一参数里出现了选项,没有出现在第二个参数中,那么此选项将被认为是disable的。

ActionBar是android3之后才新出的组件。值得我们去学习。使用。

(4)onDestroy()

方法如下:

?
1
2
3
4
5
6
7
8
@Override
protected void onDestroy() {
     super .onDestroy();
     ToastMaster.cancelToast();
     if (mCursor != null ) {
         mCursor.close();
     }
}

上面方法,主要执行两个操作,将数据库的Cursor关闭(如果不为空的话)

取消Toast。ToastMaster是此应用自定义的一个封装类,代码比较简单。

 

一般在我们使用数据库的时候,都应该在最后正确的关闭数据库。

上面的方法就是这样做的。

到此这个类的分析,暂时到这里了。

其中还有一些地方肯定没有分析得很好,希望各位如果看到了这里,发现不足,欢迎给我些指点。谢谢

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序邦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值