5月 30

そうです。懇親会だけです(笑

5月28日(土)に「第9回 Android 勉強会 in 札幌」が開催されました。残念ながら、この日は次男の運動会だったため勉強会自体には不参加だったのですが、その後開催された懇親会にだけちゃっかり参加してきました。懇親会の会場は前回の懇親会に引き続きすすきのの味処酒房なかむら(通称:かつうまのなかむら)でした。このお店のスペシャルヒレカツとカツ丼はお勧めです。

勉強会自体には sapporo6h さんによる UStream での中継があったため途中からリモートで参加させていただきました。前半の見てない部分は後で録画をゆっくり拝聴させていただきます。



Video streaming by Ustream


Posted by sak+

Tagged with:
5月 25
処理を別スレッドにしてその間はプログレスダイアログを出す。こんなこと良くあると思います。では途中で処理を止めたいときはどうすればいいんだろう?こんな時のテクニックです。

まずはサンプルアプリをご覧ください。ボタンを押すと中止ボタンのついたプログレスダイアログを表示し、中止ボタンを押すと「中止しました」のトーストメッセージを表示し、中止ボタンを押さないで処理が終了すると「終了しました」のトーストメッセージを表示します。

device-1.png device-2.png

開始ボタンを押すと中止ボタン付きのプログレスダイアログを表示すると同時にバックグラウンドで動作するスレッドを作成します。

        Button btnStart = (Button)findViewById(R.id.btnStart);
        btnStart.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                showDialog(REQ_PROGRESS_DIALOG);

                thread = new MyThread();
                thread.start();
            }
        });

作成するスレッドには脱出ポイントを用意し、定期的にスレッドの終了指示がなされていないかどうかをチェックしてあげます。中止するにはプログレスダイアログの中止ボタンを押すとこのスレッドを終了フラグを立ててあげればいいです。スレッドが終了指示で終わったかそれとも処理を終えて止まったかの区別をすることもできます。

    private class MyThread extends Thread {
        private boolean running = true;
        private boolean interupted = true;

        @Override
        public void run() {
            int counter = 10;
            try {
                while (running) {
                    Thread.sleep(500);
                    counter --;
                    if (counter == 0) {
                        stop_();
                        interupted = false;
                    }
                }

            } catch (InterruptedException e) {
            }

            handler.post(new Runnable() {
                @Override
                public void run() {
                    dialog.dismiss();
                    Toast.makeText(Main.this,
                            interupted ? "中止しました" : "終了しました", Toast.LENGTH_SHORT).show();
                }
            });
        }

        public void stop_() {
            running = false;
        }

端末ローテーション時の処理の無効化

あと、プログレスダイアログ表示中に端末の向きが変わると面倒なことになるので、向きが変わったときに走る処理をオーバライドして初期化処理が走らないようにしています。

まず、AndroidManifest.xml の Activity の指定のところで android:configChanges=”orientation” を指定します。

<activity android:name=".Main" android:configChanges="orientation">

次に Main.java で onConfigurationChanged をオーバライドします。処理の記述は必要ありません。

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

サンプルコード

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


Posted by sak+

Tagged with:
5月 21
昨日に続き ListView ネタをもうひとつ紹介します。

サーバからデータを取ってくる時などで一度にデータを持ってくるのではなく必要に応じてデータを追加したい時があります。なるべくならユーザ操作無しで。そんな時に使えるテクニックです。

サンプルアプリは47都道府県を10件ずつ表示させるアプリです。リストの最後に到達する毎に10件のデータを追加します。データはサーバではなく自前で持っているので瞬時に入手できるのですが、サーバにアクセスしている雰囲気を出すため少々タメを入れています。なお、この間 ListView にフッターを追加して処理中ということがわかるようにしています。

device-1.png device-2.png

ListView が最後の行まで到達したかどうかは ListView に setOnScrollListener を指定することで取得できます。データを追加したあとにデータの更新が完了したことを adapter の notifyDataSetChanged を使って ListView に知らせてあげます。

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

        adapter = new MyAdapter(this);

        lv = (ListView)findViewById(R.id.list);

        /*
         * Footer の追加は setAdapter の前でする。
         */

        LayoutInflater inflater = LayoutInflater.from(this);
        final View footer = inflater.inflate(R.layout.footer, null);
        lv.addFooterView(footer);

        lv.setAdapter(adapter);

        lv.setOnScrollListener(new OnScrollListener() {
            private int mark = 0;

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

                if (totalItemCount >= sItems.length) {
                    lv.removeFooterView(footer);    // 必要なくなったので消す。
                    return;
                }

                if ((totalItemCount - visibleItemCount) == firstVisibleItem && totalItemCount > mark) {
                    mark = totalItemCount;

                    new MyTask().execute();
                }
            }

            @Override
            public void onScrollStateChanged(AbsListView arg0, int arg1) {
            }
        });

        addItems();
    }

    /*
     * サーバにアクセスしている雰囲気を出すためタメを作る。
     */

    private class MyTask extends AsyncTask<Object, Integer, Long> {

        @Override
        protected synchronized Long doInBackground(Object... arg0) {
            try {
                Thread.sleep(2000);
                addItems();

            } catch (InterruptedException e) {
            }
            return 0L;
        }

        @Override
        protected void onPostExecute(Long count) {
            adapter.notifyDataSetChanged();
        }
    }

    /*
     * 要素を10個追加する。
     */

    private synchronized void addItems() {
        int start = current_position;
        int index = start;
        for (int i = start; i < start + 10; i++, index ++) {
            if (index >= sItems.length) {
                index = 0;
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        lv.removeFooterView(tv);
                    }
                });
                return;
            }
            mItems.add(sItems[index]);
        }
        current_position = index;
    }

