8月 20
前回の記事ではレイアウトファイルを使用して Fragment の生成を行いました。レイアウトが固定の場合はそれで良いのですが、レイアウトを動的に変更したい、あるいはボタンを押すなどの操作を行いその結果としてレイアウトの一部を変更させたい場合などは今回取り上げる方法が有効です。

その方法とは、予め Fragment を挿入する領域を確保し(サンプルでは LinearLayout)そこに Fragment を挿入する方法です。

今回のサンプルの画面は前回とほぼ同じです。

device-1.png

まず、レイアウトファイルでは Fragment を挿入する枠組みを用意します。

(res/layout/main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="horizontal"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   >
    <LinearLayout
       android:id="@+id/fragment1"
       android:layout_height="match_parent"
       android:layout_width="0dp"
       android:layout_weight="1"
       />
    <LinearLayout
       android:orientation="vertical"
       android:layout_height="match_parent"
       android:layout_width="0dp"
       android:layout_weight="2"
       >
        <LinearLayout
           android:id="@+id/fragment2"
           android:orientation="horizontal"
           android:layout_height="0dp"
           android:layout_width="match_parent"
           android:layout_weight="1"
           />
        <LinearLayout
           android:id="@+id/fragment3"
           android:orientation="horizontal"
           android:layout_height="0dp"
           android:layout_width="match_parent"
           android:layout_weight="2"
           />
    </LinearLayout>
</LinearLayout>

プログラムで Fragment を生成し LinearLayout で定義してあった領域に挿入します。(replace)

(Main.java)

public class Main extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        MyFragment1 df1 = MyFragment1.newInstance();
        MyFragment2 df2 = MyFragment2.newInstance();
        MyFragment3 df3 = MyFragment3.newInstance();

        FragmentTransaction ft = getFragmentManager().beginTransaction();
       
        ft.replace(R.id.fragment1, df1);
        ft.replace(R.id.fragment2, df2);
        ft.replace(R.id.fragment3, df3);
       
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.commit();
    }
}

最後に commit すると反映されます。

この仕組みを使うと画面遷移にいろいろな効果を与える事ができそうです。その件についてはまたの機会にでも。

サンプルコード

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


Posted by sak+

Tagged with:
8月 15
以前、『レイアウトを分割しよう!』という記事を書きましたがそれと同じことが fragment を使うと簡単に実現できちゃいます。Fragment は Honeycomb(Android 3.0)から導入された機能ですが、Backport を使えば、それ以前のバージョンの Android でも使用できちゃいます。Google の中の人も積極的に使ってくださいと言っていますので、これを使わない手はないですね。

Fragment を使ったサンプルはまだ意外と少ないのでなるべき最小構成でのサンプルを目指します。といってもそれだけでは寂しいのでActionButton を付けてみました。ActionButton はメニューに変わる新しい機能でよく使うボタンはメニューを開くことなく押せるようになっています。この ActionButton は Fragment をホストする Activity に付与してもいいし、Activity の中に配置する Fragment に付与してもいいです。

では、今回のサンプルアプリの画面はこちら。

device-1.png

赤(Fragment1)、緑(Fragment2)、青(Fragment3)の部分は Fragment でそれぞれ対応するレイアウトファイルを持っています。

(Main.java)

public class Main extends Activity {

    private static final int MENU_FRAGMENT_0_0 = Menu.FIRST + 0;
    private static final int MENU_FRAGMENT_0_1 = Menu.FIRST + 1;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
   
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, MENU_FRAGMENT_0_0, 0, "Main_0")
            .setIcon(android.R.drawable.ic_menu_help)
            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        menu.add(0, MENU_FRAGMENT_0_1, 0, "Main_1")
            .setIcon(android.R.drawable.ic_menu_compass)
            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        return true;
    }
   
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        boolean result = true;
        switch (item.getItemId()){
        case MENU_FRAGMENT_0_0:
            Toast.makeText(this, "MENU_FRAGMENT_0_0 が選択された。", Toast.LENGTH_LONG).show();
            break;
        case MENU_FRAGMENT_0_1:
            Toast.makeText(this, "MENU_FRAGMENT_0_1 が選択された。", Toast.LENGTH_LONG).show();
            break;
        default:
            result = super.onOptionsItemSelected(item);
            break;
        }
        return result;
    }
}

