8月 08

おはようございます、Selphyです。
前回のエントリーでは、テクスチャーを使った簡単なサンプルを説明しました。

最後のサンプルでは、カルーセルの基本形を作ります。

RS内で使用している型やメソッドなどについては、RenderScriptのReference に記載されているので、そちらをご覧ください。

完成形のスクリーンショット

完成したアプリのスクリーンショット

ファイル構成

今までは RSHelloWorld を使っていましたが、新しいプロジェクトを作成します。

完成したアプリのスクリーンショット

コード

MainActivity.javaMainTextureView.javamain.xmlは前回のプロジェクトと大きく変わっていません。
プロジェクトが変わったことによるパッケージ名の修正程度なので省きます。

  • SampleScript.java
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    protected SampleScript(RenderScriptGL rs, Resources res) {
        script = new ScriptC_SampleScript(rs, res, R.raw.samplescript);
       
        // 構造体の初期化
        //  構造体内部にrs_allocationがあるので、そこにテクスチャーを設定する。
        ScriptField_RS_Droid droids = new ScriptField_RS_Droid(rs, MODEL_COUNT, Allocation.USAGE_GRAPHICS_TEXTURE);
        for (int i=0; i<MODEL_COUNT; i++) {
            ScriptField_RS_Droid.Item droid = new ScriptField_RS_Droid.Item();
            droid.texture = Allocation.createFromBitmap(rs, BitmapFactory.decodeResource(res, textureIds[i]));
            droids.set(droid, i, true);
        }
        script.bind_gDroids(droids);
       
        // ProgramStore
        //  Alpha値を有効にする。
        progStoreBlendAlpha = ProgramStore.BLEND_ALPHA_DEPTH_TEST(rs);
        script.set_gProgStoreBlendAlpha(progStoreBlendAlpha);
       
        // テクスチャー用のFragment
        //  Samplerを設定してテクスチャーのジャギーを目立たなくする。
        ProgramFragmentFixedFunction.Builder texBuilder = new ProgramFragmentFixedFunction.Builder(rs);
        texBuilder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, ProgramFragmentFixedFunction.Builder.Format.RGBA, 0);
        progFragmentTexture = texBuilder.create();
        progFragmentTexture.bindSampler(Sampler.CLAMP_LINEAR(rs), 0);
        script.set_gProgFragmentTexture(progFragmentTexture);
       
        // Raster
        //  テクスチャーの表裏を描画する。
        script.set_gCullNone(ProgramRaster.CULL_NONE(rs));
       
        rs.bindRootScript(script);
    }

    Step3で構造体を扱ったので、ここでは復習のためにテクスチャーを構造体で用意しました。
    コード内にコメントもあるので、特に説明はありません。
    また、実際には、タッチイベントを RS に通知するためのメソッドもありますが、いままでのサンプルコードと同じ部分なので省いています。

  • SampleScript.rs
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    int32_t root() {
        rsgClearColor(0.0f, 0.0f, 0.0f, 1.0f);      // RGBA
        rsgClearDepth(1.0f);
       
        // プロジェクション設定
        rs_matrix4x4 proj;
        rsMatrixLoadPerspective(&proj, 30.0f, (float) rsgGetWidth() / (float) rsgGetHeight(), 0.01f, 1000.0f);
        rsgProgramVertexLoadProjectionMatrix(&proj);
       
        rsgBindProgramStore(gProgStoreBlendAlpha);
        rsgBindProgramFragment(gProgFragmentTexture);
       
        // モデル座標の回転を計算
        if (gSwipePoint.x != 0) {
            gRotX += gSwipePoint.x * 0.5f;
            gSwipePoint.xy = 0;
            if (gRotX > 360 || gRotX < -360)
                gRotX %= 360;
        }
       
        // モデル座標
        rs_matrix4x4 matrix;
        rsMatrixLoadIdentity(&matrix);
        rsMatrixLoadTranslate(&matrix, 0, 0, -500);
        rsMatrixRotate(&matrix, 30, 1, 0, 0);
        rsMatrixRotate(&matrix, gRotX, 0, 1, 0);
        rsgProgramVertexLoadModelMatrix(&matrix);
       
        float x, y, z, minX, minY, maxX, maxY;
       
        rsgBindProgramRaster(gCullNone);
       
        // テクスチャー描画
        for (int32_t i=0; i<MODEL_COUNT; i++) {
            RS_Droid_t droid = gDroids[i];
            float rad = VALUE_DOUBLE_PI * (float) i / (float) MODEL_COUNT;
            x = 50 * sin(rad);
            y = 0;
            z = 50 * cos(rad);
           
            minX = z * sin(rad - VALUE_DOUBLE_PI) + x * cos(rad - VALUE_DOUBLE_PI) - 25;
            maxX = minX + 50;
            minY = y - 25;
            maxY = minY + 50;
            z = z * cos(rad - VALUE_DOUBLE_PI) - x * sin(rad - VALUE_DOUBLE_PI);
           
            rsgBindTexture(gProgFragmentTexture, 0, droid.texture);
            rsgDrawQuadTexCoords(minX, maxY, z, 0.f, 0.f,
                                 minX, minY, z, 0.f, 1.f,
                                 maxX, minY, z, 1.f, 1.f,
                                 maxX, maxY, z, 1.f, 0.f);  
        }
       
        // 20fps
        return 50;
    }

     プロジェクションでは、3Dモデルが描画される際のパースペクティブに関わる値を設定しています。
    どういうことかというと、3Dモデルをカメラで撮影して、その映像が描画されているとすると、カメラの視野角、アスペクト比、どこまで遠くまで撮影できるかという設定です。
     モデル座標については、 rsgProgramVertexLoadModelMatrix というメソッド名だったのでモデル座標ということにしましたが、このサンプルでは、ワールド座標として扱っています。
    これが何かというと、3Dモデルが描画される空間の座標系だと思ってください。
     最後にテクスチャーを描画するための座標が出てきますが、今回のサンプルでは、個々のテクスチャーをモデルとして扱っているので、テクスチャーの描画座標がモデル座標という感覚で扱っています。
     このあたりの知識は3D描画の基本なので、詳細な説明は省きます。

