这次是一个时钟类应用,目前依旧是主要的功能,长得还是很挫。当然了,核心功能是有的……
布局的话,不管是采用FrameLayout或者LinearLayout都可以。
我这里采用了FrameLayout,然后加上一个TabHost,之前在论坛看到有同学提问在WF中这种多个栏目的用什么控件,我的答案是在WF、WPF、Windows App、ASP.NET以及安卓上都是Tab开头的控件。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TabHost
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/tabHost">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
TabWidget>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent">
FrameLayout>
LinearLayout>
TabHost>
FrameLayout>
定义好了TabHost之后就可以在MainAcitivity类中实例化它们了。
private static TabHost tabHost;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tabHost = (TabHost)findViewById(R.id.tabHost);
tabHost.setup();
tabHost.addTab(tabHost.newTabSpec("tabAlarm").
setIndicator("闹钟").setContent(R.id.tabAlarm));
tabHost.addTab(tabHost.newTabSpec("tabTime").
setIndicator("时钟").setContent(R.id.tabTime));
tabHost.addTab(tabHost.newTabSpec("tabTimer").
setIndicator("计时器").setContent(R.id.tabTimer));
tabHost.addTab(tabHost.newTabSpec("tabStopWatch").
setIndicator("秒表").setContent(R.id.tabStopWatch));
}
如果你想调整“闹钟”与“时钟”这些的先后顺序,直接在代码中调整代码执行顺序即可。
首先呢,别的不管,我为大家准备了非常好听的铃声,可惜铃声只能放源码里而不能放博客上……
现在来写一个AlarmView类,到时候在布局文件中可以直接使用它。
.nomasp.com.clock.AlarmView
android:id="@+id/tabAlarm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
"@+id/lvAlarmList"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp">
.nomasp.com.clock.AlarmView>
AlarmView类应该扩展LinearLayout,因为前面说到的,AlarmView是用作布局的。
private AlarmManager alarmManager;
public AlarmView(Context context) {
super(context);
init();
}
public AlarmView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AlarmView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
alarmManager = (AlarmManager)getContext().getSystemService(Context.ALARM_SERVICE);
}
实例化一个闹钟管理类,后面就通过它来使用闹钟服务。
核心部分都在这里了,注释都加了,大家慢慢看吧:
@Override
protected void onFinishInflate(){
super.onFinishInflate();
// 实例化按钮和列表
btnAddAlarm = (Button)findViewById(R.id.btnAddAlarm);
lvAlarmList = (ListView)findViewById(R.id.lvAlarmList);
// 实例化适配器
adapter = new ArrayAdapter(getContext(),
android.R.layout.simple_list_item_1);
// 为列表添加适配器
lvAlarmList.setAdapter(adapter);
// 保存闹钟列表
readSavedAlarmList();
// 添加闹钟的监听事件
btnAddAlarm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 添加闹钟
addAlarm();
}
});
// 闹钟列表的监听事件
lvAlarmList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view,
final int position, long id) {
// 单击其中一项会调出对话框,其中有“删除”和“全部删除”两项可以选择
new AlertDialog.Builder(getContext()).setTitle("操作选项").setItems(
new CharSequence[]{"删除", "全部删除"}, new DialogInterface.
OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
// 对话框中的项索引从0开始,删除选中项
deleteAlarm(position);
break;
case 1:
// 删除所有项,实际App中并不常用,只是因为测试过程中添加了太多闹钟。
deleteAllAlarm();
break;
default:
break;
}
}
}
).setNegativeButton("取消", null).show(); // 为对话框添加一个取消的按钮
return true;
}
});
}
上面用到的addAlarm方法,这里也列出来了。注意弹出的时间选择对话框会因安卓系统版本而不同,我GIF中的是Android 5.0的。
// 添加闹钟
private void addAlarm(){
// 实例化一个Calendar类,并取当前时间
Calendar c = Calendar.getInstance();
// 弹出时间选择对话框
new TimePickerDialog(getContext(),new TimePickerDialog.OnTimeSetListener(){
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute){
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY,hourOfDay);
calendar.set(Calendar.MINUTE,minute);
calendar.set(Calendar.SECOND,0);
calendar.set(Calendar.MILLISECOND,0);
// 当前时间
Calendar currentTime = Calendar.getInstance();
// 如果设置的闹钟时间比当前时间还小,你不能将闹钟定在过去咯?所以令其往前加一天
if(calendar.getTimeInMillis() <= currentTime.getTimeInMillis()){
calendar.setTimeInMillis(calendar.getTimeInMillis() + 24*60*60*1000);
}
// 实例化一个AlarmData类
AlarmData ad = new AlarmData(calendar.getTimeInMillis());
// 为适配器添加数据
adapter.add(ad);
// 将数据设置到闹钟管理中
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, // 唤醒
ad.getTime(), // 闹钟时间
5*60*1000,
PendingIntent.getBroadcast(getContext(),
ad.getId(), // 请求码
new Intent(getContext(),
AlarmReceiver.class),
0));
// 添加之后记得保存闹钟列表
saveAlarmList();
}
},c.get(Calendar.HOUR_OF_DAY),c.get(Calendar.MINUTE), true).show();
}
下面的是保存闹钟列表的方法,并没有什么难度,重点在最后的if判断。
// 保存闹钟列表
private void saveAlarmList(){
Editor editor = getContext().getSharedPreferences(
AlarmView.class.getName(),
Context.MODE_PRIVATE).edit();
StringBuffer sb = new StringBuffer();
for(int i = 0; i < adapter.getCount(); i++){
sb.append(adapter.getItem(i).getTime()).append(",");
}
if(sb.length() > 1){
String content = sb.toString().substring(0,sb.length()-1); // 去掉最后一个逗号
editor.putString(KEY_ALARM, content);
System.out.println(content); // 调试使用
}else{ // 如果长度为空,也要提交一个空的上去
editor.putString(KEY_ALARM, null);
}
// 记得提交
editor.commit();
}
应用不能每次关掉后数据就没有了,需要将它们保存起来,因此也需要能够加载保存的数据。
// 读取保存的闹钟列表,在每次加载应用的时候会调用它
private void readSavedAlarmList(){
// 实例化共享首选项
SharedPreferences sp = getContext().getSharedPreferences(
AlarmView.class.getName(), Context.MODE_PRIVATE);
String content = sp.getString(KEY_ALARM, null);
if(content != null){
String[] timeStrings = content.split(",");
// 遍历每一个字符串,并将其添加到适配器中
for(String str : timeStrings){
adapter.add(new AlarmData(Long.parseLong(str)));
}
}
}
删除闹钟的方法,重点在于:1,在AlarmManager中取消闹钟;2,不能在for循环中动态计算适配器大小。
// 删除闹钟,传入一个position位置,删除指定项
private void deleteAlarm(int position){
// 根据传入的位置参数实例化一个AlarmData
AlarmData ad = adapter.getItem(position);
// 从适配器中移除
adapter.remove(ad);
// 删除后记得再次保存列表
saveAlarmList();
// 记得在闹钟管理中将其取消掉,否则删除后闹钟依旧会激活
alarmManager.cancel(PendingIntent.getBroadcast(getContext(),ad.getId(),
new Intent(getContext(), AlarmReceiver.class),0));
}
// 删除所有闹钟
private void deleteAllAlarm(){
// 获取适配器中的闹钟数量
int adapterCount =adapter.getCount(); // 为adapter的个数进行计数
AlarmData ad;
// 因为每次删除后适配器的数量都会改变,所以需要在上面一次性计算好,不能将其放到for循环中计算
for(int i = 0; i < adapterCount; i++){
// 此处括号中不能填i,因为每次移除后第二项又变成第一项
ad = adapter.getItem(0); // 每次从第1个开始移除
adapter.remove(ad);
saveAlarmList(); // 移除后重新保存列表
alarmManager.cancel(PendingIntent.getBroadcast(getContext(),ad.getId(),
new Intent(getContext(),AlarmReceiver.class),0)); // 取消闹钟的广播
}
}
最后就要设置一个AlarmData类,其中设置方法获取闹钟的请求码。
private static class AlarmData{
private String timeLabel = "";
private long time = 0;
private Calendar date;
public AlarmData(long time){
this.time = time;
date = Calendar.getInstance();
date.setTimeInMillis(time);
timeLabel = String.format("%d月%d日 %d:%d",
date.get(Calendar.MONTH)+1,
date.get(Calendar.DAY_OF_MONTH),
date.get(Calendar.HOUR_OF_DAY),
date.get(Calendar.MINUTE));
}
public long getTime(){
return time;
}
public String getTimeLabel(){
return timeLabel;
}
// 设置唯一的请求码
public int getId(){
return (int)(getTime()/1000/60);
}
// 重载头String()方法
@Override
public String toString(){
return getTimeLabel();
}
}
现在来写一个PlayAlarmAty类,它得扩展自Activity。
private MediaPlayer mp;
Button btnAlarmPause;
Button btnAlarmReset;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.alarm_player_aty);
mp = MediaPlayer.create(this,R.raw.ringtone);
mp.start();
btnAlarmPause = (Button)findViewById(R.id.btnAlarmPause);
btnAlarmPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onPause();
}
});
btnAlarmReset = (Button)findViewById(R.id.btnAlarmReset);
btnAlarmReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onDestroy();
}
});
}
@Override
protected void onPause() {
super.onPause();
mp.pause();
mp.release();
}
@Override
protected void onDestroy() {
super.onDestroy();
mp.stop();
mp.release();
}
其对应的布局是:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/play_sound"
android:layout_gravity="center_horizontal"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pause"
android:id="@+id/btnAlarmPause"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reset"
android:id="@+id/btnAlarmReset"/>
LinearLayout>
LinearLayout>
这是在闹钟激活是需要弹出的界面,所以你需要一个接收器。
AlarmReceiver类需要扩展自BroadcastReceiver,重载onReceive方法:
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("闹钟执行了!"); // 作调试用
// 闹钟管理
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// 取消闹钟
am.cancel(PendingIntent.getBroadcast(context, getResultCode(),
new Intent(context, AlarmReceiver.class), 0));
// 实例化一个Intent,启动该Activity
Intent i = new Intent(context, PlayAlarmAty.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
别放了在清单文件中添加它们……(在application下)
<receiver android:name=".AlarmReceiver">
receiver>
<activity android:name=".PlayAlarmAty" android:screenOrientation="fullSensor">
activity>
哦对了,忘了说,需要用到的string都放在strings.xml,这个就不贴出来了。
因为还是比较长的,所以就分成3篇来写好了。
准备的铃声挺好听的,至少比代码好听,哈哈……不管是需要铃声还是需要代码的,直接评论留邮箱吧,我就不上传到CSDN资源了。代码会继续更新的,注释也会继续更新……
项目也上传到Github了,欢迎大家贡献代码啊——传送门 。
后面还有两篇哦,大家快跟上……Android 开发第六弹:简易时钟(计时器) 和 Android 开发第七弹:简易时钟(秒表) 。