June 30, 2012

目次

Nexus7 2013



Xperia VL

入門編

アプリ、タスク、Activity、Intent

Service

UI(ビュー)

UI(レイアウト)

UI(その他)

オプションメニュー、Action Bar

通知、Notification

マルチメディア

June 29, 2012

NotificationのUIをカスタマイズする

NotificationのUIのカスタマイズ方法です。

Notificationのデフォルト表示方法は「Notificationで通知する」を参照してください。ここではそこからのカスタマイズ方法を説明します。

例)独自のレイアウトでNotificationを表示し、さらにその中の画像をクリックするとServiceにIntentを飛ばします。これはNotification上のボタンからServiceで動作している機能に、いろいろな操作(例:音楽再生の操作とか)が出来ることを意味します。

まずはNotificationに表示するレイアウトを作成します。ここでは画像とテキスト2つです。背景色がデバイスによって異なる可能性があるため、テキストには標準のstyleを設定しています。注意事項は高さがGingerBread(2.3.x)までは固定のようです。HoneyComb(3.x)以降は高さもコンテンツにあわせて変化します。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView 
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true" />
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        style="@android:style/TextAppearance.StatusBar.EventContent.Title" />
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        android:layout_below="@id/title"
        style="@android:style/TextAppearance.StatusBar.EventContent" />
</RelativeLayout>

レイアウトはRemoteViewsクラスを使って上記のレイアウトを生成し、notificationに設定します。またsetOnClickPendingIntentを使って画像をクリックするとServiceにIntentを飛ばすようにnotificationに設定します。ここではactionに"Play"を付加しています。注意事項として、このような画像クリックはHoneyComb(3.x)以降でないと機能しないようです。通常のクリックと同じ扱いになってしまいます。

private void showNotification() {
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Intent intent = new Intent(this, SubAppActivity.class);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    String tickerText = getString(R.string.hello);
    // Customized Layout
    RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.row);
    contentView.setImageViewResource(R.id.icon, R.drawable.jelly);
    contentView.setTextViewText(R.id.title, "Title");
    contentView.setTextViewText(R.id.text, "text message!");
    Intent intent2 = new Intent(this, SampleService.class);
    intent2.setAction("Play");
    PendingIntent pi2 = PendingIntent.getService(this, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT);
    contentView.setOnClickPendingIntent(R.id.icon, pi2);
    // Notify
    Notification notification;
    try {
        Class.forName("android.app.Notification$Builder");
        notification = new Notification.Builder(this)
        .setContentIntent(contentIntent)
        .setContent(contentView)
        .setSmallIcon(android.R.drawable.stat_sys_download)
        .setTicker(tickerText)
        .setWhen(System.currentTimeMillis())
        .getNotification();
    } catch (ClassNotFoundException e) {
        notification = new Notification(android.R.drawable.stat_sys_download, tickerText, System.currentTimeMillis());
        notification.contentIntent = contentIntent;
        notification.contentView = contentView;
    }
    manager.notify(1, notification);
}

Intentを受け取るServiceクラスは下記のようにしています。Intentを受け取るとonStartCommand()が呼ばれますので、そのときのactionをログ表示します。ここに必要な機能を作り込みます。

public class SampleService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();
        Log.i("Service", action);
        return START_STICKY;
    }
}

実行すると下記のようになります。画像をクリックするとServiceにintentが飛びます。


参考:Android Developers:Status Notifications
参考:Android Developers:RemoteViews

June 26, 2012

Notificationで通知する

Notificationを使って通知する方法です。通常はBackgroundで動作しているServiceからユーザーに何かを通知するのが目的で使います。

通知はTickerと呼ばれる通知時のみ速報のように表示されるテキストメッセージと、通知欄に表示されるアイコン、通知を開いた時に表示されるタイトルとテキスト、通知をタップしたときの動作(Intent)が最小構成となります。

通知は自分で割り振ったID番号で管理することができ、通知ごとに新しい通知を作ることもできれば、通知済みであれば1つの通知に集約して作らないこともできます。基本は集約して同じ通知は1つにまとめるUIが推奨されています。

オプションで通知時にバイブレーション、LED、音楽再生もセットできます。また通知のUIレイアウトもカスタマイズ可能です。

例)通知します。タップすると単独の別Activityを起動します。通知は1つに集約するようにしています。

通知はいろいろな情報をセットしたNotificationを作成し、それをNotificationManagerを使って通知を実行します。IntentはPendingIntentというものに包み込んでセットします。これはそのIntentが自分のアプリが発行したかのように他のアプリから実行することができる特別なIntentになります。NotificationはAPI Level 11から作成方法が異なり、以前はnewしていましたが、今はNotification.Builderを使って作成するため、両方の作り方を記載しています。

ここでは通知の発行は下記の関数を用意しました。

private void showNotification() {
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Intent intent = new Intent(this, SubAppActivity.class);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    String tickerText = getString(R.string.hello);
    Notification notification;
    try {
        Class.forName("android.app.Notification$Builder");
        notification = new Notification.Builder(this)
        .setContentIntent(contentIntent)
        .setSmallIcon(android.R.drawable.stat_sys_download)
        .setTicker(tickerText)
        .setContentTitle("Title")
        .setContentText("Text")
        .setWhen(System.currentTimeMillis())
        .getNotification();
    } catch (ClassNotFoundException e) {
        notification = new Notification(android.R.drawable.stat_sys_download, tickerText, System.currentTimeMillis());
        notification.setLatestEventInfo(this, "TITLE", "TEXT", contentIntent);
    }
    manager.notify(1, notification);
}

タップされると起動するSubAppActivityは単独のタスクとするため、AndroidManifestで下記のようにActivityを設定します。

<activity
    android:name="SubAppActivity"
    android:launchMode="singleTask"
    android:taskAffinity=""
    android:excludeFromRecents="true">
</activity>

実行すると、まずアイコンとTickerが表示されます。


通知を開くとアイコン、TitleとTextが表示され、タップするとSubAppActivityが独立したタスクで起動します。


参考:Android Developers:Status Notifications

June 25, 2012

Androidのクラスが使えるか確認する

Androidのクラスが使えるか確認する方法です。

Androidのクラスはversionによって追加されたり削除(deprecated)されているため、versionが違っても同じアプリで動作するようにするには、クラスの存在を確認してコードを動的に切り替える必要があります。

クラスの確認にはClass.forName()を使い、引数にクラス名を文字列で設定します。通常のクラスやパッケージはドット(.)で接続しますが、内部クラスはドル($)で接続します。

例)Notification.BuilderクラスはAPI Level 11から追加されており、Notificationの作成方法が異なっています。BuilderクラスはNotificationクラスの内部クラスです。

try {
    Class.forName("android.app.Notification$Builder");
    Log.i(TAB,"Found");
} catch (ClassNotFoundException e) {
    Log.i(TAB,"Not found");
}