(Fragment1.java)

public class MyFragment1 extends Fragment {

    private static final int MENU_FRAGMENT_1_0 = Menu.FIRST + 100;
    private static final int MENU_FRAGMENT_1_1 = Menu.FIRST + 101;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        this.setHasOptionsMenu(true);
    }
   
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment1, container, false);
        return v;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, android.view.MenuInflater inflater) {
        menu.add(0, MENU_FRAGMENT_1_0, 0, "Fragment1_0")
            .setIcon(android.R.drawable.ic_menu_call)
            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
        menu.add(0, MENU_FRAGMENT_1_1, 0, "Fragment1_1")
            .setIcon(android.R.drawable.ic_menu_gallery)
            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
    }
   
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        boolean result = true;
        switch (item.getItemId()){
        case MENU_FRAGMENT_1_0:
            Toast.makeText(getActivity(), "MENU_FRAGMENT_1_0 が選択された。", Toast.LENGTH_LONG).show();
            break;
        case MENU_FRAGMENT_1_1:
            Toast.makeText(getActivity(), "MENU_FRAGMENT_1_1 が選択された。", Toast.LENGTH_LONG).show();
            break;
        default:
            result = super.onOptionsItemSelected(item);
            break;
        }
        return result;
    }
}

(res/layout/main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="horizontal"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   >
    <fragment class="sak.samples.hellofragment.MyFragment1"
       android:id="@+id/fragment1"
       android:layout_height="match_parent"
       android:layout_width="0dp"
       android:layout_weight="1"
       />
    <LinearLayout
       android:orientation="vertical"
       android:layout_height="match_parent"
        android:layout_width="0dp"
       android:layout_weight="2"
        >
        <fragment class="sak.samples.hellofragment.MyFragment2"
           android:id="@+id/fragment2"
           android:orientation="horizontal"
           android:layout_height="0dp"
           android:layout_width="match_parent"
           android:layout_weight="1"
           />
        <fragment class="sak.samples.hellofragment.MyFragment3"
           android:id="@+id/fragment3"
           android:orientation="horizontal"
           android:layout_height="0dp"
           android:layout_width="match_parent"
           android:layout_weight="2"
           />
    </LinearLayout>
</LinearLayout>

(res/layout/fragment1.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:background="#ff00"
   >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Fragment 1"
        android:textColor="#ffff"
        android:textSize="64sp"
       />        
</RelativeLayout>

サンプルコード

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


Posted by sak+

Tagged with:
7月 18
インターネットアクセスには時間がかかる場合が多いので、画面遷移中にプログレスダイアログを表示させたい場合があります。今日はそんな時に使用できる Tips です。

ただ WebView の画面遷移中にプログレスダイアログを出すだけでは面白くないのでダイアログにはカスタムビューを使用します。また、進捗を分かり易く表現するためにアニメーションを使用してみましょう。

サンプルアプリ

今日のサンプルアプリではページの切替時に以下のような進捗を示すダイアログを表示させます。ちょっと見づらいですが画面中央に透過の背景を持つダイアログが表示されています。中央のインディケータはアニメーションしています。

device-1.png

ダイアログの表示/非表示のタイミング

ダイアログを表示/非表示させるタイミングには WebView の持つ WebViewClient の onPageStarted, onPageFinished を使います。標準の振る舞いを変えるために WebViewClient を継承したクラスを作成したうえで onPageStarted, onPageFinished をオーバライドします。(onPageStarted でダイアログの生成と表示、onPageFinished でダイアログの非表示と破棄)ここで作成したクラスを WebView の setWebViewClient に設定します。

        WebView wv = (WebView)findViewById(R.id.webview);
        wv.setWebViewClient(new MyWebViewClient());

    (略)

    private class MyWebViewClient extends WebViewClient {
        private AnimationDrawable animation;
       
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            Log.d("debug", "onPageStarted: " + url);
            if (dialog != null) {
                dialog.dismiss();
            }
            dialog = new Dialog(Main.this, R.style.MyProgressTheme);
            dialog.setContentView(R.layout.my_progress_dialog);
            dialog.show();

            // アニメーションの動作
            ImageView iv = (ImageView)dialog.findViewById(R.id.image);
            iv.setBackgroundResource(R.drawable.ic_spinner);
            animation = (AnimationDrawable)iv.getBackground();
            startAnimation();
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            Log.d("debug", "onPageFinished: " + url);
            stopAnimation();
            dialog.dismiss();
            dialog = null;
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            Log.d("debug", "onReceivedError: " + failingUrl + " (code: " + errorCode + ")");
            stopAnimation();
            dialog.dismiss();
            dialog = null;
        }
       
    (略)
    }

Main.java

カスタムダイアログ

ダイアログのカスタマイズは、ダイアログ生成時にテーマを指定することで簡単に行えます。(手を加えるファイルが多いので一見複雑ですが慣れれば簡単です。)

            dialog = new Dialog(Main.this, R.style.MyProgressTheme);
            dialog.setContentView(R.layout.my_progress_dialog);

(layout/my_progress_dialog.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   >
    <ImageView
       android:id="@+id/image"
       android:layout_width="90dp"
       android:layout_height="90dp"
       android:background="@drawable/ic_spinner"
       android:layout_centerInParent="true"
       />
</RelativeLayout>

(values/themes.xml)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="MyProgressTheme" parent="android:style/Theme.Dialog">
        <item name="android:windowBackground">@drawable/my_progress_frame</item>
        <item name="android:windowNoTitle">true</item>
    </style>
</resources>

(drawable/my_progress_frame.xml)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#80808080"/>
    <corners android:radius="20dp"/>
    <padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />
    <stroke android:width="0dp" android:color="#a0808080" />
</shape>

アニメーション

イメージをアニメーションさせるには AnimationDrawable を使用します。アニメーションは何もしなければ始まりません。onPageStarted でダイアログを表示させた後でアニメーションを開始させます。終了時には onPageFinished, onReceivedError でアニメーションを停止させます。

(drawable/ic_spinner.xml)

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
   android:oneshot="false" >
    <item android:drawable="@drawable/ic_spinner1" android:duration="60" />
    <item android:drawable="@drawable/ic_spinner2" android:duration="60" />
    <item android:drawable="@drawable/ic_spinner3" android:duration="60" />
    <item android:drawable="@drawable/ic_spinner4" android:duration="60" />
    <item android:drawable="@drawable/ic_spinner5" android:duration="60" />
    <item android:drawable="@drawable/ic_spinner6" android:duration="60" />
    <item android:drawable="@drawable/ic_spinner7" android:duration="60" />
    <item android:drawable="@drawable/ic_spinner8" android:duration="60" />
</animation-list>

(Main.java)

            // アニメーションの動作
            ImageView iv = (ImageView)dialog.findViewById(R.id.image);
            iv.setBackgroundResource(R.drawable.ic_spinner);
            animation = (AnimationDrawable)iv.getBackground();
            startAnimation();

      (略)

        private void startAnimation() {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    animation.start();
                }
            });
        }

