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

还是先从简单的开始吧:

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

private void updateAlarm(boolean enabled,
            Alarm alarm) {
        Alarms.enableAlarm(this, alarm.id, enabled);
        if (enabled) {
            SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes,
                    alarm.daysOfWeek);
        }
 }
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方法中,如下:

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状态。
     }
 });
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所代码布局代码贴上来,如下:


<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>
<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布局的背景。

代码如下:

/**
 * 这是一个允许其父类本身被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);
    }
}
/**
 * 这是一个允许其父类本身被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的签名吧,

public abstract class CursorAdapter extends BaseAdapter implements Filterable,
        CursorFilter.CursorFilterClient {
}
public abstract class CursorAdapter extends BaseAdapter implements Filterable,
        CursorFilter.CursorFilterClient {
}

 

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


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

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

/**
     * @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;
    }
/**
     * @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)

    代码如下:

@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;
}
@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)

@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);
            }
        }
    };
@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);
            }
        }
    };

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


下面的布局代码:

<!-- 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>
<!-- 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的子类。


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

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

 

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


(3)onCreate(Bundle icicle)

代码如下:

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

        mFactory = LayoutInflater.from(this);
        mPrefs = getSharedPreferences(PREFERENCES, 0);
        mCursor = Alarms.getAlarmsCursor(getContentResolver());

        updateLayout();
    }
@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()

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

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);
        }
    }
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()

方法如下:

@Override
    protected void onDestroy() {
        super.onDestroy();
        ToastMaster.cancelToast();
        if (mCursor != null) {
            mCursor.close();
        }
    }
@Override
    protected void onDestroy() {
        super.onDestroy();
        ToastMaster.cancelToast();
        if (mCursor != null) {
            mCursor.close();
        }
    }

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

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

 

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


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

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

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