安卓小專案實戰之--定時提醒備忘錄
寫在前面:
安卓的學習也有半年多了,期間也曾寫過部落格,但大多都是一些瑣碎的筆記,基本沒用任何參考價值,這幾天閒來無事,便想做個小專案來玩玩,鞏固一下基本知識,並且完整的記錄下來整個開發的過程,以作留念。
/————————我是華麗的分割線—————————-/
本次專案選擇了可定時提示的備忘錄。原理是利用系統每分鐘傳送一條時間改變的廣播,通過接受這條廣播來判斷是否為使用者設定的時間,如果是則與使用者互動提醒使用者。
首先,在Eclipse中建立一個Android專案,取名為Notification
類:MainActivity.java
此Activity主要用於使用者新增備忘錄頁面的轉跳與已新增備忘錄的展示。
在其佈局檔案activity_main.xml中加入一個Button和一個TextView 。目前還沒有實現多事件的共同設定,所以暫時使用TextView顯示事件。
因為儲存的資料比較少,這裡採用SharedPreferences進行儲存,在Activity的onCreate()方法中進行成員變數的初始化操作:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
sp = getSharedPreferences("UserNote",MODE_PRIVATE);
}
public void init() {
button = (Button) findViewById(R.id .bt);
title = (TextView) findViewById(R.id.main_title);
note = (TextView) findViewById(R.id.main_note);
time = (TextView)findViewById(R.id.main_time);
}
在onStart()中對按鈕bt新增點選事件監聽,並通過startActivity(intent)啟動AddActivity轉跳到新增的Activity:
protected void onStart() {
super .onStart();
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Intent intent = new Intent(MainActivity.this, AddActivity.class);
startActivity(intent);
}
});
}
在onResume()方法中,獲取SharedPerfences中的資料並更新TextView的內容:
protected void onResume(){
super.onResume();
strTime = sp.getString("time","null");
strTitle = sp.getString("title","null");
strNote = sp.getString("text","null");
title.setText(strTitle);
note.setText(strNote);
time.setText(strTime);
System.out.println("onResume");
}
MainActivity.java整體程式碼如下:
package com.example.notification;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button button;
private SharedPreferences sp;
private TextView title, note,time;
private String strTitle,strNote,strTime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
sp = getSharedPreferences("UserNote",MODE_PRIVATE);
}
@Override
protected void onStart() {
super.onStart();
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Intent intent = new Intent(MainActivity.this, AddActivity.class);
startActivity(intent);
}
});
}
protected void onResume(){
super.onResume();
strTime = sp.getString("time","null");
strTitle = sp.getString("title","null");
strNote = sp.getString("text","null");
title.setText(strTitle);
note.setText(strNote);
time.setText(strTime);
System.out.println("onResume");
}
public void init() {
button = (Button) findViewById(R.id.bt);
title = (TextView) findViewById(R.id.main_title);
note = (TextView) findViewById(R.id.main_note);
time = (TextView)findViewById(R.id.main_time);
}
}
該Activity佈局檔案activity_main.xml如下
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.notification.MainActivity"
android:background="#fcf3ea" >
<Button
android:id="@+id/bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="新增備忘錄"
android:textSize="20dp" />
<TextView
android:id="@+id/setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/bt"
android:textSize="20dp"
android:text="" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/setting"
android:layout_toRightOf="@+id/setting"
android:orientation="vertical" >
<TextView
android:id="@+id/main_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="" />
<TextView
android:id="@+id/main_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="" />
<TextView
android:id="@+id/main_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="" />
</LinearLayout>
</RelativeLayout>
類:AddActivity.java
該Activity為使用者新增備忘錄事件的Activity,使用者通過EditText和DatePicker和TimePicker進行資料的輸入,單擊按鈕由SharedPerfences記錄使用者輸入的資料.在onCreate()中進行初始化,在onStart()中對按鈕新增監聽,並且將時間選擇器和使用者輸入的內容存入SharedPerfences,這裡應當注意的是,時間選擇器選擇的資料如果為一位數如1月 則記錄的日期為1 並非01,這點與SimpleDateFormat生成的不一致,所以我們需要自己建立一個類來規範我們的時間,這個類取名為MyTime,在儲存完畢使用者鍵入的資料以後,Activity會啟動一個服務NotifyService,並且呼叫finish()關閉自身。
AddActivity程式碼如下:
package com.example.notification;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.TimePicker;
import android.widget.Toast;
public class AddActivity extends Activity {
private Button conButton;
private TimePicker tp;
private DatePicker dp;
private EditText etTitle, etNote;
private String title, text;
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add);
init();
}
protected void onStart() {
super.onStart();
conButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
saveTime();
}
});
}
@SuppressLint("NewApi")
public void init() {
conButton = (Button) findViewById(R.id.add_bt_confirm);
tp = (TimePicker) findViewById(R.id.timePicker1);
dp = (DatePicker) findViewById(R.id.datePicker1);
etTitle = (EditText) findViewById(R.id.add_et_title);
etNote = (EditText) findViewById(R.id.add_et_note);
tp.setIs24HourView(true);
dp.setCalendarViewShown(false);
sp = getSharedPreferences("UserNote", MODE_PRIVATE);
}
public void saveTime() {
text = etNote.getText().toString();
title = etTitle.getText().toString();
int day = dp.getDayOfMonth();
int mon = dp.getMonth()+1;//getMonth 返回從0開始
System.out.println("setMon is"+mon);
int year = dp.getYear();
int hour = tp.getCurrentHour();
int min = tp.getCurrentMinute();
SimpleDateFormat sdf = new SimpleDateFormat("HHmm");
String time = MyTime.getTime(year,mon,day,hour, min);
System.out.println(time);
SharedPreferences.Editor editor = sp.edit();
editor.putString("text", text);
editor.putString("time", time);
editor.putString("title",title);
editor.commit();
System.out.println(time);
Intent intent = new Intent(AddActivity.this, NotifyService.class);
startService(intent);
Log.i("ADD","intent完畢");
finish();
}
}
MyTime.java如下
package com.example.notification;
public class MyTime {
public MyTime() {
// TODO 自動生成的建構函式存根
}
public static String getTime(int year,int mon,int day,int hour, int min) {
String mins,mons,days,hours;
if(hour<10)
hours = 0+String.valueOf(hour);
else
hours = String.valueOf(hour);
if(min<10)
mins =0+ String.valueOf(min);
else
mins = String.valueOf(min);
if(mon<10)
mons =0+ String.valueOf(mon);
else
mons = String.valueOf(mon);
if(day<10)
days = 0+String.valueOf(mon);
else
days = String.valueOf(day);
return year+":"+mons+":"+days+":"+hours+":"+mins;
}
}
服務NotifyService.java
新建服務需要在AndroidManifest.xml進行註冊,在節點下加入以下程式碼進行註冊
<service android:name=".NotifyService" ></service>
該服務主要功能為判斷當前時間與使用者儲存時間是否一致,並進行相應動作,在onCreate()方法動態註冊一個廣播接收器TimeRecevier,該廣播接收器接收系統時間變化的廣播,該廣播每分鐘由系統發出一次,並在OnDestroy()方法中登出該廣播接收器。具體ACTION如下(要接收該廣播,必須進行動態註冊):
private String ACTION = Intent.ACTION_TIME_TICK;
在onStartCommand()方法中,利用SimpleDateFormat將當前系統時間格式化與本地儲存一致的格式,當前時間可以通過new Date()獲取(new Date()實質上就是System.currentTimeMillis());然後進行時間的比對,如果一致,則通知欄通知使用者,並且啟動一個可以在鎖屏狀態下喚醒螢幕的Activity。這裡值得注意的是,在服務中啟動一個Activity必須為呼叫的intent新增一個Flag如下:
ActIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
最後呼叫stopSelf()方法,關閉自身.
NotifyService.java程式碼如下:
package com.example.notification;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.util.Log;
public class NotifyService extends Service {
private NotificationManager nm;
private boolean isRec = false;
private boolean isFirst = true;
private String ACTION = Intent.ACTION_TIME_TICK;
private SharedPreferences sp;
private IntentFilter ifter;
private TimeReceiver receiver;
@Override
public IBinder onBind(Intent arg0) {
// TODO 自動生成的方法存根
return null;
}
@Override
public void onCreate() {
// TODO 自動生成的方法存根
super.onCreate();
sp = getSharedPreferences("UserNote", MODE_PRIVATE);
nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
ifter = new IntentFilter();
ifter.addAction(ACTION);
receiver = new TimeReceiver();
if (isRec == false) {
registerReceiver(receiver, ifter);
isRec = true;
}
Log.i("Service","onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
System.out.println("服務拜拜");
unregisterReceiver(receiver);
}
@SuppressWarnings("deprecation")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO 自動生成的方法存根
Log.i("Service","onStart");
if (isFirst) {
isFirst = false;
} else {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy:MM:dd:HH:mm");
String curTime = sdf.format(new Date());
System.out.println(curTime);
String time =sp.getString("time", "0000");
System.out.println("curTime"+curTime+" setTime"+time);
if (curTime.equals(time)) {
String text = sp.getString("text", "預設事件");
System.out.println("onStart");
Notification notify = new Notification(R.drawable.ic_launcher,
"小貼士", System.currentTimeMillis());
PendingIntent pi = PendingIntent.getActivity(this,0,
new Intent(this,MainActivity.class),0);
notify.setLatestEventInfo(this, "小貼士提醒您", time+" "+text, pi);
notify.defaults = Notification.DEFAULT_ALL;
nm.notify(1044, notify);
Intent ActIntent = new Intent(this,DialogActivity.class);
ActIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(ActIntent);
stopSelf();
}
}
return super.onStartCommand(intent, flags, startId);
}
}
類,廣播接收器:TimeReceiver.java
整個接收器的作用只有一個,就是每次接收到廣播,便啟動服務NotifyService。
TimeReceiver.java如下
package com.example.notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class TimeReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("recevice");
context.startService(new Intent(context,NotifyService.class));
}
}
類DialogActivity.java
該Activity為使用者預設的時間到了通知使用者的Activity,該Activity有如下特性:
1.可以點亮螢幕。
2.可以在鎖屏狀態下啟動。
實現這兩個特點並不困難,只需要加入這兩句話:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
另外,值得一提的是,QQ客戶端的鎖屏聊天視窗就是這樣實現的(如圖):
這個視窗看似是在彈窗之上。其實本身只是一個Activity,玄機就在他的背景圖片,他是一個以系統桌布為背景的沒有標題欄的Activity。因此為了不讓Activity突然彈出看起來那麼突兀,我們要自定義一個Theme。在AndroidManifest.xml的節點下加入以下程式碼
<activity
android:name=".DialogActivity"
android:label="提醒您"
android:theme="@android:style/Theme.Wallpaper.NoTitleBar"
>
在res/values/styles.xml加入自定義的Style
<style name="FullScreenTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
</style>