June 24, 2012

EclipseからAndroid端末のスクリーンショットの撮り方

EclipseからAndroid端末のスクリーンショットの撮り方です。

EclipseからDDMSを起動します(Open perspectiveからDDMSを選択)。

Devicesウインドウに端末が表示されるので、選択して写真アイコンをクリックします。そうすると、そのときの端末のスクリーンショットがキャプチャされますので、それを保存すればOKです。



June 17, 2012

List Viewの作り方とカスタマイズ

List Viewの作り方とカスタマイズ方法です。

List Viewはその名の通り、リスト表示を行う専用のViewです。標準はテキストのみのリスト表示ですが、カスタマイズすることで画像付きのテキストなど自由なレイアウトでリスト表示することができるようになります。

リスト表示するときは通常のActivityではなく、ListActivityというリスト表示専用のActivityを使います。このListActivityのデフォルトはsetContentViewでレイアウトを指定しなくても全画面がListViewになっていますが、あえて設定することで通常のActivityのように自由なレイアウトでListViewを配置することもできます。ただし配置できるListViewは1つだけで、そのid属性に"@android:id/list"とする必要があります。

例)中央にリスト表示、最上部にタイトルのテキスト、最下部にボタンの3段構成で表示する場合、レイアウトは下記のようにします。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/text"
        android:background="#EE3333"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/myname" />
    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/launch" />
</LinearLayout>

リスト表示の1行ごとのレイアウトとそこで表示するデータを決めます。ここでは左端にアイコン画像、右側にテキストを表示するレイアウトにします。

<?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="horizontal" >
    <ImageView 
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/title"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="wrap_content" />    
</LinearLayout>

表示に必要なデータはクラスで定義します。コンストラクタで初期値を設定できるようにしておくと便利です。

public class MyItems {
    public int mIconId;
    public String mTitle;

    public MyItems(int iconId, String title) {
        mIconId = iconId;
        mTitle = title;
    }
}

このデータを使ってレイアウトに表示するクラスを作ります。ArrayAdapterを継承して独自のカスタマイズクラスを作ります。このクラスの役割はgetView()で上記で作ったレイアウトに指定された行のデータを設定したViewを生成することです。そのViewがリストの1行として表示されます。

public class MyListAdapter extends ArrayAdapter<MyItems> {
    private LayoutInflater mInflater;

    public MyListAdapter(Context context, int textViewResourceId, List<MyItems> objects) {
        super(context, textViewResourceId, objects);
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        MyItems items = getItem(position);
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.row, null);
        }
        // Icon
        ImageView imageView = (ImageView) convertView.findViewById(R.id.icon);
        imageView.setImageResource(items.mIconId);
        // Title
        TextView textView = (TextView) convertView.findViewById(R.id.title);
        textView.setText(items.mTitle);

        return convertView;
    }
}

ListActivityで実際に表示する具体的なデータのリストをArrayListで作成し、それを使って上記のMyListAdapterを作成します。それをListActivityに設定すればリスト表示の完成です。また、アイテムをクリックされた時の処理は、ListActivityが持っているListViewのOnItemClickListenerが呼ばれるので、ここではデータのリストからタイトルを取得して表示するようにしています。

public class SampleAppActivity extends ListActivity {
 private final static String TAB = "SampleAppActivity";
 private List<MyItems> mMylist;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mMylist = new ArrayList<MyItems>();
        mMylist.add(new MyItems(android.R.drawable.ic_menu_today, "Today"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_agenda, "Agenda"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_gallery, "Gallery"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_month, "Month"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_mylocation, "My Location"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_rotate, "Rotate"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_more, "More..."));
        setListAdapter(new MyListAdapter(this, 0, mMylist));
        
        getListView().setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                MyItems items = mMylist.get(position);
                Log.i(TAB, "Clicked:" + items.mTitle);
            }
        });
 }
}

実行すると下記のような表示になります。


参考:Android Developers:ListActivity
参考:Android Developers:ListView
参考:Android Developers:ArrayAdapter
参考:Android Developers:Hello, Views - List View

Action Barからサブメニューを表示する

Action Barのオプションメニューからサブメニューを表示する方法です。

Action Barにオプションメニューを表示するまでは「Action Barにオプションメニューを表示する」を参照してください。ここでは、そこからの差分を説明します。

サブメニューを表示するには、ActionProviderクラスを使用します。このクラスを継承してユーザー独自のクラスを作ることでオリジナルのサブメニューが表示できます。

まずはオプションメニューからサブメニューを呼び出します。呼び出すには"actionProviderClass"属性にクラス名を設定します。これでこのメニューアイテムが選択されると、このクラスを呼び出します。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item1" android:showAsAction="ifRoom|collapseActionView" android:icon="@android:drawable/ic_menu_search" android:actionViewClass="android.widget.SearchView"></item>
    <item android:id="@+id/item2" android:icon="@android:drawable/ic_menu_share" android:showAsAction="ifRoom" android:actionProviderClass="jp.myapp.sample.SampleActionProvider"></item>
    <item android:id="@+id/item3" android:title="@string/menu3" android:showAsAction="ifRoom"></item>
    <item android:id="@+id/item4" android:title="@string/menu4" android:showAsAction="ifRoom"></item>
</menu>

呼び出し先のActionProviderクラスを作成します。後でViewを作成するためにコンストラクタでcontextを保存しておくのが通例です。onCreateActionViewではViewを返す必要はなくnullで構いません。サブメニューがあることを伝えるためにhasSunMenu()ではtrueを返します。メニュー表示のたびにonPrepareSubMenu()が呼ばれるので、そこでサブメニューを作成します。この例ではxmlで作ったmenuリソースをセットし、それをクリックしたときのリスナーを自分自身になるようにしています(onMenuItemClick()が呼ばれます)。

public class SampleActionProvider extends ActionProvider implements OnMenuItemClickListener {
    private final static String TAB = "SampleActionProvider";
    private Context mContext;

    public SampleActionProvider(Context context) {
        super(context);
        mContext = context;
    }

    @Override
    public View onCreateActionView() {
        return null;
    }

    @Override
    public boolean hasSubMenu() {
        return true;
    }

    @Override
    public void onPrepareSubMenu(SubMenu subMenu) {
        subMenu.clear();
        MenuInflater inflator = new MenuInflater(mContext);
        inflator.inflate(R.menu.provider_menu, subMenu);
        for (int i = 0; i < subMenu.size(); ++i) {
            subMenu.getItem(i).setOnMenuItemClickListener(this);
        }
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        Log.i(TAB, "Title:" + item.getTitle());
        return true;
    }
}

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item1" android:title="@string/menu1" android:icon="@android:drawable/ic_menu_compass"></item>
    <item android:id="@+id/item2" android:title="@string/menu2" android:icon="@android:drawable/ic_menu_camera"></item>
</menu>

