3月 30
今日はボタンのカスタマイズの話しです。

Android で用意されているボタン類はカスタマイズで見栄えを変えることができます。ここでは Button, CheckBox, RadioButton, ToggleButton で見栄えをチューンしてみました。左がチューン前、右がチューン後です。(チューン後に見栄えが良くなっているかどうかは微妙ですが、、)

device-1.png device-2.png

ボタンのクラス階層

よく使うボタン部品には Button, CheckBox, ToggleButton, RadioButton, ImageButton などがありますが、これらの部品のクラス階層は以下のようになっています。

java.lang.Object
  android.view.View
    android.widget.TextView
      android.widget.Button
        android.widget.CompoundButton
          android.widget.CheckBox
          android.widget.ToggleButton
          android.widget.RadioButton

ちなみに ImageButton のクラス階層はこうです。

java.lang.Object
  android.view.View
    android.widget.ImageView
      android.widget.ImageButton

ボタンの使い方

次にいろいろあるボタンの使い道について考えてみました。私の主観なのでそうではないかもしれませんがだいたいこんな感じでしょう。

Button

いわゆるボタン用の部品。押すと何かアクションを発生させたいときに使用します。ステートは保持しません。

Button は background パラメータに drawable 要素を指定することで見栄えを変更することができます。実は Button の実体は TextView を継承しただけのクラスで機能に代わりはありません。スタイル定義で background のスタイルを変更しているだけに過ぎません。つまり background を変更してしまう場合は Button をベースにしても TextView をベースにしても同じことが実現できます。もっと言うとボタンに text が必要ない場合(画像を指定する場合など)は View をベースとしても同じことでできます。

ImageButton

Button と機能は同じです。ImageView を継承しており ImageView の機能が使用できます。Button と ImageButton の関係は TextView と ImageView のボタンの関係と同じです。

CompoundButton

OFF/ON のステートを持ったボタンの共通機能を具備した抽象クラスです。抽象クラスなのでこれ自体をそのまま部品として使うことはできません。ステート(OFF/ON)を持ち、背景とは別にその状態を示す表現を button パラメータで指定できます。この button パラメータに @null を指定することで状態を示す部分を無くすることも可能です。(この button に @null を指定する方法はサンプルアプリの RadioButton の2つ目の例として紹介しています。)

CheckBox

OFF/ON の状態を表現するボタンで ToggleButton と同じ機能を持ちます。CheckBox はどちらかというと文章メインで長い文字列を指定する場合はこちらを使うと良いでしょう。この実体は CompoundButton そのままで、そのデザインがスタイルで定義されているだけです。(TextView と Button の関係と同じです。)

ToggleButton

OFF/ON の状態を表現するボタンで CheckBox と同じ機能を持ちます。ToggleButton は CheckBox とは逆でイメージがメインで指定する文字も長いものは適していません。いわゆるスイッチを作る場合に重宝する部品だと思います。なお、ステートが OFF/ON のときのテキストを textOff/textOn パラメータで指定することができます。

RadioButton

複数の中からひとつを選択する場合に用いるボタン。RadioGroup と複数の RadioButton を組み合わせて用いる。単体としては CompoundButton ほぼそのままであるが、1点 ON 状態の場合にボタンが押されても OFF にならないように toggle() メソッドがオーバーライドされている。サンプルアプリでは button パラメータを活かしたチューンと、button パラメータに @null を指定し機能を殺したチューンの2通りを紹介しています。

ステートリスト

background パラメータや button パラメータでは状態によって drawable を変化できるステートリストを指定するのが一般的です。ステートリストのフォーマットは以下の通りです。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:constantSize=["true" | "false"]
    android:dither=["true" | "false"]
    android:variablePadding=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:state_pressed=["true" | "false"]
        android:state_focused=["true" | "false"]
        android:state_selected=["true" | "false"]
        android:state_checkable=["true" | "false"]
        android:state_checked=["true" | "false"]
        android:state_enabled=["true" | "false"]
        android:state_window_focused=["true" | "false"] />