サンプルコード

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


Posted by sak+

Tagged with:
7月 06
今回のテーマは Scroller を使用したカスタムビューの作成です。Scroller は座標を管理するオブジェクトで初期位置とターゲットの座標、あとそれに要する時間を指定してあげることでその時点での座標を計算し取得できます。オブジェクトの生成時に Interpolator を指定できるので移動量を任意に調整することもできます。

また View には computeScroll というメソッド持ち、その表示位置を変更させることができます。カスタムビューの中で computeScroll をオーバーライドしてあげることでカスタムビューにアニメーションを持たせる事ができるようになります。Scroller はここで使います。こんな感じで。

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

SlideView.java

サンプルアプリ

では今回のサンプルアプリです。画面の中に3つのボタンがあります。3つのボタンを押すと選択されたビュー(カスタムビュー)がスライドして現れます。(View1 は左から、View2 は右から、View3 は下から)既に表示されたボタンを押すとスライドで閉じます。なお、ビューは同時には一枚しか開けないように制御しており、既に画面上に他のカスタムビューが表示されている場合はそのビューはスライドで自動的に閉じます。

device-1.png

device-2.png

device-3.png

作成したカスタムビューはパラメータの指定で諸条件を指定できるようにしています。

    <RelativeLayout
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       >
        <sak.samples.slideview.MyView1
           android:id="@+id/myview1"
           android:layout_width="160dp"
           android:layout_height="120dp"
           android:layout_alignParentLeft="true"
           android:layout_marginTop="10dp"
           sak:distance="160dp"
           sak:duration="1000"
           sak:location="left"
           sak:init_opened="false"
           />

        <sak.samples.slideview.MyView2
           android:id="@+id/myview2"
           android:layout_width="180dp"
           android:layout_height="180dp"
           android:layout_alignParentRight="true"
           android:layout_marginTop="140dp"
           sak:distance="180dp"
           sak:duration="1000"
           sak:location="right"
           sak:init_opened="true"
           />

        <sak.samples.slideview.MyView3
           android:id="@+id/myview3"
           android:layout_width="140dp"
           android:layout_height="320dp"
           android:layout_alignParentBottom="true"
           android:layout_marginLeft="50dp"
           sak:distance="320dp"
           sak:duration="500"
           sak:location="bottom"
           sak:init_opened="false"
           />
    </RelativeLayout>