実行すると下記のようになります。Action Barのオプションメニューの2番目のShareアイコンのボタンを押すとサブメニューが現れます。


June 16, 2012

Action Barにオプションメニューを表示する

Action Barにオプションメニューを表示する方法です。Action BarはAndroid 3.0(API 11)から追加された機能ですので、それ以上のデバイスで有効です。Action Barとは画面最上部に表示されるバー(アイコンやタイトルなどが表示される)のことです。

オプションメニューの作り方は「オプションメニューの表示」を参照してください。ここではその差分を説明します。

メニューのリソースを下記のように作成します。差分は"showAsAction"属性に"ifRoom"を設定していることです。この設定をすると、スペースがあればAction Barに表示するようになります。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item1" android:title="@string/menu1" android:showAsAction="ifRoom" android:icon="@android:drawable/ic_menu_add"></item>
    <item android:id="@+id/item2" android:icon="@android:drawable/ic_menu_share" android:showAsAction="ifRoom"></item>
    <item android:id="@+id/item3" android:title="@string/menu3" android:showAsAction="ifRoom"></item>
    <item android:id="@+id/item4" android:title="@string/menu4" android:showAsAction="ifRoom"></item>
</menu>

縦スクリーンで表示した場合、一部のみAction Barに表示されています。残りはオプションメニューでの表示となります。


横スクリーンで表示した場合、スペースがあるため、すべてAction Barに表示されています。


Action Barはアイコン優先、オプションメニューではテキストが表示されます。

また、縦スクリーンでは右上に表示するのではなく、画面下部に分割して表示することも可能です(横幅が狭いときのみ有効なので、幅広な横スクリーンでは使えません)。AndroidManifest.xmlのapplicationやactivityに下記のような"uiOptions"を設定します。

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:uiOptions="splitActionBarWhenNarrow">


参考:Android Developers:Action Bar
参考:Android Developers:Action Bar Icons

オプションメニューの表示

オプションメニューの表示方法です。メニューボタンを押すと表示されるオプションメニューのことです。

作り方は、menuリソース(xml)を作成し、res/menuにおきます。初期化時に1回だけonCreateOptionsMenu()が呼ばれるので、そこでメニューアイテムを登録し、メニューアイテムが選択されるとonOptionsItemSelected()が呼ばれるので、そこに押された時の処理を加えます。

例)4つのメニューアイテムを持つ場合

public class SampleAppActivity extends Activity {
    private final static String TAB = "SampleAppActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.mymenu, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        boolean ret = true;
        switch (item.getItemId()) {
        case R.id.item1:
            Log.i(TAB, "Menu item 1");
            break;
        case R.id.item2:
            Log.i(TAB, "Menu item 2");
            break;
        case R.id.item3:
            Log.i(TAB, "Menu item 3");
            break;
        case R.id.item4:
            Log.i(TAB, "Menu item 4");
            break;
        default:
            ret = super.onOptionsItemSelected(item);
        }
        return ret;
    }
}

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item1" android:title="@string/menu1" android:icon="@android:drawable/ic_menu_add"></item>
    <item android:id="@+id/item2" android:title="@string/menu2"></item>
    <item android:id="@+id/item3" android:title="@string/menu3"></item>
    <item android:id="@+id/item4" android:title="@string/menu4"></item>
</menu>

メニュー1だけシステムの画像を設定して表示していますが、独自の画像を使うこともできます(デザイン方法は参考を参照)。メニューアイテムは最大6つまで同時に表示され、それ以上は6つ目のアイテムがMoreにかわり、ダイアログがでて6つ以上のメニューアイテムも選択できます。

Android 2.3(API 10)までのデバイスで実行すると下記のように表示されます。


Android 3.0(API 11)以降のデバイスで実行すると下記のように表示されます。



この違いは、今回の手法がAndroid2.3(API 10)までを対象としたやり方のためです。Android 3.0からは物理的なメニューボタンを必須からオプション扱いにしたため、それにあわせてオプションメニューに対するUIも変更したからです。Android 3.0からはAction Barというものが表示されるようになり、基本的にオプションメニューはそこに表示して、よりダイレクトに選択できるUIにかえています。

Android 3.0以降でもAction Barなしのテーマを選択している場合、Action BarがないのでAndroid 2.3以前と同様な表示になります。

参考:Android Developers:Activity
参考:Android Developers:Say Goodbye to the Menu Button
参考:Android Developers:Menu Icons

システムリソースの画像を使う

Androidにはシステムリソース(android.R)が用意されており、標準的なアイコン画像などが入っています。

使い方は簡単で、xmlで使いたい時は、@android:drawableと記述して使います。

<item android:id="@+id/item1" android:title="@string/menu1" android:icon="@android:drawable/ic_menu_add"></item>

ソースコードで使いたい時は、android.R.drawableと記述して使います。

item.setIcon(android.R.drawable.ic_menu_camera);

どんなリソースが用意されているかはドキュメントを参考にしてください。画像以外も用意されています。

参考:Android Developers:R.drawable

June 15, 2012

バイブレーションを使う

バイブレーションの使い方です。

バイブレーションの操作はVibratorクラスを使いますが、OSのシステムサービスとして提供されていますので、インスタンスはgetSystemService()で取得して操作します。機能は下記の3種類です。時間の単位はすべてms(ミリ秒)です。パターンはOFFから始まり、ON、OFF・・・と任意の回数で自由に設定できます。

  • 1回だけ指定時間ONにする
  • 指定のON/OFFパターンを1回だけONにする
  • 指定のON/OFFパターンを停止されるまでずっと繰り返す

例)上記の3種類で動かす場合

long pattern[] = {0, 100, 50, 200}; // Off, On, Off, On [ms]
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
switch (type) {
case 1:
    vibrator.vibrate(200); // [ms]
    break;
case 2:
    vibrator.vibrate(pattern, -1); // No repeat
    break;
case 3:
    vibrator.vibrate(pattern, 0); // Start repeat
    break;
case 4:
    vibrator.cancel(); // Stop repeat
    break;
}

パーミッションの設定が必要です。

<uses-permission android:name="android.permission.VIBRATE" />

パーミッションはPermissionsタブから簡単にブラウズして追加できます。


参考:Android Developers:Vibrator
参考:Android Developers:Context - getSystemService()

June 14, 2012

EclipseからGitを使ってAndroidアプリのソースコード管理

EclipseからGitを使ってAndroidアプリのソースコードを管理する方法です。GitはAndroidそのものの開発でも使われているソースコード管理ツールです。Gitを使ったことがない、よくわからない、という人は、下記のサイトが超オススメです。もともとは洋書でしたが日本語に翻訳された上に全文掲載されています。

「Pro Git」

★インストール

まずはEclipsenにGitのプラグイン「EGit」をインストールします。インストールは、EclipseのHelp --> Install New Softwareとすると、Available softwareの一覧がでます。Addボタンの下に青字でAvailable software sitesがあるので、http://download.eclipse.org/egit/updatesを選択してOKします。