サンプルコード

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


Posted by sak+

Tagged with:
5月 20
今回は ListView を使って複数選択する Activity を作る場合の Tips です。

ListView の要素上に CheckBox 風の画像をのせたい場合がままあります。でも、CheckBox 部品をそのままのせてしまっては上手く動きません。これでは CheckBox 部分のタッチにのみ反応し、それ以外の部分を押しても選択できなくなってしまいます。

ListView の要素の選択状態は isItemChecked メソッドで取得できることができます。これを使いましょう。このメソッドで取得した選択状態を使って On/Off の画像を切替えたり、On 画像の表示/非表示を切替えるのです。要素自体の背景色を変えてしまう事もできます。

device-1.png device-2.png

左図は On/Off 画像の切替、右図は On 画像の表示/非表示の切替の例です。ともに選択された要素の背景色をも変更させています。OKボタンを押すと選択された要素がトースト表示されます。

要素のレイアウト(res/layout/item.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="horizontal"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   >
    <TextView
       android:id="@+id/name"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:layout_gravity="center_vertical"
       android:paddingLeft="15dp"
       android:paddingTop="10dp"
       android:paddingBottom="10dp"
       android:text="title"
       android:textSize="28sp"
       />

    <!-- チェックマーク -->
    <TextView
       android:id="@+id/check"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="center"
       android:layout_marginRight="15dp"
       android:background="@drawable/btn_check_buttonless_on"
       />
</LinearLayout>

キモとなるソースコード

ListView で指定している Adapter 内の getView メソッドで画像の指定や表示/非表示の切替を行います。

        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.tvName = (TextView) convertView.findViewById(R.id.name);
                holder.tvCheck = (TextView) convertView.findViewById(R.id.check);
                convertView.setTag(holder);

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

            holder.tvName.setText(mItems[position]);

            boolean checked = lv.isItemChecked(position);

            /*
             * チェックボックス画像
             */

            boolean buttonless = true;
            if (buttonless) {
                holder.tvCheck.setBackgroundResource(R.drawable.btn_check_buttonless_on);
                holder.tvCheck.setVisibility(checked ? View.VISIBLE : View.GONE);
            } else {
                holder.tvCheck.setBackgroundResource(
                        checked ? R.drawable.btn_check_on : R.drawable.btn_check_off);
            }

            /*
             * 背景色
             */

            convertView.setBackgroundColor(checked ? background_selected : background_normal);

            return convertView;
        }

サンプルコード

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


Posted by sak+

Tagged with:
5月 15

以前、カスタムタブについてのエントリ『カスタムタブと selector』を書きましたが、カスタムタブを使うとタブのプレス状態やフォーカス状態で背景イメージが変わらないという指摘を頂きました。試してみようと思いましたが、このエントリのサンプルアプリではプレス状態やフォーカス状態の背景を設定していませんでしたので新たにサンプルアプリを作成しました。

device-1.png device-2.png

左図は「さん」のタブがフォーカス状態です。右図は「さん」のタブがプレス状態です。

drawable/tab_focus.xml, drawable/tab_press.xml を追加し、念のため drawable/tab.xml は Android のソースのタブの部分の内容にそっくり入れ替えました。また、Main.java の内容を一部修正しました。

    private class MyView extends FrameLayout {
        private LayoutInflater inflater;
        private View indicator;
        private TabHost tabHost;

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

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

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

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

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

            indicator.setFocusable(true);
            indicator.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (v == v1.getIndicator()) {
                        tabHost.setCurrentTab(0);
                    } else if (v == v2.getIndicator()) {
                        tabHost.setCurrentTab(1);
                    } else if (v == v3.getIndicator()) {
                        tabHost.setCurrentTab(2);
                    }
                 }
            });

            addView(indicator);
        }

        public View getIndicator() {
            return indicator;
        }
    }

実は、当初 setFocusable(), setOnClickListener() の部分が無かったのですが、これがないとプレス状態とフォーカス状態を認識してくれません。次に setOnClickListener() を代わりに setClickable() を使ってみるとプレス状態とフォーカス状態を認識してくれるものの今度は画面の切替ができなくなってしまいました。試行錯誤の末、このようなソースコードに落ち着いています。

あとがき

フォーカス状態を作るのに久しぶりに HT-03A を引っ張りだしました。当初、トラックボールが普通についていましたが、最近の端末には付かなくなっちゃいしたね。トラックボール好きなんだけどなー。

サンプルコード

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


Posted by sak+

Tagged with:
5月 01
今回は Honeycomb (Android 3.0) で追加された AppWidget の新機能を試してみました。

Honeycomb からはこれまで使えなかった ListView, GridView に対応の他、新しい Widget の StackView も使えるようになりました。StackView が複数の View を暦をめくるように切替えることができる新しい Widget です。この StackView を使った AppWidget はプリインアプリでは Android Market や YouTube でも使われています。

StackView0.png
(Android Market の AppWidget)

StackView1.png
(YouTube の AppWidget)

サンプルアプリ

StackView を使った Widget のサンプルコードは、Androide Developers のサイト(http://developer.android.com/resources/samples/StackWidget/index.html)にはあるのですが、何故か SDK のサンプルには付属していません。そこでこのサイトからソースコードを持って来て自分なりに理解できるようにソースを整形して試してみました。

StackView2.png

ソースを見る限りこれまでの Widget の作り方と大きくは変わっていないようですね。ただ、元々 AppWidget の作り方は独特のクセがあり複数の xml ファイルを上手に連携させなければならないので結構面倒なんですよね。

でも、この新しい Widget を使った新アプリを何か作ってみたいですね。

サンプルコード

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


Posted by sak+

Tagged with:
preload preload preload