main.xml

実際に動かしている映像はこちらです。

サンプルコード

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


Posted by sak+

Tagged with:
6月 28
まずはお詫びから、6/5のエントリで「カスタムダイアログと遷移アニメーション」のソースコードが GoogleCode のレポジトリに登録されていませんでした。なぜかローカルの svn のレポジトリに登録されていました。orz 本日、登録しましたのでよろしくー。

で、今日のお題は「レイアウトを分割しよう!」です。簡単な場合やひとりで開発する場合はさておき複雑な画面(Activity)やひとつの画面を複数人で開発しなければならない場合、レイアウトファイルが大きくなりすぎて見通しが悪くなったり他の人の修正とコンフリクトを起こしひどいめにあったりする場合がよくあると思います。今回はそんなときに使える Tips です。

今日のサンプルアプリ

device-1.png

今日のサンプルアプリの画面はこんな感じです。リセットボタンが1つに数字が3つ。数字はそれぞれ別個のTextViewです。青のビューの数字が1の位、緑のビューの数字が十の位、赤のビューの数字が百の位を表しています。アプリを起動するとカウントアップが開始され999を超えると000に戻ります。またリセットボタンを押すことで000に戻すこともできます。普通に作ればなんということのないアプリです。今回はこれをカスタムビューと複数のレイアウトを使って作りました。

blog-1.png

aaa.xml, bbb.xml, ccc.xml に対応するカスタムビューを Aaa, Bbb, Ccc をそれぞれ用意します。カスタムビューは FrameLayout または LineraLayout のサブクラスにするといいでしょう。ここで示す例では FrameLayout のサブクラスとしています。xml ファイルから読み込まれるコンストラクタで対応するレイアウトファイルを inflate し、それを addView します。

public class Aaa extends FrameLayout {