</selector>

この方法を使って各種状態の drawable を指定すればいいのですが、どこまで指定すれば良いか迷ってしまいますよね。ちなみに私は次のような基準で使っています。(サンプルコードはこの方針で作成しています。)

Button

「無効」「通常」「押された状態」の3種類。

CheckBox, ToggleButton, RadioButton

「無効」「OFF状態」「ON状態」の3種類。

サンプルコード

いつものようにサンプルアプリのソースコードは Google Code で公開しています。完全なサンプルコードを参照したい場合はそちらをどうぞ。


Posted by sak+

Tagged with:
3月 22
本日(3/22)から Amazon 独自の Android Market である Amazon Appstore for Android がオープンしました。

amazon.png

使い方はまだ把握できていませんが、専用の Android アプリも用意されてるようですね。(左図)実は私のアプリもいくつか販売されています。(右図)

device-1.png device-2.png

Google の Android のマーケットは Google Checkout の認知度の問題もあり有料アプリの課金に大きな障壁がありましたので、既に多くのアカウントを持つ Amazon の参入には多いに期待しています。

Amazon Appstore の成否には私も注目していきます。

最後に私の Android アプリの URL はこちら。Amazon Appstore (sak+)


Posted by sak+

Tagged with:
3月 21
次に『CF電話帳』で使用しているインデックスの実装方法を紹介します。

といっても難しいことをしているわけではありません。
インデックスには Gallery を使用しています。

device-02.png

サンプルアプリ

今回のサンプルアプリではこのカスタムインデックスの部分を抜き出して紹介します。

device-3.png

このサンプルアプリでは連絡先のグループへのアクセスを行います。 AndroidManifest.xml でパーミッションの追加を忘れないように。

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

次にメインとなるソースコードです。至って普通ですね。選択した項目は selector を使って表示色を変えています。また選択内容によってコンテンツの内容および背景色を変えています。

public class Main extends Activity {

    private class MyItem {
        long id;
        String title;
        MyItem(long id, String title) {
            this.id = id;
            this.title = title;
        }
    }

    private ArrayList<MyItem> mItems = new ArrayList<MyItem>();

    private TextView tvText;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        /*
         * テキスト
         */

        tvText = (TextView)findViewById(R.id.text);

        /*
         * グループを追加する
         */

        String sort = Groups.TITLE + " DESC";

        Cursor c = getContentResolver().query(
                        Groups.CONTENT_URI,
                        new String[] {
                            Groups._ID,      // 0
                            Groups.TITLE,    // 1
                        },
                        null,
                        null,
                        sort);

        int firstPos = 0;
        while (c.moveToNext()) {
            long id = c.getLong(0);
            String title = c.getString(1);
            if (title.equals("Starred in Android")) {
                continue;    // skip
            } else if (title.startsWith("System Group: My Contacts")) {
                continue;    // skip
            } else if (title.startsWith("System Group: Friends")) {
                title = "友人";
            } else if (title.startsWith("System Group: Family")) {
                title = "家族";
            } else if (title.startsWith("System Group: Coworkers")) {
                title = "同僚";
            }
            mItems.add(new MyItem(id, title));
            firstPos ++;
        }
        c.close();

        /*
         * 全部、お気に入りを追加する
         */

        mItems.add(new MyItem(-1, "全部"));
        mItems.add(new MyItem(-2, "☆"));

        /*
         * あかさたなを追加する
         */

        mItems.add(new MyItem(-11, "あ"));
        mItems.add(new MyItem(-12, "か"));
        mItems.add(new MyItem(-13, "さ"));
        mItems.add(new MyItem(-14, "た"));
        mItems.add(new MyItem(-15, "な"));
        mItems.add(new MyItem(-16, "は"));
        mItems.add(new MyItem(-17, "ま"));
        mItems.add(new MyItem(-18, "や"));
        mItems.add(new MyItem(-19, "ら"));
        mItems.add(new MyItem(-20, "わ"));
        mItems.add(new MyItem(-21, "#"));