すると、Eclipse Git Team ProviderにEclipse EGitがあるので、チェックをいれてOKしていけばインストール完了します。


★Gitの初期設定

GitをCommitする人を登録します。
Eclipse --> 環境設定 --> Team --> Git --> Configuration --> User settingsでnew entryで入力します。keyにuser.nameでvalueにユーザー名、user.emailでメールアドレスと2つentryします。

★Gitの簡単な使い方

AndroidアプリのProjectを作ったら右クリックでメニューからTeam --> Share Project --> Gitを選択します。



ここでリポジトリ名をつけてプロジェクトをGitの管理にします。これでローカルリポジトリが作成されました。

ソースコードを修正していくと、修正差分を見たくなると思いますので、そのときは、ソースコードの右クリックでCompare Withを選択してLocal Historyを選択します。履歴がでるので見たい差分をクリックすればソースコードの差分が見れます。


修正がOKであればコミットします。同様に右クリックでCommitを選択すると、Commitするファイルの選択とメッセージの記入ができるので設定してOKすればコミット完了です。

コミットの履歴はTeam --> Show in Historyで見れます。


ソースコードを修正していて前のコードに戻したい時はReplace With --> Commitで復元できます。

その他にも、ブランチを作ったり、リポートリポジトリを操作したり、いろいろ機能がありますので、テストプロジェクト上でいろいろ試してみると良いと思います。

参考:EGit公式サイト

June 13, 2012

Broadcast intentを送受信する

Broadcast intentの送受信の使い方です。

Broadcast intentとは、Implicit intent(暗黙的なintent)とほぼ同じものですが、1点だけ大きく異なるところがあります(Implicit intentについては「外部アプリから起動されるアプリを作る」を参照)。それは、Implicit intentが選択された1つのActivityしか受信できないのに対し、Broadcast intentは基本的に全Activityが受信できます。受信するかどうかIntent filterで設定するのは同じです。

ただし、基本的に全Activityと書いたのは、そうでないBroadcast intentもあるからです。Broadcast intentを投げる方法は2通りあり、1つはsendBroadcast()を使います。この場合、全Activityが一斉に(非同期に)受信できます。もう1つの方法はsendOrderedBroadcast()で、これは受信するActivityに優先順位をつけて1つづつ順番に通知していきながら全Activityまで通知していく方法です。この場合、途中でActivityが次に通知するか停止するか選択する権利があり、全Activityが受信できるとは限らない仕様になっています。優先順位はIntent filterのpriorityの値で決まります。同じ値の場合の優先順位は不定です。

このBroadcast intentはシステムも多く発信しており、バッテリーや通信状況、デバイスの接続、などなどさまざまな状況変化を通知しています。そのようなシステムの状態変化を察知するのにも使えます。どんなBroadcast intentがあるかはintentのドキュメントに一覧が記載されています。またアプリ間連携でオリジナルのBroadcast intentを通知することもできます。ただし、名前がかぶらないように通常はaction名にアプリのpackage名を先頭にいれるのが慣例のようです。

上記の方法で送信されたBroadcast intentは、専用のBroadcastReceiverクラスのみ受信することができます。通常のActivityクラスなどでは受信できません。

例)システムが発行する日付のタイムゾーン変化の通知の受信、およびオリジナルのBroadcast intentの発行と受信を行う場合、sendBroadcast()で発行し、BroadcastReceiverクラスを作成し、Intent filterを設定して受信します。

public class SampleAppActivity extends Activity {
    private final static String TAB = "SampleAppActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("jp.myapp.sample.clicked");
                sendBroadcast(intent);
            }
        });
    }
}

public class SampleReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("RECEIVE", "action:" + intent.getAction());
    }
}

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.myapp.sample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
        <activity
            android:name="SampleAppActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name="SampleReceiver">
            <intent-filter>
                <action android:name="android.intent.action.TIMEZONE_CHANGED"/>
                <action android:name="jp.myapp.sample.clicked"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

参考:Android Developers:BroadcastReceiver
参考:Android Developers:Intent

June 12, 2012

外部アプリから起動されるアプリを作る

外部アプリから起動されるアプリの作り方です。

「電話帳やブラウザなど外部アプリを起動する」では外部アプリをIntentを使って起動しましたが、今度はその逆にIntentを受け取って自分のアプリを外部から起動してもらう方法です。(正確にいうとアプリではなくActivityですが)

ここでいうIntentはImplicit intents(暗黙的なIntent)を指します。いわゆるAction、Category、Dataといった実行したい行動を記述して、それに対応できるアプリの起動を要求する方式です。ブラウザや電話などのアプリが起動するのは、そういうIntentを受け取れるとAndroidManifest.xmlで宣言しているからです。逆に言えば、自分のアプリでもそのような記述をすればIntentを受けれて外部アプリとして起動されるようになります。

その方法は、AndroidManifest.xmlでIntent FilterというものをActivityに設定することで実現できます。Intent Filterとは、Intentのフィルター、つまり自分が受け取れるIntentはこれですよ、それ以外は受け取りません、と宣言するものです。このフィルターに記載されたもののみ受け取ります。なにも記載がなければ一切受け取らないことを意味します。ただし、このフィルターはImplicit Intentsだけに適用されます(起動するActivityを直接指定するExplicit intentはフィルターする意味がないので関係ありません)。

例)URLの閲覧を外部アプリ起動要求する場合、通常はブラウザアプリが候補にあがりますが、そこに自分のアプリも候補にあがるようにします。下記のソースコードでImplicit intentを発行し、Activityの起動を要求します。IntentのactionはACTION_VIEW、dataのスキームはhttpとなります。

Uri uri = Uri.parse("http://android-dev-talk.blogspot.jp/");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

下記にはActivityが2つ登録されており、SubAppActivityのほうでIntent Filterを登録し、actionがaction.VIEW、dataのschemeがhttp、categoryがDEFAULTを受け取れるように設定しています。

action名がソースコードと違うように見えますが、実は同じです。Intent.ACTION_VIEWはStringの定数で、その中身は"android.intent.action.VIEW"です。Intentのドキュメントにすべて記載されていますので、確認して一致するものを設定します。

あと、ソースコードでは指定していないcategoryにDEFAULTを設定しています。これには理由があり、startActivity()で発行するImplicit intentは自動でcategoryにDEFAULTを付加します。フィルターのルールでcategoryが付加されているintentは、同じものがフィルターに設定されていないと不適合と判断されてしまいます。なので、この設定は必ず必要です。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.myapp.sample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
        <activity
            android:name="SampleAppActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="SubAppActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <data android:scheme="http"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

実行すると下記のようにSampleAppが候補にでてきます。


参考:Android Developers:Intents and Filters
参考:Android Developers:Intent

June 11, 2012

電話帳やブラウザなど外部アプリを起動する

