40hくらいでデジタル時計ウィジェットを作った話。
アンドロイドで動作するデジタル時計ウィジェット(AppWidget)を作りました。
10月26日頃から作り始めて、それなりに動くまでに40時間くらいかかりました。
レイアウトの微調整をしたり、
あれこれAndroidの仕様を確認してたので、
最終的に60時間くらいかかってると思います・・。
この程度であれば、
仕様が分かってる人なら4時間くらいで作っちゃうんじゃないですかね。
事情
標準のデジタル時計ウィジェットを使ってたんですが、
UTC(世界協定時)が表示されないのが不便だったんです。
俺、こっそりLang-8で英語日記をつけてるんですよ。
たまにメンテナンスで日記が書けなくなるタイミングがあって。
その復旧予定時刻がUTCでアナウンスされるんですね。
そこで「いったい今はUTCで何時なのよ」と思うわけです。
加えて、Facebookにログインしている時の話。
全然知らない外国人からフレンド登録されたり
チャットしたりすることがあるんです。
話すときに相手の地域が何時なのかを知りたかったりね。
「そっちは昼かー。こっちは深夜で、もう寝るからまたね」
みたいな。
だからUTCが常に表示されていてくれるとうれしいのです。
ついでに
バッテリー状況をパーセントで表示できたらうれしいな!と。
一応表示できるウィジェットはたくさんあるけど、
電池のグラフィック使ってたり、
バッテリーの温度とか表示してたり、
無駄に豪華なので邪魔。
その上、バッテリーの状況を表示するだけなのに
「完全なインターネットアクセス」
って権限を要望するのはなんなのこわい。
参考にしたサイト
最近のアプリ開発は、たいがいネットにサンプルソースが転がってるので、
とても楽ちんですね。
http://www.techfirm.co.jp/lab/android/widget.html
↑ウィジェットの作り方
http://www.atmarkit.co.jp/fsmart/articles/android10/android10_3.html
↑Androidのホーム画面に常駐するアプリを作るには
http://www.adakoda.com/android/000140.html
↑バッテリーの情報(Battery information)を取得するには
http://www.adakoda.com/android/000071.html
↑テキストビュー(TextView)を使用するには
http://android-er.blogspot.com/2010/10/update-widget-in-onreceive-method.html
↑Update Widget in onReceive() method
http://side2.jp/2011/04/textview-dropshadow/
↑[TIPS]文字にドロップシャドウを付ける方法
http://boco.hp3200.com/beginner/widget02-4.html
↑バッテリー残量ウィジェットの作り方 (4/4)
以下コード。
package com.amemilitia.KairosForce; import java.util.Date; import java.util.TimeZone; import java.util.regex.*; import com.amemilitia.KairosForce.R; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; import android.os.IBinder; import android.util.Log; import android.widget.RemoteViews; public class KairosForceProvider extends AppWidgetProvider { private static final String ACTION_KAIROS_INTERVAL = "com.amemilitia.KairosForce.INTERVAL"; private static final long INTERVAL = 60 * 1000; //---------------------------------------------------------- private PendingIntent getAlermPendingIntent(Context context){ Intent alarmIntent = new Intent(context, KairosForceProvider.class); alarmIntent.setAction(ACTION_KAIROS_INTERVAL); return PendingIntent.getBroadcast(context, 0, alarmIntent, 0); } //---------------------------------------------------------- private void setInterval(Context context, long interval) { PendingIntent operation = getAlermPendingIntent(context); AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); long now = System.currentTimeMillis(); long oneHourAfter = ((long)(now / interval)) * interval + interval; am.set(AlarmManager.RTC, oneHourAfter, operation); } private void updateClock(Context context){ ComponentName cn = new ComponentName(context, KairosForceProvider.class); RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.main); long now = System.currentTimeMillis(); Date dt = new Date(now); // 表示の更新 java.text.DateFormat fmt; //***************************************** // // ローカル時間の表示 // //***************************************** fmt = android.text.format.DateFormat.getTimeFormat(context); Pattern pt = Pattern.compile("^(\\D*)(\\d+\\:\\d+)(\\D*)$"); Matcher match = pt.matcher(fmt.format(dt)); String prefix = ""; String suffix = ""; String TimeText = ""; if(match.find()){ prefix = match.group(1).trim(); TimeText = match.group(2).trim(); suffix = match.group(3).trim(); } rv.setTextViewText(R.id.PrefixText, prefix); rv.setTextViewText(R.id.TimeText, TimeText); rv.setTextViewText(R.id.SuffixText, suffix); //***************************************** // // ローカル日付の表示 // //***************************************** fmt = android.text.format.DateFormat.getLongDateFormat(context); rv.setTextViewText(R.id.DateText, fmt.format(dt)); //***************************************** // // UTC日付の取得 // //***************************************** fmt = android.text.format.DateFormat.getLongDateFormat(context); fmt.setTimeZone(TimeZone.getTimeZone("UTC")); rv.setTextViewText(R.id.UTCDateText, fmt.format(dt)); //***************************************** // // UTC時間の取得 // //***************************************** fmt = android.text.format.DateFormat.getTimeFormat(context); fmt.setTimeZone(TimeZone.getTimeZone("UTC")); rv.setTextViewText(R.id.UTCTimeText, "UTC / " + fmt.format(dt)); // インターバルの設定 setInterval(context, INTERVAL); AppWidgetManager.getInstance(context).updateAppWidget(cn, rv); return; } @Override public void onEnabled(Context context){ super.onEnabled(context); Log.i("onEnable", "DONE."); return; } @Override public void onUpdate(Context context, AppWidgetManager manager, int ids []){ super.onUpdate(context, manager, ids); // バッテリー情報更新のサービスを開始する Intent intent = new Intent(context, WidgetService.class); if(context.startService(intent) == null){ Log.e("onUpdate", "サービスが登録できなかった!"); } //時計の更新処理 //updateClock(context); setInterval(context, 100); Log.i("onUpdate", "DONE."); return; } //---------------------------------------------------------- public static class WidgetService extends Service { @Override public void onStart(Intent in, int si) { Log.i("onStart", "Begin WidgetService!"); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); registerReceiver(mBroadcastReceiver, filter); } @Override public IBinder onBind(Intent in) { return null; } @Override public void onDestroy(){ unregisterReceiver(mBroadcastReceiver); Log.i("onDestroy", "Stop WidgetService!"); return; } } //---------------------------------------------------------- private static BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_BATTERY_CHANGED) == false) { Log.i("mBroadcastReceiver", action); return; } ComponentName cn = new ComponentName(context, KairosForceProvider.class); RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.main); int status = intent.getIntExtra("status", 0); //int plugged = intent.getIntExtra("plugged", 0); int level = intent.getIntExtra("level", 0); switch (status) { case BatteryManager.BATTERY_STATUS_CHARGING: rv.setImageViewResource(R.id.IconView, R.drawable.plug); break; default: rv.setImageViewResource(R.id.IconView, R.drawable.battery4); break; } rv.setTextViewText(R.id.BatteryText, level + "%" ); AppWidgetManager.getInstance(context).updateAppWidget(cn, rv); } }; @Override public void onReceive(Context context, Intent intent){ if(ACTION_KAIROS_INTERVAL.equals(intent.getAction())){ updateClock(context); Log.i("onReceive", "ACTION_KAIROS_INTERVAL."); } super.onReceive(context, intent); return; } @Override public void onDeleted(Context context, int[] ids){ super.onDeleted(context, ids); Log.i("onDeleted", "DONE."); return; } @Override public void onDisabled(Context context){ // バッテリー情報更新のサービスを停止する Intent intent = new Intent(context, WidgetService.class); context.stopService(intent); //context.unregisterReceiver(mBroadcastReceiver); // 時計更新のタイマーを停止する PendingIntent operation = getAlermPendingIntent(context); AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); am.cancel(operation); Log.i("onDisabled", "DONE."); super.onDisabled(context); return; } }
アプリ名
今、シュタゲ脳になってるのであえて厨二風な名前にしてみました。
時間の神様カイロスと、電力表示するので「力」って意味でフォースをつけて、くっつけました・・。