サンプルでは、モデルが常に同一方向を向き、x軸方向のスワイプにのみ動くようにしました。
2自由度でモデルを動かしたい場合には、クォータニオンによる計算をおすすめします。
常に正面を向かせたい場合、テクスチャーの描画座標を三角関数で計算してください。
また、モデルの動きに慣性を与えるとUXが向上するので試してみてください。

サンプルは Google Code で公開しています。

このエントリーの内容について
クリエイティブ・コモンズ・ライセンス
RSHelloWorld by Selphy, Kiroru Inc. is licensed under a Creative Commons 表示 – 非営利 – 継承 3.0 非移植 License.


Posted by selphy

Tagged with:
7月 31

おはようございます、Selphyです。
前回のエントリーでは、 構造体 を使った簡単なサンプルを説明しました。

今回は、テクスチャーを使ってみます。

RS内で使用している型やメソッドなどについては、RenderScriptのReference に記載されているので、そちらをご覧ください。

完成形のスクリーンショット

完成したアプリのスクリーンショット

ファイル構成

プロジェクトは、前回のものを使うので、特に変更はありません。

コード

改造したファイルのみ載せます。

  • HelloWorldScript.java
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    protected HelloWorldScript(RenderScriptGL rs, final Context c, Resources res) {
        script = new ScriptC_HelloWorldScript(rs, res, R.raw.helloworldscript);
       
        texture = Allocation.createFromBitmapResource(rs, res, R.drawable.droid_yellow);
        script.set_texture(texture);

        progStoreBlendAlpha = ProgramStore.BLEND_ALPHA_DEPTH_TEST(rs);
        script.set_gProgStoreBlendAlpha(progStoreBlendAlpha);

        ProgramFragmentFixedFunction.Builder texBuilder = new ProgramFragmentFixedFunction.Builder(rs);
        texBuilder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, ProgramFragmentFixedFunction.Builder.Format.RGBA, 0);
        progFragmentTexture = texBuilder.create();
        progFragmentTexture.bindSampler(Sampler.CLAMP_LINEAR(rs), 0);
        script.set_gProgFragmentTexture(progFragmentTexture);

        cullNone = ProgramRaster.CULL_NONE(rs);
        script.set_gCullNone(cullNone);
       
        rs.bindRootScript(script);
    }

    テクスチャーは、文字列と同様に、Allocationでメモリの確保を行います。
    そのため、テクスチャー自体のメモリ確保は簡単です。
    しかし、テクスチャーを表示するだけで、ProgramStoreや、ProgramFragmentFixedFunctionなどが必要となってくるため、文字列の描画に比べて難しそうな印象を受けるかもしれません。
    やっていることは、2DイメージのAlpha値を有効にしたり (7,8行目) 、ジャギーを軽減したり (13行目) 、カリング処理を施したり (16,17行目) といった作業なので、実際には、必要不可欠な行ではなく、テクスチャーの描画クォリティを上げるための行が多いです。

  • HelloWorldScript.rs
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    int32_t root() {
        rsgClearColor(1.0f, 1.0f, 1.0f, 1.0f);
       
        rsgBindProgramStore(gProgStoreBlendAlpha);
        rsgBindProgramFragment(gProgFragmentTexture);
        rsgBindProgramRaster(gCullNone);
       
        float minX = gTouch.x;
        float maxX = minX + 100;
        float minY = gTouch.y;
        float maxY = minY + 100;
       
        rsgBindTexture(gProgFragmentTexture, 0, texture);
        rsgDrawQuadTexCoords(minX, minY, 0, 0.f, 0.f,
                             minX, maxY, 0, 0.f, 1.f,
                             maxX, maxY, 0, 1.f, 1.f,
                             maxX, minY, 0, 1.f, 0.f);  
       
        // 20fps
        return 50;
    }

    14行目でバインドしたテクスチャーデータを15行目で描画しているだけです。