アプリから電話帳やブラウザなど外部アプリを起動する方法です。

Intentを使ってstartActivity()で起動要求をかけることで、外部アプリを起動することができます。これはImplicit Intents(暗黙的なIntent)と呼ばれます。なぜかというと、Intentには起動したいアプリ名を直接指定するのではなく、Action、Category、Data(URIとMIMEタイプ)という実行したいことという間接的な情報を付加し、Android OSがそれを実行できると宣言しているアプリ(Intent Filterで受け取れると宣言している)を起動する仕組みになっています。実行できるアプリが複数ある場合はユーザーがその都度選択するダイアログがでてきます。

ActionやCategoryはIntentクラスでいわゆる一般的なものがたくさん定義されています。

例)ブラウザを起動する場合、Actionは表示要求、URIのスキームにhttpを指定します。

Uri uri = Uri.parse("http://developer.android.com/index.html");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

例)電話のダイヤルを起動する場合、Actionはダイヤル要求、URIのスキームにtelを指定します。

Uri uri = Uri.parse("tel:080-1234-5678");
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
startActivity(intent);

例)電話の通話を実行する場合、Actionはコール要求、URIのスキームにtelを指定します。

Uri uri = Uri.parse("tel:080-1234-5678");
Intent intent = new Intent(Intent.ACTION_CALL, uri);
startActivity(intent);

例)カメラ(静止画)を起動する場合

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivity(intent);

例)カメラ(ビデオ)を起動する場合

Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
startActivity(intent);

例)メールを起動する場合

Uri uri = Uri.parse("mailto:someone@abc.def");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(intent);

例)地図を表示する場合、URIのスキームに"geo:緯度、経度?q=住所"のように設定します。

Uri uri = Uri.parse("geo:0,0?q=東京駅");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

例)設定画面を起動する場合、設定内容に応じてandroid.provider.Settingsで指定します。ここでは表示設定画面を起動します。

Intent intent = new Intent(android.provider.Settings.ACTION_DISPLAY_SETTINGS);
startActivity(intent);

など、いろいろなアプリが対応しています。

逆に起動してもらうアプリを作る場合は「外部アプリから起動されるアプリを作る」を参照してください。

参考:Android Developers:Intents and Intent Filters
参考:Android Developers:Intent

June 10, 2012

アプリ内の別のActivityを起動する

アプリ内の別のActivityを起動する方法です。

Intentを使って、起動するActivityクラスと、必要であれば付加情報(データ)をつけてstartActivity()で起動することができます。クラス名を直接指定して起動するため、Explicit Intents(明示的なIntent)と呼ばれます。名指しで指定なのでIntentFiliterの設定は不要です。

例)タイトル情報を付加してSampleAppActivityからSubAppActivityを起動し、受け取ったタイトル情報を表示します。

public class SampleAppActivity extends Activity {
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getApplicationContext(), SubAppActivity.class);
                String title = getResources().getString(R.string.hello);
                intent.putExtra("TITLE", title);
                startActivity(intent);
            }
        });
 }
}

public class SubAppActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main2);

        Intent intent = getIntent();
        String title = intent.getStringExtra("TITLE");
        TextView view = (TextView) findViewById(R.id.text);
        view.setText(title);
    }
}

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
        <activity
            android:name="SampleAppActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="SubAppActivity"></activity>
    </application>

Intentにはさまざまな情報(intやbooleanなど)を複数付加することができますし、独自のクラスなどを付加することもできます。続きはこちらの記事に記載しました。

参考:Android Developers:Intent

スタイルをViewに適用する

スタイルをViewに適用する方法です。

複数のViewで表示スタイル(色や背景などさまざまなプロパティ)を統一するのに使います。Viewごとに複数のプロパティを同じ設定にするのは大変なので、スタイルを別途定義して、それを各Viewにそのスタイルを指定することで同じプロパティを設定できるようになります。

まずはスタイルをリソースとしてxmlファイルで定義し、res/valuesにおきます。例ではスタイルの名称はsample 、プロパティとしてテキスト色を1つ指定しています。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="sample">
     <item name="android:textColor">#FF3333</item>
    </style>
</resources>

xmlレイアウトファイルで定義したViewからスタイルを指定します。

<TextView
        style="@style/sample"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />


ソースコードでViewを新規作成した場合、直接このスタイルを適用する方法はありません。スタイルはxmlレイアウトで指定して、それを呼び出して表示するように作るのが良さそうです。

参考:Android Developers:Styles and Themes

タイトルバーとステータスバーを非表示にする

タイトルバーとステータスバーを非表示にできます。

デフォルトでは両方とも表示されていますが、AndroidManifest.xmlに設定することで非表示に変更することができます。

例)デフォルト表示(タイトルバーとステータスバー表示)


例)ステータスバーのみ表示
android:theme="@android:style/Theme.Black.NoTitleBar"


例)すべて非表示
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"


変更方法はAndroidManifest.xmlのApplicationタブで、Application AttributesにあるThemeのBrowseボタンを押します。Resource Chooserダイアログがでるので、System Resourcesから好きなThemeを選択します。


xmlファイル内部の記述としては下記のように変更されます。

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">

June 9, 2012

スレッドからUIを操作する

スレッド(Thread)からAndroidのUIを操作する方法です。

なにも考えずにスレッド上でUI操作してしまうと"CalledFromWrongThreadException"というfatal exceptionが投げられてアプリが強制終了してしまいます。

例)exceptionが発生する失敗例

public class SampleAppActivity extends Activity {
 private final static String TAB = "SampleAppActivity";
 private ProgressBar mProgress;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        mProgress = (ProgressBar) findViewById(R.id.progress);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                }
                mProgress.setVisibility(View.GONE);
            }
        }).start();
    }
}

これはなぜかというと、AndroidのUIはActivityがもっているUIスレッド上でしか動作しないようになっていて、他のスレッドから操作されると上記のexceptionを投げるようになっています。Androidの仕組みでは、UIの操作はすべてUIスレッドで処理させるように作らなければなりません。そのため他のスレッドからUIの操作を実行する場合は、Handlerというクラスを使って、処理をRunnableオブジェクトとしてUIスレッドにメッセージで送り、UIスレッドがそれを受け取って処理を実行する、というふうに作り替えます。

例)他のスレッドからUIの操作の成功例

public class SampleAppActivity extends Activity {
 private final static String TAB = "SampleAppActivity";
 private Handler mHandler = new Handler();
 private ProgressBar mProgress;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        mProgress = (ProgressBar) findViewById(R.id.progress);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                }
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mProgress.setVisibility(View.GONE);
                    }
                });
            }
        }).start();
    }
}

参考:Android Developers:Handler

android.jar内部のソースコードを見る

Androidアプリを作っていると、時々AndroidのAPIの内部がどう動いているかソースコード(つまりandroid.jarの中身)を確認したいときがでてきます。