        MyAdapter adapter = new MyAdapter(this);

        Gallery g = (Gallery) findViewById(R.id.gallery);

        g.setFocusableInTouchMode(true);
        g.setFadingEdgeLength(150);
        g.setCallbackDuringFling(false);

        g.setAdapter(adapter);

        g.setSelection(firstPos);    // setAdapter の後ろで指定する。

        g.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
                String title = mItems.get(position).title;
                tvText.setText(title);

                long _id = mItems.get(position).id;
                if (_id < -10) {
                    tvText.setBackgroundColor(Color.GREEN);
                } else if (_id < 0) {
                    tvText.setBackgroundColor(Color.BLUE);
                } else {
                    tvText.setBackgroundColor(Color.RED);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> arg0) {
            }
        });
    }

    private class MyAdapter extends BaseAdapter {
        private LayoutInflater mInflater;

        public MyAdapter(Context context) {
            mInflater = LayoutInflater.from(context);
        }

        @Override
        public int getCount() {
            return mItems.size();
        }

        @Override
        public Object getItem(int position) {
            return position;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.item, null);
                holder = new ViewHolder();
                holder.tvTitle = (TextView) convertView.findViewById(R.id.title);
                convertView.setTag(holder);

            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            holder.tvTitle.setText(mItems.get(position).title);

            return convertView;
        }

        class ViewHolder {
            TextView tvTitle;
        }
    }

サンプルコード

サンプルアプリのソースコードは Google Code で公開しています。完全なサンプルコードを参照したい場合はそちらをどうぞ。


Posted by sak+

Tagged with:
3月 19
今日はタブのカスタマイズの話題です。CF電話帳では電話帳、発着信履歴、連絡先をタブで切替えています。この際、標準のタブではなくカスタムタブを使用しています。

device-1.png

サンプルアプリ

今回のサンプルアプリではこのカスタムタブの部分を抜き出して紹介します。

device-2.png

まずは肝となる部分のソースを記載します。ここで大事な部分は tabHost の indicator に独自に作成した View を指定するところです。

public class Main extends TabActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final TabHost tabHost = getTabHost();

        View v1 = new MyView(this, "いち", R.drawable.ic_tab_1);
        View v2 = new MyView(this, "にー", R.drawable.ic_tab_2);
        View v3 = new MyView(this, "さん", R.drawable.ic_tab_3);

        // いち
        tabHost.addTab(tabHost.newTabSpec("TAB_1")
                .setIndicator(v1)
                .setContent(new Intent(this, Tab1.class)));

        // にー
        tabHost.addTab(tabHost.newTabSpec("TAB_2")
                .setIndicator(v2)
                .setContent(new Intent(this, Tab2.class)));

        // さん
        tabHost.addTab(tabHost.newTabSpec("TAB_3")
                .setIndicator(v3)
                .setContent(new Intent(this, Tab3.class)));

        tabHost.setCurrentTab(0);
    }

    private class MyView extends FrameLayout {
        private LayoutInflater inflater;

        public MyView(Context context) {
            super(context);
            inflater = LayoutInflater.from(context);
        }

        public MyView(Context context, String title, int icon) {
            this(context);

            View v = inflater.inflate(R.layout.tabwidget, null);

            // アイコン
            ImageView iv = (ImageView) v.findViewById(R.id.icon);
            iv.setImageResource(icon);

            // テキスト
            TextView tv = (TextView) v.findViewById(R.id.text);
            tv.setText(title);

            addView(v);
        }
    }
}

次に独自に指定した View のレイアウトの中で指定する部品に selecotr を指定した画像やテキスト色を指定します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      :
   android:background="@drawable/tab"       <-- ①
   >
    <ImageView
      :
       android:src="@drawable/ic_tab_1"     <-- ②
       />
    <TextView
      :
       android:textColor="@color/tab_text"  <-- ③
       />