RSで自動生成されるもの

今回、新たに自動生成されたクラスはありません。

今回のサンプルは Google Code で公開しています。
次回は、この連載の最初にスクリーンショットで紹介したカルーセルの基本となるサンプルを説明します。

このエントリーの内容について
クリエイティブ・コモンズ・ライセンス
RSHelloWorld by Selphy, Kiroru Inc. is licensed under a Creative Commons 表示 – 非営利 – 継承 3.0 非移植 License.


Posted by selphy

Tagged with:
7月 25

おはようございます、Selphyです。
前回のエントリーでは、Hello World を説明しました。

今回は、前回のサンプルを少し改造して、 構造体 を扱ってみます。
RS内で使用している型やメソッドなどについては、RenderScriptのReferenceに記載されているので、そちらをご覧ください。

ファイル構成

プロジェクトは、前回のものを使うので、特に変更はありません。

コード

改造したファイルのみ載せます。

  • HelloWorldScript.java
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
        protected HelloWorldScript(RenderScriptGL rs, final Context c, Resources res) {
            script = new ScriptC_HelloWorldScript(rs, res, R.raw.helloworldscript);
           
            // -------------- 構造体の初期化 ------------------
            int length = COLORS.length;
            texts = new ScriptField_RS_Text(rs, length, Allocation.USAGE_SCRIPT);
            for (int i=0; i<length; i++) {
                ScriptField_RS_Text.Item item = new ScriptField_RS_Text.Item();
                item.text = Allocation.createFromString(rs, "HelloWorld", Allocation.USAGE_SCRIPT);
                ColorRGB color = COLORS[i];
                item.red = color.red;
                item.green = color.green;
                item.blue = color.blue;
               
                // 容器(.rsで定義した*gTexts)にアイテムを格納する感覚です。
                texts.set(item, i, true);
            }
            script.bind_gTexts(texts);
           
            font = Font.create(rs, res, "fantasy", Font.Style.NORMAL, 20);
            script.set_gFont(font);
           
            rs.bindRootScript(script);
        }

    ScriptField_RS_Text というクラスがあります。
    これがHelloWorldScript.rsで定義した構造体で、このクラスは自動生成されます。
    ScriptField_RS_Textのインスタンスに、 ScriptField_RS_Text.Item というサブクラスのインスタンスをセットすることにより、HelloWorldScript.rsファイルでデータを扱うことができるようになります。

  • HelloWorldScript.rs
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    typedef struct RS_Text {
        rs_allocation text;
        float red;
        float green;
        float blue;
    } RS_Text_t;
    RS_Text_t *gTexts;



    int32_t root() {
        rsgClearColor(1.0f, 1.0f, 1.0f, 1.0f);
       
        int32_t left = 0;
        int32_t right = 0;
        int32_t top = 0;
        int32_t bottom = 0;
       
        int32_t size = rsAllocationGetDimX(rsGetAllocation(gTexts));
        for (int32_t i=0; i<size; i++) {
            rsgFontColor(gTexts[i].red, gTexts[i].green, gTexts[i].blue, 1.0f);
            rsgBindFont(gFont);
            rsgMeasureText(gTexts[i].text, &left, &right, &top, &bottom);
           
            // 5 * i で文字列の表示位置を少しだけずらしています。
            int32_t x = gTouch.x - (right - left) * 0.5f + 5 * i;
            int32_t y = gTouch.y - (bottom - top) * 0.5f + 5 * i;
            rsgDrawText(gTexts[i].text, x, y);
        }
       
        return 0;
    }

    8行目に、 *gTexts; とあります。
    この場合、Javaからデータを渡すときには、他の変数と違い
    script.bind_gText(ScriptField_RS_Text) というメソッドを使います。
    21行目のfor文では、構造体のデータに保持されているテキストデータとカラーデータを使って描画しています。