通常はソースコードが見つかりません、といわれてしまいますが、


Eclipseにandroid.jarのソースコードを設定することで、そのまま閲覧できるようになります。もちろんデバッガーでトレースしていくことも可能です。

まず最初にandroid.jarのソースコードをダウンロードする必要があります。EclipseからAndroid SDK Managerを起動し、Sources for Android SDKをインストールします。


このソースコードは、フォルダ上では、"android-sdks/sources"にインストールされています。

続いてandroid.jarで右クリックでPropertiesを選択すると、jarのソースコードを選択できる画面が現れます。そこで、さきほどインストールしたソースコードのフォルダを指定すればOKです。


注意点は、jarとソースコードは一致したversionを必ず選択することです。異なるversionを指定したりすると、当然おかしなソースコードを見ることになります。

プログレスバーの作り方

プログレスバーの作り方です。ProgressBarクラスを使います。

プログレスバーは進捗状況を表示したり、作業中であることを表したりするのに使います。使い方はシンプルで進捗中であることを表現したい時だけ、表示をすればいいだけです。アニメーションは勝手に行ってくれます。進捗状況を表すときはその値をセットすれば表示されます。

例)3種類のプログレスバーを表示し、進捗状況を表すバーが2段階で進んでいき、最後にプログレスバー2つを消します。進捗状況の変化はスレッドを使って定期的に値を更新しています。

<ProgressBar
        android:id="@+id/progress1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@android:style/Widget.ProgressBar.Small" />
    <ProgressBar
        android:id="@+id/progress2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@android:style/Widget.ProgressBar.Large" />
    <ProgressBar
        android:id="@+id/progress3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@android:style/Widget.ProgressBar.Horizontal" />

public class SampleAppActivity extends Activity {
 private final static String TAB = "SampleAppActivity";
 private Handler mHandler = new Handler();
 private ProgressBar mProgress1, mProgress2, mProgress3;
 private final int PROGRESS_MAX = 256;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        mProgress1 = (ProgressBar) findViewById(R.id.progress1);
        mProgress2 = (ProgressBar) findViewById(R.id.progress2);
        mProgress3 = (ProgressBar) findViewById(R.id.progress3);
        mProgress3.setMax(PROGRESS_MAX);
        mProgress3.setProgress(10);

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(mProgress3.getProgress() < PROGRESS_MAX) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mProgress3.incrementProgressBy(3);
                            mProgress3.incrementSecondaryProgressBy(8);
                        }
                    });
                    try {
                        Thread.sleep(150);
                    } catch (InterruptedException e) {
                    }
                }
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mProgress1.setVisibility(View.GONE);
                        mProgress2.setVisibility(View.GONE);
                    }
                });
            }
        }).start();
 }
}


参考:Android Developers:ProgressBar

ラジオButtonの作り方

ラジオButtonの作り方です。

RadioButtonとRadioGroupクラスを使います。RadioButtonはButtonクラスを継承しており、ボタンの一種となります。Buttonクラスの機能もそのまま使うことができます。このRadioButtonクラスはラジオボタンだけでなくテキストも添えて表示することも可能です。

通常のボタンと大きく異なるのは、複数のラジオボタンをまとめたグループを作る必要がある、ということです。グループされたボタンの中で1つだけが押された状態になります。それをRadioGroupクラスで管理します。

例)3つのラジオボタンをグループ化して、初期値の設定や、現在押されているボタンの読み出し、ラジオボタンを押したとき、そのボタンのテキストを読み出します。

<RadioGroup
    android:id="@+id/menu"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <RadioButton
        android:id="@+id/radio1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/check1" />
    <RadioButton
        android:id="@+id/radio2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/check2" />
    <RadioButton
        android:id="@+id/radio3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/check3" />
</RadioGroup>

public class SampleAppActivity extends Activity {
 private final static String TAB = "SampleAppActivity";

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        RadioGroup menu = (RadioGroup) findViewById(R.id.menu);
        menu.check(R.id.radio2);
        Log.i(TAB, "ID:" + menu.getCheckedRadioButtonId());
        menu.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                RadioButton radio = (RadioButton) findViewById(checkedId);
                Log.i(TAB,"Checked:" + radio.getText());
            }
        });
 }
}


参考:Android Developers:RadioButton
参考:Android Developers:RadioGroup


チェックボックスButtonの作り方

チェックボックスButtonの作り方です。

CheckBoxクラスを使用します。これはButtonクラスを継承しており、ボタンの一種となります。ですので、Buttonクラスの機能もそのまま使うことができます。このCheckBoxクラスはチェックボックスだけでなくテキストも添えて表示することも可能です。

例)変数の値をみてチェックボックスの状態をセットし、isChecked()で状態を読み出す

<CheckBox
    android:id="@+id/checkbox"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/check" />

public class SampleAppActivity extends Activity {
    private final static String TAB = "SampleAppActivity";
    private boolean mState = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        CheckBox checkBox = (CheckBox) findViewById(R.id.checkbox);
        checkBox.setChecked(mState);

        if (checkBox.isChecked()) {
            Log.i(TAB,"Checked");
        } else {
            Log.i(TAB,"Not checked");
        }
    }
}


参考:Android Developers:CheckBox

SoundPoolで効果音を再生する

SoundPoolを使った効果音の再生方法です。

SoundPoolはMediaPlayerと違って事前にデコードしてオーディオデータを保持するため、メモリは消費しますが、遅延なく即座に再生できるということで効果音に向いています。効果音を複数同時再生することも可能です。

使い方もシンプルでSoundPoolのインスタンスを作成して、そこにオーディオデータをロードします。このロードは非同期処理で多少時間がかかり、ロード完了前に再生しようとすると"sample 1 not READY"のような警告ログがでます。そのため必要であればOnLoadCompleteListenerでロード完了通知を受け取ることもできます。ロードが完了したら、あとは再生を実行するだけで、いつでも再生できます。完全に使い終わったら、release()で全リソースを解放します。

再生できるオーディオフォーマットはMediaPlayerと同じですが、あまり巨大なファイルなどはデコードエラーがでて使えないことがあります。その場合、SoundPoolからエラーログがでますので、そのときはオーディオデータを疑いましょう。

例)ボタンをクリックすると2つの効果音(mp3)を同時に再生する場合、onCreate()で事前にロードまで完了させておき、ボタンをクリックしたら再生するように記述します。

public class SampleAppActivity extends Activity {
    private final static String TAB = "SampleAppActivity";
    private SoundPool mSePlayer;
    private final static int MAX_STREAMS = 10;
    private int[] mSound = new int[5];

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mSePlayer = new SoundPool(MAX_STREAMS, AudioManager.STREAM_MUSIC, 0);
        mSound[0] = mSePlayer.load(getApplicationContext(), R.raw.se1, 1);
        mSound[1] = mSePlayer.load(getApplicationContext(), R.raw.se2, 1);
        Button button = (Button) findViewById(R.id.play);
        button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              mSePlayer.play(mSound[0], 1.0f, 1.0f, 0, 0, 1.0f);
              mSePlayer.play(mSound[1], 1.0f, 1.0f, 0, 0, 1.0f);
          }
        });
    }
}


