/**
* バックグラウンド処理の間にキャンセルできないプログレスダイアログを表示する
*/
public abstract class ModalProgressTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
private final WeakReference<ProgressDialog> mProgressRef;
public ModalProgressTask(Context context, String message) {
ProgressDialog dialog = new ProgressDialog(context);
dialog.setMessage(message);
dialog.setCancelable(false);
mProgressRef = new WeakReference<ProgressDialog>(dialog);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
ProgressDialog dialog = mProgressRef.get();
if (dialog != null && !dialog.isShowing()) {
dialog.show();
}
}
@Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
ProgressDialog dialog = mProgressRef.get();
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
@Override
protected void onCancelled() {
super.onCancelled();
ProgressDialog dialog = mProgressRef.get();
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
}
2014年4月27日日曜日
バックグラウンド処理の間プログレスダイアログを表示するAsyncTask
AsyncTaskのパラメタライズはそのままに、共通化したい前処理や後処理を実装するとこんな感じ。
2014年4月15日火曜日
InsetDrawableで余白のある区切り線を作る
InsetDrawableについては「Android InsetDrawableを活用する」で取り上げましたが、これを使うとListView用に左右に余白のある区切り線を作ることができます。
res/drawable/list_divider.xml
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="8dp" android:insetRight="8dp">
<shape>
<solid android:color="#cccccc" />
<size android:height="1dp" />
</shape>
</inset>
public class MainListFragment extends ListFragment {
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ListView lv = getListView();
lv.setDivider(getResources().getDrawable(R.drawable.list_divider));
}
...
}
2014年4月14日月曜日
Android 画像サイズ
アップアイコン
(48dp x 48dp)
Action Bar (32dp x 32dp、24dp x 24dp)
Notification (24dp x 24dp、22dp x 22dp)
ldpi | 36 x 36 | (1dp = 3/4px | 4dp = 3px) | |
---|---|---|---|---|
mdpi | 48 x 48 | (1dp = 1px | 4dp = 4px) | |
hdpi | 72 x 72 | (1dp = 1.5px | 4dp = 6px) | |
xhdpi | 96 x 96 | (1dp = 2px | 4dp = 8px) | |
xxhdpi | 144 x 144 | (1dp = 3px | 4dp = 12px) | |
xxxhdpi | 192 x 192 | (1dp = 4px | 4dp = 16px) |
Action Bar (32dp x 32dp、24dp x 24dp)
外枠 | 内枠 | 差 | ||||
---|---|---|---|---|---|---|
ldpi | 24 x 24 | 18 x 18 | 6 | |||
mdpi | 32 x 32 | 24 x 24 | 8 | |||
hdpi | 48 x 48 | 36 x 36 | 12 | |||
xhdpi | 64 x 64 | 48 x 48 | 16 | |||
xxhdpi | 96 x 96 | 72 x 72 | 24 | |||
xxxhdpi | 128 x 128 | 96 x 96 | 32 |
Notification (24dp x 24dp、22dp x 22dp)
外枠 | 内枠 | 差 | ||||
---|---|---|---|---|---|---|
ldpi | 18 x 18 | 16.5 x 16.5 | 1.5 | |||
mdpi | 24 x 24 | 22 x 22 | 2 | |||
hdpi | 36 x 36 | 33 x 33 | 3 | |||
xhdpi | 48 x 48 | 44 x 44 | 4 | |||
xxhdpi | 72 x 72 | 66 x 66 | 6 | |||
xxxhdpi | 96 x 96 | 88 x 88 | 8 |
2014年4月11日金曜日
Facebookとそれ以外でACTION_SENDで渡すテキストを変える
Facebookはポリシーで投稿のpre-fillを禁止している(https://developers.facebook.com/policy/)ため、テキストの代わりにリンクを共有することがよくあります。
前回の「「ギャラリーから選択」と「カメラで撮影」を1つのchooserで表示する。」と同じ方法を使うと、Facebookとそれ以外でIntent.EXTRA_TEXTに含める文字列を変えることができます。
前回の「「ギャラリーから選択」と「カメラで撮影」を1つのchooserで表示する。」と同じ方法を使うと、Facebookとそれ以外でIntent.EXTRA_TEXTに含める文字列を変えることができます。
/**
* Facebook ではリンクを共有し、それ以外(FB Messenger 含む)ではテキストを共有する
*/
private void share() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
List<ResolveInfo> resInfo = getActivity().getPackageManager()
.queryIntentActivities(intent, 0);
if (resInfo.isEmpty()) {
return;
}
String shareText = getString(R.string.share_text);
String shareTextFacebook = getString(R.string.share_link);
List<Intent> shareIntentList = new ArrayList<>();
for (ResolveInfo info : resInfo) {
Intent shareIntent = (Intent) intent.clone();
if (info.activityInfo.packageName.toLowerCase()
.equals("com.facebook.katana")) {
shareIntent.putExtra(Intent.EXTRA_TEXT, shareTextFacebook);
} else {
shareIntent.putExtra(Intent.EXTRA_TEXT, shareText);
}
shareIntent.setPackage(info.activityInfo.packageName);
shareIntentList.add(shareIntent);
}
Intent chooserIntent = Intent.createChooser(shareIntentList.remove(0),
getString(R.string.select_app));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
shareIntentList.toArray(new Parcelable[]{}));
startActivity(chooserIntent);
}
2014年4月9日水曜日
「ギャラリーから選択」と「カメラで撮影」を1つのchooserで表示する。
異なるActionのIntentそれぞれに対応するアプリを、1つのchooserで選択できるようにする方法です。
例えば、ギャラリーから画像を選択するときは Intent.ACTION_GET_CONTENT を使いますが、 カメラを起動して撮影した画像を取得するときは MediaStore.ACTION_IMAGE_CAPTURE を使います。
そのため、まず「ギャラリーから選択」と「カメラで撮影」のどちらかを選ぶためのダイアログを用意する例をよく見ます。
*ドキュメントプロバイダーが追加されたからか、ACTION_GET_CONTENT でギャラリーが出てこなくなり、代わりにドキュメントがでてきます。ここではギャラリーの方がわかりやすいので、ギャラリーとします。
Intent.EXTRA_INITIAL_INTENTSを使えば、1つのchooserダイアログに両方入れることができます。
例えば、ギャラリーから画像を選択するときは Intent.ACTION_GET_CONTENT を使いますが、 カメラを起動して撮影した画像を取得するときは MediaStore.ACTION_IMAGE_CAPTURE を使います。
そのため、まず「ギャラリーから選択」と「カメラで撮影」のどちらかを選ぶためのダイアログを用意する例をよく見ます。
*ドキュメントプロバイダーが追加されたからか、ACTION_GET_CONTENT でギャラリーが出てこなくなり、代わりにドキュメントがでてきます。ここではギャラリーの方がわかりやすいので、ギャラリーとします。
Intent.EXTRA_INITIAL_INTENTSを使えば、1つのchooserダイアログに両方入れることができます。
private Uri mPictureUri;
private void launchChooser() {
// ギャラリーから選択
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.setType("image/*");
i.addCategory(Intent.CATEGORY_OPENABLE);
// カメラで撮影
String filename = System.currentTimeMillis() + ".jpg";
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, filename);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
mPictureUri = getContentResolver()
.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
Intent i2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
i2.putExtra(MediaStore.EXTRA_OUTPUT, mPictureUri);
// ギャラリー選択のIntentでcreateChooser()
Intent chooserIntent = Intent.createChooser(i, "Pick Image");
// EXTRA_INITIAL_INTENTS にカメラ撮影のIntentを追加
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { i2 });
startActivityForResult(chooserIntent, IMAGE_CHOOSER_RESULTCODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == IMAGE_CHOOSER_RESULTCODE) {
if (resultCode != RESULT_OK) {
if (mPictureUri != null) {
getContentResolver().delete(mPictureUri, null, null);
mPictureUri = null;
}
return;
}
// 画像を取得
Uri result = (data == null) ? mPictureUri : data.getData();
ImageView iv = (ImageView) findViewById(R.id.imageView1);
iv.setImageURI(result);
mPictureUri = null;
}
}
* MediaStore.ACTION_IMAGE_CAPTURE を使うときは android.permission.WRITE_EXTERNAL_STORAGE が必要なので忘れずに
2014年4月8日火曜日
Android InsetDrawableを活用する
タグ等、デザイン的に大きくしたくないけれど、タップに反応させるコンポーネントは、実際にタップできる領域を見た目より大きくとるのが鉄則です。
これを実現する方法が3つあります。
1. Viewの階層で実現する
すぐ思いつく方法ですが、Viewが増えるので初心者っぽいです。
2. 9patch画像で実現する
透明の領域とタグの背景部分をあわせた9patch画像を用意します。Viewが増えないので中級者です。
ただ、各解像度用の画像を用意するのは結構面倒です。おまけにタップしたときの画像も用意しないといけません。
3. InsetDrawableで実現する
InsetDrawableを使えばViewも増えないし、画像もいりません。上級者ですね。
res/drawable/tag_bg_normal.xml
これを実現する方法が3つあります。
1. Viewの階層で実現する
すぐ思いつく方法ですが、Viewが増えるので初心者っぽいです。
<!-- 透明の領域用 -->
<FrameLayout
...
android:padding="8dp"
android:background="@android:color/transparent" >
<!-- タグ部分 -->
<TextView
...
android:paddingTop="2dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="2dp"
android:background="@color/tag_bg" />
</FrameLayout>
*@color/tag_bgはColorStateListです。
2. 9patch画像で実現する
透明の領域とタグの背景部分をあわせた9patch画像を用意します。Viewが増えないので中級者です。
ただ、各解像度用の画像を用意するのは結構面倒です。おまけにタップしたときの画像も用意しないといけません。
<TextView
...
android:background="@drawable/tag_bg" />
3. InsetDrawableで実現する
InsetDrawableを使えばViewも増えないし、画像もいりません。上級者ですね。
res/drawable/tag_bg_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetTop="8dp"
android:insetRight="8dp"
android:insetBottom="8dp"
android:insetLeft="8dp">
<shape>
<solid android:color="@color/green" />
<padding android:top="2dp" android:left="8dp"
android:right="8dp" android:bottom="2dp" />
</shape>
</inset>
res/drawable/tag_bg_pressed.xml
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetTop="8dp"
android:insetRight="8dp"
android:insetBottom="8dp"
android:insetLeft="8dp">
<shape>
<solid android:color="@color/orange" />
<padding android:top="2dp" android:left="8dp"
android:right="8dp" android:bottom="2dp" />
</shape>
</inset>
res/drawable/tag_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/tag_bg_pressed" />
<item android:drawable="@drawable/tag_bg_normal" />
</selector>
<TextView
...
android:background="@drawable/tag_bg" />
* 残念ながらinsetのなかのshapeのsolidでColorStateListを指定しても適用されませんでした。
2014年4月6日日曜日
ABC 2014 Spring で講演しました。
Android Pattern Cookbookの内容をざっくり紹介したものですが、去年のABCで講演したときもトレンドを扱っていたので、各アプリがどう変わったのかも載せています。
2014年4月18日に秋葉ちひろさんと出版記念イベントを行います。
「"あんざいゆき" x "秋葉ちひろ"はカンファレンスアプリをどう作るのか?」
4月18日(金)19:00~21:00(受付18:30)@市ヶ谷セミナールーム
http://www.impressjapan.jp/items/android-20140418
有料ですが、Android Pattern Cookbookを当日持っていくと割引になります。
ABCが終わった後に、ABC用のアプリを作ります。(イベント前に公開します!)
どういう意図をもって設計したのか、どうしてこういう画面構成になったのかなどを、イベントで解説します。
2014年4月2日水曜日
Android Spinnerの選択肢をXMLで指定する
Spinnerでは、Adapterを用意しなくてもXMLから選択肢をセットすることができます。
res/values/arrays.xml
このように選択肢をセットした場合、Spinner部分のレイアウトには android.R.layout.simple_spinner_item が、ドロップダウンのレイアウトには android.R.layout.simple_spinner_dropdown_item が使われます。
AbsSpinner.java
android.R.layout.simple_spinner_item
android.R.layout.simple_spinner_dropdown_item
この属性に独自のスタイルをセットすれば、Spinner部分とドロップダウンの文字サイズや文字色を一括で変えることができます。
文字サイズを小さくする例
res/values/styles.xml
この方法のときも、setOnItemSelectedListener()を使って選択項目が変わったことを検知でき、Adapterが保持しているObjectはStringになります。
res/values/arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="spinner_values">
<item>Cupcake</item>
<item>Donut</item>
<item>Eclair</item>
<item>Froyo</item>
<item>Gingerbread</item>
<item>Honeycomb</item>
<item>ICS</item>
<item>JellyBean</item>
<item>KitKat</item>
</string-array>
</resources>
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Spinner
android:id="@+id/spinner1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/spinner_values" />
</LinearLayout>
このandroid:entries属性はAbsSpinnerで用意されています。
このように選択肢をセットした場合、Spinner部分のレイアウトには android.R.layout.simple_spinner_item が、ドロップダウンのレイアウトには android.R.layout.simple_spinner_dropdown_item が使われます。
AbsSpinner.java
67 public AbsSpinner(Context context, AttributeSet attrs, int defStyle) {
68 super(context, attrs, defStyle);
69 initAbsSpinner();
70
71 TypedArray a = context.obtainStyledAttributes(attrs,
72 com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
73
74 CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
75 if (entries != null) {
76 ArrayAdapter adapter =
77 new ArrayAdapter(context,
78 R.layout.simple_spinner_item, entries);
79 adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
80 setAdapter(adapter);
81 }
82
83 a.recycle();
84 }
android.R.layout.simple_spinner_item
<?xml version="1.0" encoding="utf-8"?>
...
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textAlignment="inherit"/>
android.R.layout.simple_spinner_dropdown_item
<?xml version="1.0" encoding="utf-8"?>
...
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerDropDownItemStyle"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="?android:attr/dropdownListPreferredItemHeight"
android:ellipsize="marquee"
android:textAlignment="inherit"/>
このレイアウトを見るとわかりますが、それぞれandroid:spinnerItemStyleとandroid:spinnerDropDownItemStyleに指定されているスタイルを適用しています。この属性に独自のスタイルをセットすれば、Spinner部分とドロップダウンの文字サイズや文字色を一括で変えることができます。
文字サイズを小さくする例
res/values/styles.xml
<resources>
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<item name="android:spinnerItemStyle">@style/SpinnerItemStyle</item>
<item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItemStyle</item>
</style>
<style name="SpinnerItemStyle" parent="android:Widget.Holo.Light.TextView.SpinnerItem">
<item name="android:textSize">12sp</item>
</style>
<style name="SpinnerDropDownItemStyle" parent="android:Widget.Holo.Light.DropDownItem">
<item name="android:textSize">12sp</item>
</style>
</resources>
この方法のときも、setOnItemSelectedListener()を使って選択項目が変わったことを検知でき、Adapterが保持しているObjectはStringになります。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Spinner spinner = (Spinner) findViewById(R.id.spinner1);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapter,
View v, int position, long id) {
Toast.makeText(MainActivity.this,
(String) adapter.getItemAtPosition(position),
Toast.LENGTH_SHORT).show();
}
@Override
public void onNothingSelected(AdapterView<?> adapter) {
}
});
}
}