RSで自動生成されるもの

今回新たに出てきたもののみを記載します。

  • ScriptField_xxx.java
  • .rsファイルでxxxという名前の構造体を定義すると自動生成されます。

今回のサンプルは Google Code で公開しています。
次回は、テクスチャーを使ったサンプルを考えてます。

このエントリーの内容について
クリエイティブ・コモンズ・ライセンス
RSHelloWorld by Selphy, Kiroru Inc. is licensed under a Creative Commons 表示 – 非営利 – 継承 3.0 非移植 License.


Posted by selphy

Tagged with:
7月 19

前回のエントリーは メリットとデメリット についてでした。

今回は、イベント処理を含めた HalloWorld について説明します。
RS内で使用している型やメソッドなどについては、RenderScriptのReferenceに記載されているので、そちらをご覧ください。

ファイル構成

プロジェクト内は以下のようになっています。

drawableには、アプリアイコンしか入っていません。
strings.xmlは、アプリ名の要素しか定義していません。
rawhelloworldscript.bcは、自動生成されます。

コード

  • MainActivity.java
  • 1
    2
    3
    4
    5
    6
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            texture = (MainTextureView) findViewById(R.id.texture);
        }

    layout.xmlを設定しているだけです。

  • MainTextureView.java
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
        public MainTextureView(Context context, AttributeSet attrs) {
            super(context, attrs);
            if (rs == null) {
                RenderScriptGL.SurfaceConfig config = new RenderScriptGL.SurfaceConfig();
                rs = createRenderScriptGL(config);
            }
            if (script == null)
                script = new HelloWorldScript(rs, context.getApplicationContext(), getResources());
        }

    RSTextureViewからRenderScriptGLを生成します。
    Android 4.0 (APILevel 14)以下のときは、RSSurfaceViewを使ってください。
    使い方は、ほとんど同じです。

  • HelloWorldScript.java
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        protected HelloWorldScript(RenderScriptGL rs, final Context c, Resources res) {
            script = new ScriptC_HelloWorldScript(rs, res, R.raw.helloworldscript);
           
            text = Allocation.createFromString(rs, "Hello World", Allocation.USAGE_SCRIPT);
            font = Font.create(rs, res, "fantasy", Font.Style.NORMAL, 20);
            script.set_gFont(font);
            script.set_gText(text);
           
            rs.bindRootScript(script);
        }
       
        protected void touchEvent(float x, float y) {
            script.invoke_setTouchPosition(new Float2(x, y));
        }

    このクラスについても、今の段階では悩むところは無いと思います。
    なぜ、このクラスをつくったかというと、RSとJavaのアクセス部分は別にしておいた方が可読性が高くなるためです。
    とくに気にしないのであれば、MainTextureView.javaに含めても良いでしょう。
    唯一、注目して欲しいところは4行目で、Stringに必要なメモリ領域をAllocationとして確保している部分です。
    charで文字を渡しても良いのですが、Allocationを使ったほうが楽です。

  • HelloWorldScript.rs
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #pragma rs java_package_name(jp.kiroru_inc.rs_hello_world)

    #include "rs_graphics.rsh"

    rs_allocation gText;
    rs_font gFont;
    float2 gTouch;

    void setTouchPosition(float2 pos) {
        gTouch = pos;
    }

    int32_t root() {
        rsgClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        rsgFontColor(0.8f, 0.3f, 0.2f, 1.0f);
       
        int32_t left = 0;
        int32_t right = 0;
        int32_t top = 0;
        int32_t bottom = 0;
        rsgBindFont(gFont);
        rsgMeasureText(gText, &left, &right, &top, &bottom);
       
        int32_t x = gTouch.x - (right - left) * 0.5f;
        int32_t y = gTouch.y - (bottom - top) * 0.5f;
        rsgDrawText(gText, x, y);
       
        return 0;
    }

    1行目のjava_package_nameは、自動生成されるリソースの展開先です。
    ここで指定したパッケージ名として、genフォルダ直下にScriptC_HelloWorldScript.java (ScriptC_xxx.java: xxxは.rsのファイル名)などが作成されます。
    JavaからRSファイルに定義したメソッドや変数にアクセスするためには、この自動生成されたクラスを介して行います。
    13行目のinit32_t root()がメインループになる部分です。
    メソッド内の最後のreturn 0;ですが、この値が再描画の更新間隔になります。
    今回は、0を返しているので、タッチイベントが発生したときにシステムが自動的に再描画してくれます。
    例えば、50を返すと、20fpsで描画されます。