参考:Android Developers:SoundPool

June 8, 2012

AndroidエミュレータのSDカードにファイルを転送する

AndroidエミュレータのSDカードにファイルを転送する方法です。

これはコマンドラインから"adb"というコマンドを実行する必要があります。

adbコマンドは、android SDKのツールとしてインストールされています。android-sdks/platform-tools/adbフォルダにあります。もしパスが通っていなければ、パスを通しておくと便利です。

まずは"adb devices"と実行して、androidエミュレータが存在しているか確認してみましょう。うまくいけば、下記のようにデバイスが表示されます。

emulator-5554 device

確認できたら、"adb push ファイル名 /sdcard/"で、ファイルをsdcardの直下におくことができます。

もしそれ以外のフォルダにおきたい時は、もしそのフォルダが存在しない場合、フォルダを事前に作成する必要があります。その場合は、"adb shell"でシェルモードに入ります。

#cd sdcard
#mkdir フォルダ名

というふうにすればSDカードにフォルダが作れます。

#exit

で終了し、その後、そのフォルダにadb pushすればOKです。

MediaPlayerでBGMを再生する

MediaPlayerを使ってBGMを再生する方法です。

MediaPlayerは逐次再生するため、長大なファイルであってもメモリ消費量が少ない特徴があります。再生開始の遅延があるため、BGMのような再生開始タイミングの精度を厳しく求めないBGMのような再生に向いています。

再生できるファイルは参考にあるAndroid Supported Media Formatsを見てください。MP3やWAVなど、たいていのオーディオフォーマットはサポートしています。

MediaPlayerを使う注意点は、ステートマシン(つまりMediaPlayerそのものが状態をもっていて、ユーザーがきちんと状態を判断して制御する必要がある)なので、状態を把握して、その状態に応じたAPIを使用しなければなりません。ステートの遷移は参考にあるMediaPlayerから引用すると下記のようになっています。APIを発行するたびに状態が遷移していきます。


再生可能状態にするprepare()は時間のかかる処理のため、同期処理のprepare()だけでなく、非同期処理のprepareAsync()があります。onPrepared()を受け取ることで完了通知を受け取ることができます。また、使用が完了した場合(もう使わない場合)、release()を呼んでリソースを解放する必要があります。これをしないで何度もMediaPlayerを使うとそのうち使用できなくなる可能性があります。

MediaPlayerは再生、停止、一時停止、ループ再生、音量調整など機能もいろいろそろっています。

例)リソースにあるオーディオデータを再生する場合、res/raw/フォルダにデータをおきます。ここではbgm.mp3をおいています。create()は特殊で実行完了するとPrepared状態なので、いきなりstart()を呼ぶだけで再生できます。ループ再生するようにセットも加えています。

public class SampleAppActivity extends Activity {
    private final static String TAB = "SampleAppActivity";
    private MediaPlayer mBgmPlayer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mBgmPlayer = MediaPlayer.create(this, R.raw.bgm);
        mBgmPlayer.setLooping(true);
        mBgmPlayer.start();
    }
}

例)SDカードなど外部ストレージにあるファイル(ここでは/sdcard/bgm.mp3)を再生する場合、下記のように指定します。ファイル名をパスで指定しますが、このとき直接"sdcard"と指定せず、あえてEnvironment.getExternalStorageDirectory()でシステムから外部ストレージのフォルダを取得しています。必ずしも外部ストレージが"sdcard"とは限らない(内部ストレージの場合もある)ため、例ではこのようにしています。

public class SampleAppActivity extends Activity {
    private final static String TAB = "SampleAppActivity";
    private MediaPlayer mBgmPlayer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mBgmPlayer = new MediaPlayer();
        try {
            mBgmPlayer.setDataSource(Environment.getExternalStorageDirectory().getPath() + "/" + "bgm.mp3");
            mBgmPlayer.prepare();
        } catch (IllegalArgumentException e) {
        } catch (SecurityException e) {
        } catch (IllegalStateException e) {
        } catch (IOException e) {
        }
        mBgmPlayer.start();
    }
}

参考:Android Developers:MediaPlayer
参考:Android Developers:Android Supported Media Formats

June 7, 2012

画像とdrawable-xxxの関係

Androidではいろいろなスクリーン(LCD)が存在するため、どのスクリーンでも最適に表示できるように工夫がされています。画像の表示に関していえば、スクリーンのdpiによって分類され、画像を格納するフォルダには次の4つの分類があります。
  • drawable-xhdpi(extra high density、320dpiまで)
  • drawable-hdpi(high density、240dpiまで)
  • drawable-mdpi(middle density、160dpiまで)
  • drawable-ldpi(low density、120dpiまで)
ちなみにdrawableはdrawable-mdpiと同じ扱いになります。

画像を用意するときは、それぞれのdpiに応じて用意するこができ、それぞれのサイズの比率はmdpiを基準(×1.0)として、ldpiは×0.75、hdpiは×1.5、xhdpiは×2.0になります。かといってそれぞれのdpi用にすべて画像を用意する必要は必ずしもありません。最低1つ用意されていれば、それを代用してスケーリングして表示してくれます。

ただし、例えば1つしか用意しない場合は、その画像をどのフォルダにおくかによって意味がかわってきますので注意が必要です。いま下記のような64×64ピクセルの画像を用意したとします。


それをdrawable-mdpiフォルダにおいた場合、それをmdpiのスクリーンで表示するとそのままのサイズで表示されますが、hdpiのスクリーンで表示すると拡大(×1.5)して表示されます。


逆にその画像をdrawable-hdpiフォルダにおいた場合、それをhdpiのスクリーンで表示するとそのままのサイズで表示されますが、mdpiのスクリーンで表示すると縮小(×0.67)して表示されます。


スケーリングがはいれば画像はそのままでなく加工されて表示されますので、奇麗に表示される場合もあれば、見苦しくなる場合もあります。

以上のことを考慮しながら画像のリソースを用意する必要があります。

参考:Android Developers:Supporting Multiple Screens

June 6, 2012

dpとピクセル、画像の単位

Androidの画像の単位で一番よく使うdp(またはdip)とピクセルの関係の説明です。

dpとはdevice independent pixelの略で、デバイス非依存のピクセル、もう少し具体的にいうとLCDデバイスに依存しないピクセルという単位のことです。

定義は「160dpiのときの1dp=1ピクセル」です。

なぜピクセルでなくこんなdpという単位を使うかというと、Androidはさまざまな端末があり、LCDのdpi(dot per inch、1インチあたりのピクセル数)もバラバラです。そんなバラバラなデバイスであっても、アプリでこの大きさで表示したいといった画像は、どんなデバイスでも同じ大きさで表示してほしいからです。

