Lintによるチェックは頼もしいものですが、ちょっと厳しい時があります。文字列のローカライズをしていて、ローカライズが必要ない文字列にまで、『is not translated』などと怒られてしまいます。
ローカライズが不要な文字列には、translatable=”false” をつけることでエラーを抑制出来ます。
[xml]
[/xml]
もしくは、XMLファイルの中で Lint 警告を抑制する機能を使います。次のように抑制します。
[xml]
….
[/xml]
これで抑制できました。
参考URL
http://tools.android.com/recent/ignoringlintwarnings
Nexus7が待望のホーム画面横対応!Androido 4.1.2 System Update
Nexus7にアップデートが来てます。どんなんがアップデートされるんかなーと更新内容を読んでみるとパフォーマンスとスタビリティが向上し、バグフィックスもなされているとの事。
更に読んでいくと、
『This update also enables rotation of the Home Screen to landscape orientation.』
おおー待ってました、ホームスクリーン横表示!これは早速アップデートです。
パスワード入力や、フェイスアンロックの画面も横対応です。
縦だとこんな感じなのが、、、
横にするとほらこの通り!
これでroot化してゴニョゴニョして横表示を有効にする必要がなくなりますね!
Androidのエミュレータからプリインストールアプリを削除する
Androidのプリインストールアプリを削除したい時ってありませんか?たとえば、プリインストールアプリの動作を確認したいと思ったら、Googleからソースコードを取ってきて、デバッグ実行すれば良いわけですが、そのとき既にアプリケーションがエミュレータにインストールされていたらインストール&実行に失敗します。
そういう時は、アプリケーションをアンインストールしてしまえば良いわけですが、プリインストールアプリはアプリケーションの一覧にも表示されないので、普通にはアンインストール出来ません。
そこで、コマンドラインから削除しなければなりません。今回、カレンダーアプリケーションの動作を確認したいので、既にインストールされているカレンダーを削除してしまいます。手順は簡単。以下のコマンドでエミュレータからアプリケーションを削除できます。
[shell]
adb shell mount -o remount,rw /dev/block/mtdblock3 /system
adb shell chmod 777 /system/app/Calendar.apk
adb shell ls -la /system/app/
adb shell rm /system/app/Calendar.apk
adb shell rm /system/app/Calendar.odex
adb uninstall com.android.calendar
[/shell]
これで、新たにアプリケーションをインストール出来るようになりました。あとはGoogleからカレンダーアプリケーションのソース一式を取得してEclipseに突っ込んで、デバッグ実行すればOKです。
カスタムコンポーネントでCalendarViewを作成
AndroidではViewを自作することができます。独自に画面の描画を行った完全に新規のカスタムビューを作ることも、既存のViewを組み合わせたカスタムコンポーネントを作ることもできます。
今回は、既存のViewを組み合わせたカスタムコンポーネントを作成してみましょう。自前のCalendarViewを作成します。
まず、作成したビューは次のようにレイアウトファイルにクラス名で記述します。
[xml]
[/xml]
com.example.calendarsample.CalendarView.java を作成していきます。
カスタムコンポーネントを作成する場合、複数のViewを組み合わせることになると思いますので、ViewGroup(レイアウト)を継承しましょう。
今回のカスタムカレンダーコンポーネントでは縦に並べたいのでLinearLayoutを継承したクラスを作成します。
public class CalendarView extends LinearLayout { .... }
コンストラクタを用意します。レイアウトXMLで使用する場合は、(Context context, AttributeSet attrs)をオーバーライドする必要があります。
[java]
public CalendarView(Context context, AttributeSet attrs) {
super(context, attrs);
}
[/java]
コンストラクタ内部でビューを作成して、ビュー自身に追加していきます。カスタムコンポーネントの作成はこれだけです。実に簡単ですね!
[java]
private TextView mTitle;
public CalendarView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOrientation(VERTICAL);
mTitle = new TextView(context);
addView(mTitle, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
[/java]
では、カレンダーのカスタムコンポーネントを作成してみましょう。
- カレンダーのタイトル用TextView
- 週の文字列(日月火水木金土)用に7つのTextView
- カレンダーの日付用に6×7=42マスのTextView
を作成しましょう。
また、今週は背景をグレーにして、今日の日付は赤色で表示するようにします。
ソースは次のようになります。
[java]
package com.example.calendarsample;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
public class CalendarView extends LinearLayout {
private static final int WEEK_7 = 7;
private static final int MAX_WEEK = 6;
public CalendarView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOrientation(VERTICAL);
// dp–>px 変換用の値
final float scale =
context.getResources().getDisplayMetrics().density;
// パディング
final int padding = (int) (scale * 5);
// 右パディング
final int paddingR = (int) (scale * 15);
// タイトルとのパディング
final int paddintT = (int) (scale * 20);
titleFontPx = scale * 30;
// 本日の日付を保持
mToday = Calendar.getInstance();
// タイトル部 年月表示
Log.v(“CalendarView”, “タイトル部 年月表示”);
mTitle = new TextView(context);
mTitle.setGravity(Gravity.CENTER_HORIZONTAL); // 中央に表示
mTitle.setTypeface(null, Typeface.BOLD); // 太字
mTitle.setText(“DEBUG”);
mTitle.setPadding(0, 0, 0, paddintT);
addView(mTitle, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
dayFontPx = mTitle.getTextSize(); // 現在のフォントサイズを保持
defaultColor = mTitle.getTextColors().getDefaultColor();
// 週表示部 日月火水木金土
Log.v(“CalendarView”, “週表示部 日月火水木金土”);
mWeekLayout = new LinearLayout(context); // 週表示レイアウト
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DAY_OF_WEEK, beginningWeek); // 週の頭をセット
SimpleDateFormat formatter = new SimpleDateFormat(“E”); // 曜日を取得するフォーマッタ
for (int i = 0; i < WEEK_7; i++) {
TextView textView = new TextView(context);
textView.setText(formatter.format(cal.getTime())); // テキストに曜日を表示
textView.setGravity(Gravity.CENTER_HORIZONTAL); // 中央に表示
LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(
0, LayoutParams.WRAP_CONTENT);
llp.weight = 1;
mWeekLayout.addView(textView, llp);
cal.add(Calendar.DAY_OF_MONTH, 1);
}
addView(mWeekLayout, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
// カレンダー部 最大6行必要
Log.v("CalendarView", "カレンダー部 最大6行必要");
for (int i = 0; i < MAX_WEEK; i++) {
LinearLayout weekLine = new LinearLayout(context);
mWeeks.add(weekLine);
// 1週間分の日付ビュー作成
for (int j = 0; j < WEEK_7; j++) {
TextView dayView = new TextView(context);
dayView.setText(String.valueOf((i * WEEK_7) + (j + 1))); // TODO:DEBUG
dayView.setGravity(Gravity.RIGHT); // 右寄せで表示
dayView.setPadding(0, padding, paddingR, padding);
LinearLayout.LayoutParams llp =
new LinearLayout.LayoutParams(
0, LayoutParams.WRAP_CONTENT);
llp.weight = 1;
weekLine.addView(dayView, llp);
}
this.addView(weekLine, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
init(mToday.get(Calendar.YEAR), mToday.get(Calendar.MONTH));
}
// 表示設定
public void init(int year, int month) {
Calendar cal = Calendar.getInstance();
cal.clear(); // まずクリアしてからセットしないといけない
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, 1);
int todayYear = mToday.get(Calendar.YEAR);
int todayMonth = mToday.get(Calendar.MONTH);
int todayDay = mToday.get(Calendar.DAY_OF_MONTH);
// タイトル 年月 設定
String formatString =
mTitle.getContext().getString(R.string.format_month_year); // 年月フォーマット文字列
mTitle.setTextSize(titleFontPx);
// 年月を取得するフォーマッタ
SimpleDateFormat formatter = new SimpleDateFormat(formatString);
mTitle.setText(formatter.format(cal.getTime()));
// 週表示部 日月火水木金土
Log.v("CalendarView", "週表示部 日月火水木金土");
Calendar week = Calendar.getInstance();
week.set(Calendar.DAY_OF_WEEK, beginningWeek); // 週の頭をセット
SimpleDateFormat weekFormatter = new SimpleDateFormat("E"); // 曜日を取得するフォーマッタ
for (int i = 0; i < WEEK_7; i++) {
TextView textView = (TextView) mWeekLayout.getChildAt(i);
textView.setText(weekFormatter.format(week.getTime())); // テキストに曜日を表示
week.add(Calendar.DAY_OF_MONTH, 1);
}
// カレンダーの最初の空白の個数を求める。
int skipCount; // 空白の個数
int firstDayMonthWeek = cal.get(Calendar.DAY_OF_WEEK); // 1日の曜日
if (beginningWeek > firstDayMonthWeek) {
skipCount = firstDayMonthWeek – beginningWeek + WEEK_7;
} else {
skipCount = firstDayMonthWeek – beginningWeek;
}
int lastDay = cal.getActualMaximum(Calendar.DATE); // 月の最終日
// 日付を生成する。
int dayCounter = 1;
for (int i = 0; i < MAX_WEEK; i++) {
LinearLayout weekLayout = mWeeks.get(i);
weekLayout.setBackgroundColor(defaultBackgroundColor);
for (int j = 0; j < WEEK_7; j++) {
TextView dayView = (TextView) weekLayout.getChildAt(j);
if (i == 0 && skipCount > 0) {
dayView.setText(” “);
skipCount–;
} else if (dayCounter <= lastDay) {
dayView.setText(String.valueOf(dayCounter));
// 今日の日付を赤文字にする
if (todayYear == year
&& todayMonth == month
&& todayDay == dayCounter) {
dayView
.setTextColor(todayColor); // 赤文字
dayView.setTypeface(null,
Typeface.BOLD); // 太字
weekLayout
.setBackgroundColor(todayBackgroundColor); // 週の背景グレー
} else {
dayView.setTextColor(defaultColor);
dayView.setTypeface(null,
Typeface.NORMAL);
}
dayCounter++;
} else {
dayView.setText(" ");
}
}
}
}
/** 週の始まりの曜日を保持する */
public int beginningWeek = Calendar.SUNDAY;
/** 今日のフォント色 */
public int todayColor = Color.RED;
/** 今週の背景色 */
public int todayBackgroundColor = Color.LTGRAY;
/** 通常の背景色 */
public int defaultBackgroundColor = Color.TRANSPARENT;
/** 通常のフォント色 */
public int defaultColor;
/** 年月のフォントサイズ */
public float titleFontPx;
/** 日付のフォントサイズ */
public float dayFontPx;
private TextView mTitle; // 年月表示部分
private LinearLayout mWeekLayout;
private ArrayList
private Calendar mToday;
}
[/java]
実行するときは、標示したい年月をinit(year,month)に渡せばOKです。
[java]
CalendarView cal = (CalendarView) findViewById(R.id.Calendar1);
cal.beginningWeek = Calendar.SUNDAY; // 週の開始曜日を指定
cal.init(2012, 9-1); // 2012年9月のカレンダーを表示
[/java]
以上で、カスタムカレンダーを作成することができました。
CalendarAPIを使用したアプリケーションの作成方法
GoogleカレンダーAPIを使用してAndroid用のカレンダーアプリケーションを作成します。
Calendar取得や書き込みはCalendarプロバイダ経由で行います。カレンダープロバイダAPIは4.0から公式APIとなりましたが、それ以前のバージョンでも非公式ながら使用することができます。ただしUriやカラム名が一部異なるのでAndroidのバージョン毎に分岐させる必要があります。 非公式のAPIを使用するためにはソースコードを読み解く必要があります。今回はAPIレベル10以上をターゲットとしたいと思います。
まずはGitのインストール。 http://git-scm.com/download/win からWindows版Gitをダウンロードしてパソコンにインストールします。
次にソースコードの取得。Android API 14以降のソースコードは「Android SDKマネージャー」で簡単に取得できますが、それ以前のソースは別途取得する必要があります。今回、Gingerbreadでもカレンダーを使えるようにしたいのでAndroid2.3のカレンダープロバイダのソースを参照する必要があります。そのためGitでAndroid2.3のソースコードを取得してきましょう。 ソースはGoogle本家から取得するのはなかなか大変なのでhttps://github.com/OESF/を利用すると楽に取得できます。 たとえばAndroidのソース一式を取得するには次のようなGitコマンドで取得します。
[shell]
git init
git clone https://github.com/OESF/OHA-Android-2.3.4_r1.0.git
[/shell]
ソースが取得できたら、早速カレンダープロバイダのソースを参照してみましょう。ソースは \frameworks\base\core\java\android\provider\Calendar.java にあります。 このソースを見れば、プロバイダのURIやカラムが理解できると思います。
さて、それではプログラムを作成していきましょう。Googleカレンダーからスケジュールを取得しリスト表示するプログラムです。
まずはプロジェクトを作成して下さい。
設定項目 |
入力値/選択項目 |
|
New Android Application | Application name |
CalendarSample |
Project Name | CalendarSample | |
Package Name | com.example.CalendarSample | |
Build SDK | Android 4.1(API 16) | |
Minimum Required SDK | API10:Android 2.3.3(Gingerbread) | |
Create Activity | Create Activity | BlankActivity |
New Blank Activity | Activity Name | MainActivity |
Layout Name | activity_main | |
Navigation Type | None | |
Hierarchical Parent | (指定なし) | |
Title | MainActivity |
MainActivity.javaを次のように編集します。
[java]
package com.example.calendarsample;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
[/java]
アクティビティはこれだけです。次にレイアウトです。
[xml]
<fragment xmlns:android=”http://schemas.android.com/apk/res/android”
android:name=”com.example.calendarsample.CalendarListFragment”
android:id=”@+id/List”
android:layout_width=”match_parent”
android:layout_height=”match_parent” />
[/xml]
レイアウトには上のようにフラグメントを1つ配置しているだけです。ではそのフラグメントのソースを見ていきましょう。フラグメントはListFragmentを使い、スケジュールをリスト表示します。
CalendarListFragment.java
[java]
package com.example.calendarsample;
import android.annotation.TargetApi;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Instances;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
import android.text.format.Time;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class CalendarListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks, ViewBinder {
SimpleCursorAdapter mAdapter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setEmptyText(“スケジュールはありません”);
String[] from;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH){
from = new String[] { Instances.TITLE, Instances.BEGIN };
} else {
from = new String[] { Instances.TITLE, Instances.BEGIN };
}
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_2, null, from,
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
mAdapter.setViewBinder(this);
setListAdapter(mAdapter);
getLoaderManager().initLoader(0, null, this);
}
@TargetApi(14)
@Override
public Loader onCreateLoader(int id, Bundle args) {
String tz = Time.TIMEZONE_UTC;
Time time = new Time(tz);
time.setToNow();
time.allDay = true;
time.year = time.year – 1;
time.month = 0;
time.monthDay = 1;
time.hour = 0;
time.minute = 0;
time.second = 0;
int begin = Time.getJulianDay(time.toMillis(true), 0);
time.year += 4;
time.month = 11;
time.monthDay = 31;
int end = Time.getJulianDay(time.toMillis(true), 0);
Uri content_by_day_uri;
String[] instance_projection;
String sort_order;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH){
content_by_day_uri =
CalendarContract.Instances.CONTENT_BY_DAY_URI;
instance_projection = new String[] {
Instances._ID,
Instances.EVENT_ID,
Instances.BEGIN,
Instances.END,
Instances.TITLE
};
sort_order =
Instances.BEGIN + ” ASC, ” + Instances.END + ” DESC, ”
+ Instances.TITLE + ” ASC”;
} else {
final String authority = “com.android.calendar”;
content_by_day_uri = Uri.parse(“content://” + authority
+ “/instances/whenbyday”);
instance_projection = new String[] {
“_id”,
“event_id”,
“begin”,
“end”,
“title”
};
sort_order = “begin ASC, end DESC, title ASC”;
}
Uri baseUri = buildQueryUri(begin, end, content_by_day_uri);
return new CursorLoader(getActivity(), baseUri,
instance_projection, null,
null, sort_order);
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
mAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader loader) {
mAdapter.swapCursor(null);
}
@Override
public boolean setViewValue(View view, Cursor cursor, int index) {
Log.v(“debug”, “index:” + String.valueOf(index));
switch (index) {
case 4:
((TextView)view).setText(cursor.getString(index));
return true;
case 2:
String text = cursor.getString(index);
Time time = new Time();
time.set(Long.parseLong(text));
((TextView)view).setText(time.format3339(false));
return true;
default:
break;
}
return false;
}
private Uri buildQueryUri(int start, int end, Uri content_by_day_uri) {
StringBuilder path = new StringBuilder();
path.append(start);
path.append(‘/’);
path.append(end);
Uri uri =
Uri.withAppendedPath(content_by_day_uri, path.toString());
return uri;
}
}
[/java]
ListFragmentを使用して、取得したスケジュールの情報を画面に一覧表示しています。取得したデータをListに渡すためにSimpleCursorAdapterを使っています。SimpleCursorAdapterはCursorAdapterを継承しておりCursorAdapterを継承したクラスには_id列がないと動作しません。
データの取得にはCursorLoaderを使います。CursorLoaderを使えば手軽に非同期でデータを取得することが出来ます。まずローダーの準備からです。
getLoaderManager().initLoader(0, null, this);
LoaderManagerがLoaderを管理します。LoaderManager.LoaderCallbacksを実装したクラスが必要になるので、Fragmentに実装しています。
onCreateLoader() コールバックメソッドでCursorLoaderを生成して戻り値にセットします。
リマインダーテーブルに個々のスケジュールが入っています。リマインダーテーブルを取得するコンテントプロバイダのURIは次のような形です。
content://com.android.calendar/instances/whenbyday/<開始日>/<終了日>
<開始日>および<終了日>は取得したいスケジュールの範囲をユリウス通日(Julian Day)で指定します。ユリウス通日とは、紀元前4713年1月1日から起算して何日目であるかという日数です。ユリウス通日を取得するにはTimeクラスを使います。
String tz = Time.TIMEZONE_UTC; Time time = new Time(tz); time.setToNow(); time.allDay = true; time.year = time.year - 1; time.month = 0; time.monthDay = 1; time.hour = 0; time.minute = 0; time.second = 0; int begin = Time.getJulianDay(time.toMillis(true), 0);
Timeクラスのインスタンスに現在日付を設定し、そこから1年前の1月1日の日付を設定しています。設定した日付からユリウス通日を取得するにはgetJulianDayメソッドを使います。このメソッドはUTCミリ秒をユリウス通日に変換してくれます。
CursorLoaderのコンストラクタにコンテントプロバイダのURIなどを渡して、CursorLoaderを生成して戻りとして戻しています。
return new CursorLoader(getActivity(), baseUri, instance_projection, null, null, sort_order);
onLoadFinishedコールバックメソッドと、onLoaderResetコールバックメソッドはそれぞれローダーの読み込みが終わった時と、ローダーがリセットされてデータが読み込めなくなった時に呼ばれます。ここでAdapterに紐付くカーソルの更新を行うわけですが、SimpleCursorAdapterにカーソルの更新を行うためのメソッドであるswapCursorメソッドが用意されていますので、次のように実装します。
@Override public void onLoadFinished(Loader loader, Cursor data) { mAdapter.swapCursor(data); } @Override public void onLoaderReset(Loader loader) { mAdapter.swapCursor(null); }
カレンダープロバイダから取得したタイトルと開始時間をリストに表示していますが、開始時間や終了時間はそのままだとUTCミリ秒なのでフォーマットして表示するようにしています。簡単にフォーマットを行うための仕組みとして、ViewBinderがあります。これを使ってフォーマットします。
mAdapter.setViewBinder(this);
thisでFragment自身をViewBinderに指定しています。FragmentにはViewBinderインタフェースを実装し、フォーマット処理をsetViewValueコールバックメソッドに記述します。
public boolean setViewValue(View view, Cursor cursor, int index) { Log.v("debug", "index:" + String.valueOf(index)); switch (index) { case 4: ((TextView)view).setText(cursor.getString(index)); return true; case 2: String text = cursor.getString(index); Time time = new Time(); time.set(Long.parseLong(text)); ((TextView)view).setText(time.format3339(false)); return true; default: break; } return false; }
indexにカーソルのカラム番号を受け取るので、switchで分岐してカラム毎にフォーマット処理を行なっています。この例ではカラムとインデックスの対応は次のようになっています。
Instances._ID, // index 0 Instances.EVENT_ID, // index 1 Instances.BEGIN, // index 2 Instances.END, // index 3 Instances.TITLE // index 4
indexが2の場合、開始時刻(BEGIN)を取得できるのでTimeクラスのformat3339メソッドでフォーマットしてリストに表示させています。
【追記】
最後に、カレンダーデータを読み取ることをAndroid Manifestに記載しなければなりません。READ_CALENDARパーミッションを追加します。
[xml]
[/xml]
以上でカレンダープロバイダからデータを取得してリストに表示することが出来ました。
Android標準のJavaフォーマッター「android-formatting.xml」を取得する
Android本家のJavaフォーマッターがあります。
http://source.android.com/source/using-eclipse.html
の「Eclipse formatting」という項目に書いてあります。フォーマッタは以下のURLより参照できます。
https://android.googlesource.com/platform/development.git/+/master/ide/eclipse
Gitで取得しましょう。
[shell]
git clone https://android.googlesource.com/platform/development
[/shell]
上記コマンドで取得した「android-formatting.xml」をEclipseにインポートして使います。インポートはEclipseの設定から「Java」→「コード・スタイル」→「フォーマッター」を開き「インポートを」クリックして、取得した「development\ide\eclipse\android-formatting.xml」を選択して適用すれば完了です。
Android 4.1 Intel Atom (x86)エミュレータを動かす時のポイント
Android4.1のAtomエミュレータを動かす時のポイント。
次の設定でAndroid仮想デバイスを新規作成します。
ターゲット:Android 4.1 – API Level 16
CPU/ABI:Intel Atom(x86)
ハードウェア:GPU emulation yes