RSで自動生成されるもの

Javaから呼び出すものについてまとめました。
直接呼び出さないものについては記載していません。

※ scriptは.javaで定義した変数です。

  • xxx.bc
  • xxx.rsを作成するとres/rawに作成されます。
    私は、過去にこのファイルをEclipseで開こうとしたとき、Eclipseが落ちたので開かないほうが良いです。
    今も落ちるのかどうかはわかりません。

  • ScriptC_xxx.java
  • xxx.rsを作成すると自動生成されます。

  • script.set_xxx
  • xxx.rs内で定義したグローバル変数へのアクセサメソッドです。

  • script.invoke_aaaaa
  • xxx.rs内で定義したaaaaaというメソッドへのアクセサメソッドです。

他にも自動生成されるものはありますが、今回のサンプルで出てきていないものについては省きました。
それらについては、随時説明していきます。

今回のサンプルは Google Code で公開しています。
次回は、構造体を使ったサンプルを考えてます。

このエントリーの内容について
クリエイティブ・コモンズ・ライセンス
RSHelloWorld by Selphy, Kiroru Inc. is licensed under a Creative Commons 表示 – 非営利 – 継承 3.0 非移植 License.


Posted by selphy

Tagged with:
7月 13

積極的に使っている人がまだ少ないと思われる RenderScript (以下、RS) について、数回に分けて説明していきます。

最終的には、以下のイメージに示すアプリが完成します。

完成したアプリのスクリーンショット

このアプリは、横にスワイプすると、メリーゴーランドのように、同一円周上に配置されたモデルが円に沿って回転します。
カルーセルの基本形とも言えるので、これを改造すれば魅力的なUIを作ることも可能です。

まず、このエントリーでは、メリットとデメリットについて説明します。

メリット

  • 描画処理が早い
  • Java側でCanvasを使った描画をするより早いです。
    パフォーマンスについては、 Developers Blogの下の方に書いてあります。
    それによると、DalvikよりもRSの方が3倍以上早いという結果が出たようです。

  • Javaとの連携がしやすい (個人的な見解)
  • なぜかというと、RSで定義した変数やメソッドにアクセスするためのクラスが、自動的に生成されるためです。
    あまり、細かいことを考えずに使えるので、私個人としては連携がしやすいと思っています。

  • アーキテクチャに依存しない
  • RS専用のランタイムで動作するので、例えばx86搭載端末が発売されても大丈夫というニュアンスの一文が公式の資料の記載されています。

デメリット

  • 開発環境が十分ではない (2012/07/03時点)
  • メモ帳でプログラミングすることに抵抗がなければ問題ありませんが、補完がないので、APIを覚えるまでは少し面倒です。

  • デバッグが難しい
  • JavaでNullPointerExceptionなどを発生させると、LogCatで行数まで表示してくれますが、RSだとそのような出力はされないので、大規模なRSになってくると大変です。

資料

このエントリーで参考にした資料です。

次回は、イベント処理を含めた HalloWorld の説明を予定しています。


Posted by selphy

Tagged with:
2月 23
今日の話題は ICS(Android 4.0)から ActionBar を取り上げます。

ActionBar は Android 3.0 (API Level 11) から導入された Android の新しいメニューの仕組みです。
これまでは本体にハードウェアキーとして「メニュー」ボタンがありましたが Android 3.0 以降では、「メニュー」を含むハードウェアボタンが基本的に廃止されました。メニュー以外のボタンはそれでもソフトウェアキーとして引き続き画面下部に配置されるのですがメニューボタンだけは廃止され Action Bar を使うことが推奨されています。

Action Bar の構成