もしここで物理的なサイズのピクセルで大きさを指定すると、例えばアプリが160ピクセルの正方形を表示するとして、160dpiのLCDで表示したときの物理的な大きさは1インチになりますが、320dpiのLCDで表示したときには半分の大きさの0.5インチになってしまいます。これをLCDのdpiに関係なく同じ物理的な大きさで表示させるためにdpを使います。

もし先ほどの例で160ピクセルでなく160dpを指定した場合、160dpiのLCDのときは160ピクセルに変換されて表示され、物理的な大きさは1インチになります。320dpiのLCDのときは320ピクセルに変換されて表示され、物理的な大きさは同じ1インチになります。

機種依存のないアプリを作るにはdpは非常に役に立ちます。

注意点はxmlレイアウトファイルなどでは単位を自由に指定でき、dp(またはdip)も当然使えるのですが、ソースコードのAPIのサイズ指定はたいていはピクセルです。そのときにdpを使いたい時は手動で変換する必要があります。

変換方法は、「ピクセル=dp × getResources().getDisplayMetrics().density」です。

その他の画像の単位として、
  • pt:ポイント、1/72インチ
  • mm:ミリメーター
  • in:インチ
も使えます。

June 5, 2012

ImageViewで画像表示

ImageViewクラスを使って画像を表示します。
xmlレイアウトファイルに記述する方法とソースコードで記述する方法があります。
結果はどちらも同じで、下記のようになります。


例)xmlレイアウトファイルで記述する方法

<ImageView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_launcher" />

例)ソースコードで記述する方法

    LinearLayout layout = (LinearLayout) findViewById(R.id.layout);
    ImageView view = new ImageView(this);
    view.setImageResource(R.drawable.ic_launcher);
    view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    layout.addView(view);

画像のサイズはコンテンツと同じサイズになるようにwrap_contentを指定しています。xmlで指定しているlayout_width、layout_heightとソースコードでいうsetLayoutParams()は同じ設定を表します。ここで指定するviewのサイズに応じて画像のサイズもアスペクト比を維持できる範囲で最大の大きさに拡大・縮小されます。ちなみにサイズは絶対値で指定もできますが、setLayoutParams()のほうは単位がピクセル固定なので、dpで指定したい場合は変換が必要です。

参考:Android Developers:ImageView

June 4, 2012

ViewFlipperによるView切り替えアニメーション

Viewから別のViewへ切り替えるとき、アニメーション付きで切り替える方法です。ViewFlipperクラスという専用のクラスが用意されており、それを使うと簡単にいろいろなアニメーションでの画面遷移が実現できます。

基本の考えは、ViewFlipperに複数のViewを登録し、それらのViewの間で切り替えができますが、その切り替えのアニメーションはAnimationクラスで設定し、それをInする側とOutする側それぞれに適用することで実現します。

アニメーションは、アルファ、スケール、移動、回転の4種類でタイミングなども細かく設定でき、複数組み合わせることもできます。

例では、3つのViewの切り替えを、アルファと移動を組み合わせたアニメーションで左右にボタンで遷移できるものを作ります。まず最初に完成品の動画から。


最初にアニメーションのxmlを作ります。XMLファイルを作成するとき、Tween Animationのsetを選択します。リソースは自動で、res/animにおかれます。左方向へのIn/Out、右方向へのIn/Outの4パターン必要になります。アニメーションの定義はfromからtoへの状態の変化と、その変化をいつから、どのぐらいの時間で遷移させるか、タイミングの設定を記載します。translateでは画面端から画面中央へ、alphaでは透明0%から100%へ変化させています。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:fromXDelta="-100%"
        android:toXDelta="0%"
        android:duration="600" />
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:startOffset="100"
        android:duration="400" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:fromXDelta="0"
        android:toXDelta="-100%"
        android:duration="500" />
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:startOffset="100"
        android:duration="400" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:fromXDelta="100%"
        android:toXDelta="0%"
        android:duration="500" />
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:startOffset="100"
        android:duration="400" />    
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:fromXDelta="0"
        android:toXDelta="100%"
        android:duration="600" />
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:startOffset="100"
        android:duration="400" />
</set>

3つのViewですが、すべて同じViewとします。サブViewとしてレイアウトを作成して、それをメインViewのレイアウトで3つincludeして、それをViewFlipperのView群として登録します。

<?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="horizontal" >
    <Button
        android:id="@+id/previous"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:text="@string/previous" />
    <ImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/myname" />
    <Button
        android:id="@+id/next"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:text="@string/next" />
</LinearLayout>
<?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="wrap_content"
    android:orientation="vertical" >
<ViewFlipper
    android:id="@+id/flipper"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
<include
    android:id="@+id/page1"
    layout="@layout/sub" />
<include
    android:id="@+id/page2"
    layout="@layout/sub" />
<include
    android:id="@+id/page3"
    layout="@layout/sub" />
</ViewFlipper>
</LinearLayout>

ソースコードでは最初にAnimationをロードし、ボタンのクリックを登録します。クリックされたら、どちらのボタンを押されたか判定して、ViewFlipperで適切なアニメーションを設定して画面遷移を行います。

public class SampleAppActivity extends Activity implements OnClickListener {
    private final static String TAB = "SampleAppActivity";
    private ViewFlipper mFlipper;
    private Animation mAnimRightIn;
    private Animation mAnimRightOut;
    private Animation mAnimLeftIn;
    private Animation mAnimLeftOut;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        mFlipper = (ViewFlipper) findViewById(R.id.flipper);

        // Load animation
        mAnimRightIn = AnimationUtils.loadAnimation(this, R.anim.right_in);
        mAnimRightOut = AnimationUtils.loadAnimation(this, R.anim.right_out);
        mAnimLeftIn = AnimationUtils.loadAnimation(this, R.anim.left_in);
        mAnimLeftOut = AnimationUtils.loadAnimation(this, R.anim.left_out);

        // Set button click action
        int pages[] = {R.id.page1, R.id.page2, R.id.page3};
        for (int page : pages) {
            LinearLayout layout = (LinearLayout) findViewById(page);
            Button nextButton = (Button) layout.findViewById(R.id.next);
            nextButton.setOnClickListener(this);
            Button previousButton = (Button) layout.findViewById(R.id.previous);
            previousButton.setOnClickListener(this);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.next:
            mFlipper.setInAnimation(mAnimRightIn);
            mFlipper.setOutAnimation(mAnimLeftOut);
            mFlipper.showNext();
            break;
        case R.id.previous:
            mFlipper.setInAnimation(mAnimLeftIn);
            mFlipper.setOutAnimation(mAnimRightOut);
            mFlipper.showPrevious();
            break;
        }
    }
}

参考:Android Developers:ViewFlipper