</LinearLayout>

selector とは状態によってリソースをで選択状態や非選択状態のそれぞれの状態の画像や色を指定した xml 形式のファイルです。

タブの背景色(res/drawable/tab.xml)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 非選択時 -->
    <item
       android:state_selected="false"
       android:drawable="@drawable/tab_unselected"
       />
    <!-- 選択時 -->
    <item
       android:state_selected="true"
       android:drawable="@drawable/tab_selected"
       />
</selector>

アイコン画像(res/drawable/ic_tab_1.xml)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 非選択時 -->
    <item
       android:state_selected="false"
       android:drawable="@drawable/ic_tab_1_unselected"
       />
    <!-- 選択時 -->
    <item
       android:state_selected="true"
       android:drawable="@drawable/ic_tab_1_selected"
       />
</selector>

テキスト色(res/color/tab_text.xml)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 非選択時 -->
    <item
       android:state_selected="false"
       android:color="#f888"
       />
    <!-- 選択時 -->
    <item
       android:state_selected="true"
       android:color="#ffff"
       />
</selector>

サンプルコード

サンプルアプリのソースコードは Google Code で公開しています。


Posted by sak+

Tagged with:
3月 12
昨日、札幌で開催されたパケットキャプチャー勉強会 Hokkaido.cap #1 で使用した LT 資料を公開します。


Posted by sak+

Tagged with:
3月 10
Android の会のメーリングリストで電話の発信のフックについての議論が盛り上がってますが、ACTION_CALL_PRIVILEGED をフックするっていう手もあるんですね。私も知りませんでした。というわけで team-hiroq さんの「CALLアクションをフック – Android」という記事を参考に実際にやってみました。

うーむ。フィルターに追加するだけで簡単に Call をフックできちゃうんですね。知らなかったー

せっかくなのでこれもサンプルに加えちゃいます。ただ team-hiroq さんと同じ方法では面白くないので設定によってこのフィルターを有効にしたり無効にしたりできるようにしました。この際 IntentFilter 自体を Activity に追加したり削除したりできないか調べたんですがそのような API が見当たりません。そんなとき Twitter で @dumapick さんから PackageManager.setComponentEnabledSetting を使ったらいいんじゃね?というツイートをいただき、Taosoftware さんの「Android Intent呼び出しを自分でコントロール方法」という記事を見つけ出しました。

要するに IntentFilter を持った Activity ごと有効にしたり無効にしたりということですね。

で、どっちが強いの?

で、前回記事にした NEW_OUTGOING_CALL を使った方法と今回紹介した CALL_PRIVILEGED を使う方法。2つ同時に適用した場合どっちが優先されるか気になるところなのですが、実際に試したところメーリングリストで team-hiroq さんが指摘しているように CALL_PRIVILEGED の方が優先されるようです。

使い分け

なので、NEW_OUTGOING_CALL と CALL_PRIVILEGED の使い分けを考えるとすると。ユーザに使うアプリを意識させても良い場合は CALL_PRIVILEGED 、意識させたくない場合は NEW_OUTGOING_CALL ということになるでしょうか?例えば、電話発信先を制限するようなアプリを作る場合には NEW_OUTGOING_CALL しかないと思いますし。IP電話のようなアプリは CALL_PRIVILEGED でも良いかな?と思います。

サンプルアプリ

それでは今回のサンプルアプリの紹介をします。

起動するとひとつのチェックボックスとひとつのボタンを持った画面が現れます。

device-1.png

チェックボックスを有効にして電話の発信を行おうとすると、アプリの選択画面が現れます。この場合、通常の「電話」とこのサンプルアプリ「CallHook」に2択です。

device-2.png

「CallHook」を選択すると電話を発信せず用意した Activity を開きます。発信しようとした電話番号が取得できるので表示させています。

