2014年4月27日日曜日

バックグラウンド処理の間プログレスダイアログを表示するAsyncTask

AsyncTaskのパラメタライズはそのままに、共通化したい前処理や後処理を実装するとこんな感じ。

/** * バックグラウンド処理の間にキャンセルできないプログレスダイアログを表示する */ 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月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)

ldpi36 x 36(1dp = 3/4px4dp = 3px)
mdpi48 x 48(1dp = 1px4dp = 4px)
hdpi72 x 72(1dp = 1.5px4dp = 6px)
xhdpi96 x 96(1dp = 2px4dp = 8px)
xxhdpi144 x 144(1dp = 3px4dp = 12px)
xxxhdpi192 x 192(1dp = 4px4dp = 16px)




Action Bar (32dp x 32dp、24dp x 24dp)

外枠内枠
ldpi24 x 2418 x 186
mdpi32 x 3224 x 248
hdpi48 x 4836 x 3612
xhdpi64 x 6448 x 4816
xxhdpi96 x 9672 x 7224
xxxhdpi128 x 12896 x 9632




Notification (24dp x 24dp、22dp x 22dp)

外枠内枠
ldpi18 x 1816.5 x 16.51.5
mdpi24 x 2422 x 222
hdpi36 x 3633 x 333
xhdpi48 x 4844 x 444
xxhdpi72 x 7266 x 666
xxxhdpi96 x 9688 x 888




2014年4月11日金曜日

Facebookとそれ以外でACTION_SENDで渡すテキストを変える

Facebookはポリシーで投稿のpre-fillを禁止している(https://developers.facebook.com/policy/)ため、テキストの代わりにリンクを共有することがよくあります。

前回の「「ギャラリーから選択」と「カメラで撮影」を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ダイアログに両方入れることができます。



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が増えるので初心者っぽいです。

<!-- 透明の領域用 --> <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 <?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) { } }); } }