    public Aaa(Context context, AttributeSet attrs) {
        super(context, attrs);

        LayoutParams params =
            new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
        View child = inflate(context, R.layout.aaa, null);
        addView(child, params);

aaa.xml
bbb.xml
ccc.xml

Aaa
Bbb
Ccc

こうすることでレイアウトファイル Aaa.xml 上のウィジェットの制御は全てカスタムビュー Aaa に集約させることができます。

でもレイアウトファイルをまたぐような連携はどうするの?もちろんレイアウトを分割したとはいえ同じ Activity 上のウィジェットです。普通に findViewById を利用すれば相互にアクセス可能です。でもこれじゃーおもしろくありませんし、なによりモジュールはできるだけ疎に保つべきという定石に反します。こういうときはカスタムモジュールに外部から操作できるメソッドと通知を受け取るリスナーを用意してあげましょう。

DigitListener

blog-2.png

リスナーで受け取ったコールバックの中で UI を操作するとスレッドの例外が発生するので必ず Handler を用いて UI スレッドに処理を post してあげるのを忘れずに。

サンプルコード

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


Posted by sak+

Tagged with:
6月 05
今日はカスタムダイアログ作成の話しです。標準のダイアログではなく iPhone のような洒落たダイアログを作成するにはどうしたら良いでしょう。そのためには、レイアウトのカスタマイズと画面遷移の際のアニメーションの設定が必要です。この2つをすることで Android でもこんなカスタムダイアログを作成できます。(サンプルの画面はそれほど洒落た画面ではありませんね。 (^^; でも頑張ればちゃんとイカした画面が作成できます!)

device-1.png device-2.png

左のダイアログは中央に表示するカスタムダイアログです。レイアウトはレイアウトファイルで自由に指定できます。右は iPhone のアクションシートのようなカスタムダイアログです。ダイアログ画面が下からせり上がります。

このような画面遷移のアニメーションはアニメーションファイル(res/anim/*.xml)で作成します。これをスタイルファイル(res/value/styles.xml)とテーマファイル(res/value/themas.xml)の設定で作成したカスタムダイアログに関連付けるのです。

今回は Android のソースに含まれている8種類の画面遷移アニメーションを利用しサンプルアプリを作成しました。ダイアログのレイアウトは3種類です。画像とソースだけではアニメーションの動きを伝えるのは難しいので是非サンプルアプリを入手しお試しください。アニメーションの Alpha, translate, scale 等を組み合わせて様々な画面遷移を実現できることを体感していただけると思います。

device-3.png

public class Main extends Activity {

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

        Button b1 = (Button)findViewById(R.id.button1);
        b1.setOnClickListener(new OnMyClickListener(R.layout.dialog1, R.style.AnimDialog));

        Button b2 = (Button)findViewById(R.id.button2);
        b2.setOnClickListener(new OnMyClickListener(R.layout.dialog1, R.style.AnimToast));

        Button b3 = (Button)findViewById(R.id.button3);
        b3.setOnClickListener(new OnMyClickListener(R.layout.dialog1, R.style.AnimWallPaper));

        Button b4 = (Button)findViewById(R.id.button4);
        b4.setOnClickListener(new OnMyClickListener(R.layout.dialog1, R.style.AnimSubmenu));

        Button b5 = (Button)findViewById(R.id.button5);
        b5.setOnClickListener(new OnMyClickListener(R.layout.dialog1, R.style.AnimInputMethod));

        Button b6 = (Button)findViewById(R.id.button6);
        b6.setOnClickListener(new OnMyClickListener(R.layout.dialog1, R.style.AnimInputMethodFancy));

        Button b7 = (Button)findViewById(R.id.button7);
        b7.setOnClickListener(new OnMyClickListener(R.layout.dialog2, R.style.AnimOptionsPanel));

        Button b8 = (Button)findViewById(R.id.button8);
        b8.setOnClickListener(new OnMyClickListener(R.layout.dialog3, R.style.AnimStatusBar));
    }

    private class OnMyClickListener implements OnClickListener {
        private int layout;
        private int style;

        public OnMyClickListener(int layout, int style) {
            this.layout = layout;
            this.style = style;
        }

        @Override
        public void onClick(View arg0) {
            final Dialog dialog = new Dialog(Main.this, style);
            dialog.setContentView(layout);

            // OK ボタン
            Button btnOk = (Button)dialog.findViewById(R.id.ok);
            btnOk.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    dialog.dismiss();
                }
            });

            // Cancel ボタン
            Button btnCancel = (Button)dialog.findViewById(R.id.cancel);
            btnCancel.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    dialog.cancel();
                }
            });

            dialog.show();
        }
    }
}

res/value/themes.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
   <style name="Theme.Dialog">
       <item name="android:windowFrame">@null</item>
       <item name="android:windowTitleStyle">@android:style/DialogWindowTitle</item>
       <item name="android:windowBackground">@android:drawable/panel_background</item>
       <item name="android:windowIsFloating">true</item>
       <item name="android:windowContentOverlay">@null</item>
       <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
       <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
   </style>
-->
    <style name="MyDialog" parent="android:style/Theme.Dialog">
        <item name="android:windowNoTitle">true</item>
    </style>

    <style name="AnimSubmenu" parent="MyDialog">
        <item name="android:windowBackground">@drawable/frame</item>
        <item name="android:windowAnimationStyle">@style/AnimSubmenu</item>
    </style>
    <style name="AnimToast" parent="MyDialog">
        <item name="android:windowBackground">@drawable/frame</item>
        <item name="android:windowAnimationStyle">@style/AnimToast</item>
    </style>
    <style name="AnimWallPaper" parent="MyDialog">
        <item name="android:windowBackground">@drawable/frame</item>
        <item name="android:windowAnimationStyle">@style/AnimWallPaper</item>
    </style>
    <style name="AnimDialog" parent="MyDialog">
        <item name="android:windowBackground">@drawable/frame</item>
        <item name="android:windowAnimationStyle">@style/AnimDialog</item>
    </style>
    <style name="AnimInputMethod" parent="MyDialog">
        <item name="android:windowBackground">@drawable/frame</item>
        <item name="android:windowAnimationStyle">@style/AnimInputMethod</item>
    </style>
    <style name="AnimInputMethodFancy" parent="MyDialog">
        <item name="android:windowBackground">@drawable/frame</item>
        <item name="android:windowAnimationStyle">@style/AnimInputMethodFancy</item>
    </style>
    <style name="AnimOptionsPanel" parent="MyDialog">
        <item name="android:windowBackground">@drawable/frame_0</item>
        <item name="android:windowAnimationStyle">@style/AnimOptionsPanel</item>
    </style>
    <style name="AnimStatusBar" parent="MyDialog">
        <item name="android:windowBackground">@drawable/frame_0</item>
        <item name="android:windowAnimationStyle">@style/AnimStatusBar</item>
    </style>
</resources>

res/value/styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="AnimStatusBar" parent="android:Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/status_bar_enter</item>
        <item name="android:windowExitAnimation">@anim/status_bar_exit</item>
    </style>
    <style name="AnimOptionsPanel" parent="android:Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/options_panel_enter</item>
        <item name="android:windowExitAnimation">@anim/options_panel_exit</item>
    </style>
    <style name="AnimSubmenu" parent="android:Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/submenu_enter</item>
        <item name="android:windowExitAnimation">@anim/submenu_exit</item>
    </style>
    <style name="AnimToast" parent="android:Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/toast_enter</item>
        <item name="android:windowExitAnimation">@anim/toast_exit</item>
    </style>
    <style name="AnimWallPaper" parent="android:Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/wallpaper_enter</item>
        <item name="android:windowExitAnimation">@anim/wallpaper_exit</item>
    </style>
    <style name="AnimDialog" parent="android:Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/dialog_enter</item>
        <item name="android:windowExitAnimation">@anim/dialog_exit</item>
    </style>
    <style name="AnimInputMethod" parent="android:Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/input_method_enter</item>
        <item name="android:windowExitAnimation">@anim/input_method_exit</item>
    </style>
    <style name="AnimInputMethodFancy" parent="android:Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/input_method_fancy_enter</item>
        <item name="android:windowExitAnimation">@anim/input_method_fancy_exit</item>
    </style>
</resources>

サンプルコード

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


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:
preload preload preload