device-3.png

サンプルコード

前回同様、サンプルアプリのソースコードは Google Code で公開しています。


Posted by sak+

Tagged with:
3月 08
Android の会のメーリングリストで電話の発信をフックする方法についての話題が挙っていたので、私の知っている範囲で記事にします。

Android 端末での電話発信について

Android の電話アプリは実際には2つのアプリから構成されています。

  • Contacts (com.android.contacts)
  • Phone (com.android.phone)

Contacts はダイヤラーや電話帳などのアプリです。Phone は実際に通話を制御するアプリです。発着信画面や通話画面は Phone のアプリです。この2つのアプリと電話帳や発着信履歴などのデータ(ContentProviderで保持)は互いに連携して1つのアプリケーションかのように動作しています。

Contacts が Phone を呼び出すトリガーは暗黙的INTENT ACTION_CALL です。Phone はこれを受けたのち一旦 NEW_OUTGOING_CALL をブロードキャスト送信します。ここで他のアプリが発信をフックする機会を与えます。通常はそのまま自分自身がこのブロードキャストを受信し発信処理を進めます。

new_outgoing_call.png

サンプルアプリ

実際に NEW_OUTGOING_CALL を捕捉するサンプルアプリを作ってみました。

発信のたびに NEW_OUTGOING_CALL を捕捉しても困ってしまいますので、アプリから捕捉するタイミングを制御できるようにしました。このため単純なアプリではなく、Activity と Service と Receiver からなるアプリケーションになっています。

アプリを起動するとサービスを開始するボタン、サービスを停止するボタン、ダイヤラーを開くボタンの3つのボタンを表示した画面が現れます。これがメイン画面です。

device-1.png

サービスを開始するボタンを押すと NEW_OUTGOING_CALL の捕捉を開始します。サービス起動中はステータス画面にこのメイン画面を呼び出すための Notofication が表示されています。

ダイヤラーを開く画面を押すと『117』が入力された状態でダイヤラー画面が開きます。

device-2.png

必要に応じてここで電話番号を変更してください。電話番号を確認したら発信を行います。本来ならば発信画面が現れて電話の発信が行われるはずですが、サンプルアプリで NEW_OUTGOING_CALL をフックして別の画面が表示されます。

device-3.png

サンプルコード

ここで挙げたサンプルアプリのソースコードは Google Code で公開しています。

注意点としては、NEW_OUTGOING_CALL を捕捉するために AndroidManifest.xml の中で以下のパーミッションを指定するのを忘れてはいけません。

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


Posted by sak+

Tagged with:
3月 07

Google Code でサンプルコードの公開を開始したわけですが数が多くなるとひとつひとつのアプリをラウンチャーから起動するのが面倒になります。

そんなときに便利なのが『sak samples exploler』です。

名前がアレなのですが、語呂で決めました。
要するに Android SDK に付属の API DEMO のような感じのアプリです。INTENT のカテゴリーに「android.intent.category.SAK_SAMPLES」を持つ Activity をリストに表示し、このアプリから起動できるようになります。

sak-samples-explorer.png

実はサンプルコードは INTENT のカテゴリーに「android.intent.category.SAMPLE_CODE」という記述もしてあるので API DEMO のメニューにも追加するようになっています。

apidemo-1.png apidemo-2.png

インストールするサンプルコードが多くなる場合は、各サンプルコードの AndroidManifest.xml からを「android.intent.category.LAUNCHER」を削除(またはコメントアウト)すればラウンチャーを汚しません。この場合、sak samples exploler または API Demo を利用してサンプルコードを利用することができるようになります。

<activity android:name="Main">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <!--
         <category android:name="android.intent.category.LAUNCHER" />
        -->
        <category android:name="android.intent.category.SAMPLE_CODE" />
        <category android:name="android.intent.category.SAK_SAMPLES" />
    </intent-filter>
</activity>

sak samples explorer 自身のアプリケーションも Google Code で公開していますので、参考にしてください。