Action Bar は4つの部分から構成されています。(参考サイト

  1. App Icon
  2. View Control
  3. Action Buttons
  4. Action Overflow

p0.png

App Icon は、アプリを識別する用途で用いられます。またトップレベルで無い場合は、ひとつ上の階層に遷移するためのボタンとしても使用されます。

View Control には、ドロップダウンメニューやタブコントロールなどを配置できます。そういうもの必要でない場合はアプリのタイトルを表示します。

Action Buttons には、頻度が高いアクションや重要なアクションに対する操作ボタンが割り当てられます。

Action Overflow には、頻度の低いアクションや Action Buttons の領域に入りきらなかったボタンが格納されます。

Action Bar の作成

Action Bar はこれまでのメニューと同様にプログラムまたはリソースから作成することができます。ただし、showAsAction という属性が追加されており、その項目が Action Buttons の領域に表示されるべきか Action Overflow の領域に表示するかの判断の材料になります。リソースで指定した場合の一例です。

res/menu/list.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/action_one"
         android:icon="@drawable/ic_action_one"
         android:title="@string/action_one"
         android:showAsAction="ifRoom" />
        :
    <item android:id="@+id/action_five"
         android:icon="@drawable/ic_action_five"
         android:title="@string/action_five"
         android:showAsAction="ifRoom" />
</menu>

ここで android:showAsAction には ifRoom が指定されています。これは、表示できない場合は Action Overflow 領域に格納せよという指示です。他に常に Action Buttons 領域に表示する always、常に Action Overflow に格納する never という設定値があります。

Action Bar の表示

Action Bar を表示させる場合はこれまで同様 Activity で onCreateOptionsMenu をオーバライドさせてやれば良いのですが、Fragment を使用する場合、各 Fragment の onCreateOptionsMenu をオーバライドさせてやることが可能です。

ここで良くやってしまうミスがタイトルにもつけた、おまじないの付け忘れです。
Fragment でメニューを追加する場合は、必ず setHasOptionsMenu(true) を付けましょう。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
   
        setHasOptionsMenu(true); // 付け忘れないように!!
    }

サンプル

Action Bar を使用したサンプルとして Fragment でメニューを指定したものを作成しました。

p1.png p2.png

47都道府県をリスト表示したメイン画面と、アクションバーのアクションを実行して遷移する詳細画面の2画面で構成しています。それぞれ別のメニューを設定しています。詳細画面からメイン画面に戻るには Action Bar 上の App Icon を押してください。

また、メイン画面のリスト上のアイテムを長押しすると Action Mode になり、複数のアイテムが選択できるようになります。このときも別のメニューを表示するようにしています。

p3.png

完全なサンプルコード

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


Posted by sak+

Tagged with:
2月 17
最初に ICS 開発でのハマりポイントの紹介です。私はこれで1晩悩みました。(^^;

過去のエントリ『プログレスダイアログから処理を中断する。』の中で、端末の向きを変更した場合に onCreate が走るのを避けたい場合、AndroidManifest.xml の中で android:configChanges=”orientation” を指定すると onCreate の代わりに onConfigurationChanged が走りますよ!と紹介しています。

ICS で開発において同じようにしたのですが onConfigurationChanged がどうしても呼ばれません。コードをいろいろ変更して切り分けたところ android:targetSdkVersion を Android 3.2 (API Level 13) 以降で動作するように設定した場合、onConfigurationChanged が呼ばれなくなることが分かりました。理由は Android Developers の API リファレンスにズバリ記載されてました。


Caution: Beginning with Android 3.2 (API level 13), the “screen size” also changes when the device switches between portrait and landscape orientation. Thus, if you want to prevent runtime restarts due to orientation change when developing for API level 13 or higher (as declared by the minSdkVersion and targetSdkVersion attributes), you must include the “screenSize” value in addition to the “orientation” value. That is, you must decalare android:configChanges=”orientation|screenSize”. However, if your application targets API level 12 or lower, then your activity always handles this configuration change itself (this configuration change does not restart your activity, even when running on an Android 3.2 or higher device).


要するに Android 3.2 からは android:configChanges=”orientation” とするだけではダメで、新たに追加された screenSize も一緒に指定してねということのようです。

こんな感じで。

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

android:configChanges=”orientation|screenSize” を AndroidManifest.xml の中で指定するとちゃんと onConfigurationChanged が呼ばれるようになりました。めでたしめでたし (^^

では!


Posted by sak+

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