Posted by sak+

Tagged with:
3月 04
ここ数日、ブログでサンプルコードを紹介してきましたが、これらを整理して『sak-android-samples』って名前で Google Code にて公開しました。自分があとで再利用できるようにまとめたものですが、よければみなさんも使ってください。コードのライセンスは Apache License 2.0 です。


googlecode.png

これからも少しずつ整理して公開していきます。乞うご期待を!


Posted by sak+

Tagged with:
3月 03

今回は2つの View の間でアイテムを Drag and Drop で行き来させるサンプルです。

まず、上下に2つの GridView ベースのカスタムビューを用意します。その中に予め5つのアイテムを配置します。これらのアイテムは Drag and Drop で上下の View を行き来させることができます。

device-1.png device-2.png

サンプルコード

このサンプルは明日の鍵さんのブログの記事を参考にさせてもらいました。

res/layout/main.xml

Drag and Drop をサポートした GridView ベースのカスタムビュー (sak.samples.DragAndDropView) を2つ配置します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <sak.samples.DragAndDropView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:background="#2f88"
        android:numColumns="5"
        android:layout_margin="10dp"
        android:padding="10dp"
        />
    <sak.samples.DragAndDropView
        android:id="@+id/list2"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:background="#28f8"
        android:numColumns="5"
        android:layout_margin="10dp"
        android:padding="10dp"
        />
</LinearLayout>

res/layout/item.xml

View に表示するアイテムは ImageView と TextView をそれぞれ1つずつ持っています。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    >
    <ImageView
        android:id="@+id/icon"
        android:src="@drawable/icon"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:scaleType="center"
        />
    <TextView
        android:id="@+id/name"
        android:text="dummy"
        android:textSize="12dp"
        android:gravity="center_horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />
</LinearLayout>

src/sak/samples/DragAndDropDemo

View の定義と表示を行います。アイテムは ArrayList で管理し、表示には ArrayAdapter ベースのカスタムクラスを用いています。

public class DragAndDropDemo extends Activity {
    private DragAndDropView mView1;
    private DragAndDropView mView2;
    private MyAdapter mAdapter1;
    private MyAdapter mAdapter2;
    private ArrayList<Item> mItems1 = new ArrayList<Item>();
    private ArrayList<Item> mItems2 = new ArrayList<Item>();
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        /*
         * 5つの要素を用意する。
         */

        mItems1.add(new Item(R.drawable.icon, "1"));
        mItems1.add(new Item(R.drawable.icon, "2"));
        mItems1.add(new Item(R.drawable.icon, "3"));
        mItems1.add(new Item(R.drawable.icon, "4"));
        mItems1.add(new Item(R.drawable.icon, "5"));

        /*
         * 上の View の定義
         */

        mAdapter1 = new MyAdapter(this, mItems1);
       
        mView1 = (DragAndDropView) findViewById(R.id.list);
        mView1.setAdapter(mAdapter1);
        mView1.setOnDragnDropListener(new DragAndDropListener() {
            @Override
            public void dropped(int from, int x, int y) {
                Rect rect = new Rect();
                mView2.getHitRect(rect);
                if (rect.contains(x, y)) {
                    Item item = mAdapter1.getItem(from);
                    mAdapter1.remove(item);
                    mAdapter2.add(item);
                }
            }
        });
       
        /*
         * 下の View の定義
         */

        mAdapter2 = new MyAdapter(this, mItems2);
       
        mView2 = (DragAndDropView) findViewById(R.id.list2);
        mView2.setAdapter(mAdapter2);
        mView2.setOnDragnDropListener(new DragAndDropListener() {
            @Override
            public void dropped(int from, int x, int y) {
                Rect rect = new Rect();
                mView1.getHitRect(rect);
                if (rect.contains(x, y)) {
                    Item item = mAdapter2.getItem(from);
                    mAdapter2.remove(item);
                    mAdapter1.add(item);
                }
            }
        });
    }

    /*
     * 要素
     */

    private class Item {
        public int icon;        // リソースID
        public String name;
        Item(int icon, String name) {
            this.icon = icon;
            this.name = name;
        }
    }
   
    /*
     * 表示用アダプタ
     */

    private class MyAdapter extends ArrayAdapter<Item> {
        private LayoutInflater mInflater;
        private ArrayList<Item> items;
       
        public MyAdapter(Context context, ArrayList<Item> items) {
            super(context, R.layout.item, items);
            mInflater = LayoutInflater.from(context);
            this.items = items;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.item, null);
                holder = new ViewHolder();
                holder.ivIcon = (ImageView) convertView.findViewById(R.id.icon);
                holder.tvName = (TextView) convertView.findViewById(R.id.name);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.ivIcon.setImageResource(items.get(position).icon);
            holder.tvName.setText(items.get(position).name);
            return convertView;
        }
    }

    class ViewHolder {
        ImageView ivIcon;
        TextView tvName;
    }
}

src/sak/samples/DragAndDropView

Drag And Drop の要のクラスです。GridView を継承したカスタムクラスを作成します。

public class DragAndDropView extends GridView {
     
    private DragAndDropListener mListener;
    private WindowManager mWm;
    private WindowManager.LayoutParams mWindowParams;
    private ImageView mDragView = null;
    private Bitmap mDragBitmap = null;
    private int mFromPosition = AdapterView.INVALID_POSITION;

    public DragAndDropView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);

        mWm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
       
        mWindowParams = new WindowManager.LayoutParams();
        mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
        mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mWindowParams.flags =
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
            WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
        mWindowParams.format = PixelFormat.TRANSLUCENT;
        mWindowParams.windowAnimations = 0;
        mWindowParams.x = 0;
        mWindowParams.y = 0;
    }

    public DragAndDropView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setOnDragnDropListener(DragAndDropListener listener) {
        mListener = listener;
    }
   
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int x = (int) event.getX();
        final int y = (int) event.getY();

        int action = event.getAction();

        if (action == MotionEvent.ACTION_DOWN) {
            mFromPosition = pointToPosition(x, y);
            if (mFromPosition == AdapterView.INVALID_POSITION)
                return false;
            startDrag();
            updateLayout(x, y);
            return true;
         
        } else if (action == MotionEvent.ACTION_MOVE) {
            updateLayout(x, y);
            return true;

        } else if (action == MotionEvent.ACTION_UP) {
            if (mListener != null) {
                mListener.dropped(mFromPosition, (int)event.getRawX(), (int)event.getRawY());   // Callback
            }
            endDrag();
            return true;
         
        } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE) {
            endDrag();
            return true;
        }

        return super.onTouchEvent(event);
    }
   
    private void updateLayout(int x, int y) {
        mWindowParams.x = getLeft() - getPaddingLeft() + x;
        mWindowParams.y = getTop() - getPaddingTop() + y;

        mWm.updateViewLayout(mDragView, mWindowParams);
    }

    private void startDrag() {
        View view = getChildByPosition(mFromPosition);
        mDragBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas();
        canvas.setBitmap(mDragBitmap);
        view.draw(canvas);

        if (mDragView != null) {
            mWm.removeView(mDragView);
        }

        mDragView = new ImageView(getContext());
        mDragView.setImageBitmap(mDragBitmap);

        mWm.addView(mDragView, mWindowParams);
    }

    private void endDrag() {
        mWm.removeView(mDragView);
        mDragView = null;
        mDragBitmap = null;
    }

    private View getChildByPosition(int position) {
        return getChildAt(position - getFirstVisiblePosition());
    }
}

src/sak/samples/DragAndDropListener

カスタムビューで発生したイベントを定義しています。

public interface DragAndDropListener {
    public void dropped(int from, int x, int y);
}


Posted by sak+

Tagged with:
preload preload preload