2012年1月30日月曜日

paddingの留意点

paddingの留意点

副題: CheckBox bug

この記事の続編をRadioButtonへのpaddingに書きました。(2012/05/09)

この記事ではCheckBoxを題材にしていますが、他のViewでも同じ趣旨のことは言えると思われます。

そして、また、この記事ではpaddingを題材にしていますが、背景色(background)の設定でも同じことが言えると思われます。

既に次のサイトでもお馴染みのCheckBoxに関する不都合です。
Android - Spacing between CheckBox and text
Is that is a bug with CheckBox in android?

CheckBoxに対してpaddingを施すと、CheckBoxの中の「文字列」と「チェックの画像」とが重なってしまう、という現象が発生します。

ここで言う、「paddingを施す」というのは、CheckBoxを定義するxmlでpaddingを施すことと、Javaのcodeでpaddingを施すことの両方を言います。

教科書どおりにCheckBoxを定義したxmlは次のとおりです。これだけだと何の問題もありません。
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
    <CheckBox
        android:id="@+id/CheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello"
        >
    </CheckBox>
</LinearLayout>

上記xmlを使って次のJava codeを実行すると変になる。
public class CheckBoxActivity extends Activity{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        CheckBox cb;
        cb = (CheckBox)findViewById(R.id.CheckBox);
        cb.setPadding(5,5,5,5);
    }
}

xmlにpaddingを入れても変になる
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello"
        android:padding="5dp"
        >
    </CheckBox>
</LinearLayout>

解決方法として、次の4通りが考えられます。
  1. CheckBoxに対してpaddingを施すことをやめる。(-_-;)
  2. 変になることを考慮に入れて、CheckBoxの左側にpaddingを多めに施す。
  3. 文字列はTextViewで表示する。
  4. CheckBoxの上位にLinearLayout等のViewGroupを設け、このViewGroupに対してpaddingを施す。
このバグは今後修正されるのでしょうか。この問題が提起されたのが2010年10月ですので、ずっと放置されたままですね。むむむ!
今後修正される可能性があるとするならば、上記2の方法を講じることは避けた方がよろしいです。
「修正なんかあり得る訳が無い!」という確信のある御仁は、上記2の方法でも良いかもしれません。そして、その確信は、今まさに、現実のものとして実現されています。この現実は、今後もずっと続くでしょう。この確信者は、先見の明があると言って良いかもしれません。(-_-;)

しかし、Androidの設計者を信奉する迷える子羊は、修正があることを疑いません。
そこで、上記3の方法を講じたxmlを次に掲げておきます。文字列の縦位置をチェックボックス画像の中央に位置付けるのが重要です。

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
    <CheckBox
        android:id="@+id/CheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:padding="5dp"
        >
    </CheckBox>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/CheckBox"
        android:text="長い、長い、とても長い文字列が、ドンブラコ ドンブラコ と流れてきました。 TextView は処理をしきれずに落ちてしまいました。おしまい。"
        >
    </TextView>
</RelativeLayout>

Viewに対してpaddingを施すと変になるって記事は、既に書いておきました。この記事を読んで頂ければ、上記第4番目の対処方法が分かると思います。

2012年1月29日日曜日

xml:文字列の前後の空白文字が表示されない

xml:文字列の前後の空白文字が表示されない

画面構成を整えるため、文字列の前後に空白文字を挿入したい場合がある。
英語の環境で次のxmlを実行させた場合、文字列の前後の空白文字は表示されない。
    <string name="TextVolume"> Volume </string>
空白文字を表示するためには、次のように、前後を括弧で括らねばならない。
    <string name="TextVolume">" Volume "</string>

日本語の環境において、文字列の前後に全角の空白文字を書いた場合、その全角空白文字は表示される。
    <string name="TextVolume"> 音 量 </string>
しかし、半角の空白文字であれば、その空白文字は表示されない。この場合、前後に括弧を施せば表示される。

これらはxmlでの話である。Javaのcodeにおいては、普通に、教科書どおりに書けば良い。

2012年1月27日金曜日

xml:勝手に空白文字が挿入されてしまう

xml:勝手に空白文字が挿入されてしまう

次のxmlがある。この文字列を画面に表示すると、「春」から「夏」にかけて、改行はされる。しかし、その部分に、空白文字が1個挿入されてしまうのである。なので、「夏」が1個右にズレて表示される。

<string name="AboutThis3">
春\n
夏
</string>

これは、「春\n」と「夏」の間に、(エディタ上では見えない)改行文字が挿入されているために発生する現象である。
そして、「春\n」の前、及び「夏」の後にも、各々1個の(エディタ上では見えない)改行文字が挿入されている。
この(エディタ上では見えない)改行文字が空白文字に置き換えられるのである。
なので、この(エディタ上では見えない)改行文字を書かないようにすれば良い。解決策は次のとおりである。

<string name="AboutThis3">春\n夏</string>

(エディタ上では見えない)改行文字が空白文字に変換されてしまう仕様は、英語を記述することを想定しているためのようである。日本語しか使わないのであれば、違和感のある仕様だ。
参考:第10回 落とし穴になる空白文字と改行文字

上記例では短い文字列であったため問題は無い。
しかし、日本語で比較的長い文字列を書く場合、エディタ上で読み難くなる。

<string name="AboutThis3">【動作環境】android 2.2\n  【ソフトウェア種別】フリーソフトウェア\n【制作者名】Androyer\n 読み難いぞ(T_T)</string>

エディタ上で読み難くなるという、ただそれだけであるため、問題は無い。
しかし、やっぱり見易くしたいという御仁もいらっしゃるであろう。その場合、やむを得ないが、括弧で括り、文字列の先頭に半角空白文字を挿入して頭を揃えることになる。

    <string name="AboutThis3">
        " 【動作環境】android 2.2\n"
        "【ソフトウェア種別】フリーソフトウェア\n"
        "【制作者名】Androyer"
    </string>

第1列目が空白文字で表示されてしまうが、ご容赦して頂くことになる。このことについて、文句を言う利用者はいないであろう。

2012年1月12日木曜日

実験:configuration changeは、画面の無いActivityでも発生するのか

実験:configuration changeは、画面の無いActivityでも発生するのか

本実験の趣旨
世間に出回っている多くのアプリの共有機能は、(Serviceに対してではなく)Activityに対して共有するものある。
拙作プログラムのActivityは、ゴチャゴチャしており、これ以上何か機能を追加したくは無い。
このため、共有を受け付けるためだけのActivityを作成することにした。共有を受け付けるためだけであるため、画面を表示してはいけない。そこで、画面の無いActivityが必要になった。

画面の無いActivityとは、AndroidManifest.xmlにおいて、次の行を追加したものです。
android:theme="@android:style/Theme.Translucent.NoTitleBar"

画面が表示されない状態であるから、端末を縦位置から横位置へ、又は横位置から縦位置へ変更したとしても、configuration changeは発生しないと想定される。果たしてどうなのか。

結果
上記設定による画面を持たないActivityにおいて、端末の位置を変更させた場合、onRetainNonConfigurationInstanceメソッドが実行され、Activityは再起動する。
つまり、configuration changeが発生するということである。

追記(重要)
上記は、2012年1月時点の結果であった。
2012年5月1日に、この実験を行おうとした。onRetainNonConfigurationInstanceメソッドはdeprecatedであるとされていた。このため、onSaveInstanceStateメソッドでconfiguration changeの発生を捕捉しようとした。しかし、onSaveInstanceStateメソッドでは、画面の無いActivityのconfiguration changeを捕捉できなかった。

このように、1月時点と、5月時点での結果が異なっていたので、詳しく実験をしてみた。
参考:透明のActivityにはconfiguration changeが発生しない件

2011年12月18日日曜日

AdMob画像の場所に、代替画像を表示する

AdMob画像の場所に、代替画像を表示する

副題:待ち時間に、愛想笑い微笑みを

アプリの画面の表示と同時に、AdMob画像が表示されるのでは無い。
アプリの画面が表示された後、インターネットから広告のデータをダウンロードしてから、その広告データに基づきAdMob画像が表示されるのである。AdMob画像が物理画面の下端にあるのであれば気にはならない。

しかし、何も考慮せずに、AdMob画像を物理画面の上端に配置した場合、AdMob画像が表示された時、その画像がその位置に「挿入される」ということになるため、その画像の下に配置しているViewが、一斉に下にズレてしまう。このような振る舞いは、利用者に違和感を与える。
if(あなたの感じ方=="別に問題では無い。") return;

この問題への対処の方法として、AdMob画像を表示する位置に、予め別の画像を表示するようにしておいて、そこに後からAdMob画像を表示するようにすれば良い。
この方法を講じるためには、愛想笑い微笑みを描いた横320×縦50の画像を作成し、この画像をres>drawableディレクトリに入れておかねばならない。
if(あなたの画力=="苦笑い程度しか描く画力が無い") return;

下記のxmlで実装する。画像ファイル名はbanner.pngである。FrameLayoutを使うことにより、同一の場所に表示させることができる。

<?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="#a00"
    >
    <FrameLayout
        android:id="@+id/FrameLayout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:gravity="center"
        >
        <ImageView
            android:src="@drawable/banner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            >
        </ImageView>
        <LinearLayout
            android:id="@+id/AdMob"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            >
        </LinearLayout>
    </FrameLayout>
    <Button
        android:text="@string/Button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/FrameLayout"
        >
    </Button>
</RelativeLayout>

この方法だと、AdMob画像が表示される前も後もButtonが下にズレることは無い。かつ、AdMob画像が表示されるまでの短い時間ではあるが、愛想笑い微笑みの画像を表示させることができて、利用者を飽きさせない。
if(あなたの感想=="飽きはしないが、好感を持てるということも無い") return;

2011年12月16日金曜日

AdMobを中央に配置する。

AdMobを中央に配置する。

副題:その空間に、愛想笑いを。

何も考慮せずにAdMobのlayoutを作成すると、AdMobの広告は物理画面の左詰めになる。この場合、端末が横位置になった場合、広告の右側に無意味な空白が発生する。
if(あなたの考え=="別に構わないじゃないか") return;

このような無意味な空白はAdMob側で何か措置を施して頂きたいが、現状ではどうにもならないので、プログラマ側でなんとかする。
無意味な空間を、なんとか取りツクロうのである。言わば、気まずい雰囲気の中で行われる、愛想笑いのような仕掛けだ。
if(あなたの表情=="笑い顔には自信が無い") return;

取り敢えず考えられるのは、広告を横方向の中央に配置すれば良い、ということであろう。中央に配置することにより、無意味な空間を左右に分散させるのである。これにより、無意味と感じる程度が低下することが期待できる。
if(あなたの感想=="左側にも無意味な空間が広がるだけだ") return;

背景が画像である場合は、FrameLayoutを使うことになるであろう。そのためのlayout用xmlのsample codeは次のとおりである。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ViewGroup"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#a00"
    >
    ここに背景用のViewを設置する。
    <LinearLayout
        android:id="@+id/AdView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        >
    </LinearLayout>
</FrameLayout>

他のViewがBottunやScrollView等であれば、次のようにRelativeLayoutを用いることになる。
<?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="#a00"
    >
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center_horizontal"
        >
        <LinearLayout
            android:id="@+id/AdView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            >
        </LinearLayout>
    </LinearLayout>
    ここに他のViewを書いていく。
</RelativeLayout>

上記例では、中央に配置したことを視覚的に明確にするため、背景を赤色で塗ってある。しかし、広告を目立たせるためには、背景を目立たない色にするべきであろう。
if(あなたの趣味=="背景は広告よりも目立つようにすべきだ") return;

javaのcodeは、AdMob Version 4.3.1の実装に書いたとおりである。

後日談は、画面サイズに応じてAdMobのサイズを変えるに書きました。

2011年12月3日土曜日

AdMob Version 4.3.1の実装

AdMob Version 4.3.1の実装

AdMobは、アプリ内に広告を表示する機能を提供する。教科書どおりに作れば良い。ここでは、難易度の高かった項目を解説する。

問題意識
現在、androidのplatform 2.2 API level 8で稼働するアプリを開発している。
教科書では、AndroidManifest.xmlに次の1行を挿入するように指示がされている。
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
ところが、screenSize及びsmallestScreenSizeは、platform 3.2 API level 13以上が必要とされるため、Error: String Types not allowedというコンパイルエラーが発生する。

screenSize及びsmallestScreenSizeを削除すると、次の趣旨の実行時エラーが発生する。
E/Ads(11952): The android:configChanges value of the com.google.ads.AdActivity must include screenSize.
E/Ads(11952): The android:configChanges value of the com.google.ads.AdActivity must include smallestScreenSize.
E/Ads(11952): You must have AdActivity declared in AndroidManifest.xml with configChanges.
どのようにすれば良いのか。

結論
回答はAdmob in Android 2.2にあった。この問答から、次のような手順を講じれば良いことがわかった。
  1. Eclipseを最新版にする。(必須では無い)
  2. Android SDKを最新版にする。
  3. (重要)Eclipseのメニューコマンド>Project>Properties>Android>Project Build Target>platform 4.0 API level 14(最新版)を選択する。このコマンドはJava Build Path>Librariesに連動している。
  4. AndroidManifest.xmlに次の1行を追加する。
    <uses-sdk android:minSdkVersion="8"/>
  5. Project>Cleanを実行する。
Project>Properties>Java Build Path>Librariesの設定は手作業で行うのではなく、上記3の操作を行えば、自動的にJava Build Path>Librariesの設定が行われる。

この手順によって作成されたアプリは、platform 2.2 API level 8で無事稼働する。
上記の各手順の値を具体的にどうするかは、各プログラマーの置かれた立場によって異なる。

参考:AdMob
教科書:Google AdMob Ads SDK

実機をテスト用端末に設定する方法
開発者は、広告をクリックしてはいけない。広告を誤ってクリックしてしまうことを避けるため、広告を(本物の広告では無く)開発用の広告にしておかねばならない。

AndroidManifest.xmlの書き方は、教科書に掲載されているとおり、そのまま書けば良い。

layout用のxmlは、例えば、次のようにする。普通にlayoutを作成すれば良いだけだ。
<?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="wrap_content"
    >
    <LinearLayout
        android:id="@+id/AdView"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:layout_alignParentBottom="true"
        >
    </LinearLayout>
(以下省略)

上記layout用xmlを用いるとして、Javaにおけるsourceは、取り合えずonCreateメソッド内で次のとおり書いて、一旦、実機で実行させる。
        AdView adView;
        LinearLayout ll;
        AdRequest ar;
        
        //第三番目の引数にはAdMob publisher IDを指定する。
        adView = new AdView(this, AdSize.BANNER, "xxxxxxxx");  
        ll = (LinearLayout)findViewById(R.id.AdView);  
        ll.addView(adView);
        
        ar = new AdRequest();
        //ar.setTesting(true);//4.0.4の場合使える。4.3.1では使えない。
        ar.addTestDevice(AdRequest.TEST_EMULATOR);
        adView.loadAd(ar);

上記プログラムを実機で実行させると、LogCatに次の趣旨の行が出力される。
I/Ads(12492): To get test ads on this device, call adRequest.addTestDevice("yyyyyyyy");
この"yyyyyyyy"の部分を引用して、adView.loadAd(ar);の直前に、次の行を書き込む。
        ar.addTestDevice("yyyyyyyy");
そうすると、次回の実行時から、実機では、本物の広告を表示するのでは無く、テスト用の広告を表示する。

本番では、addTestDeviceメソッドを削除する。
もっとも、削除を忘れても実害は無い。何故なら、指定したIDの端末だけがテスト用になっているだけだからだ。(このあたりは、実際には試してはいません。)

そういう意味で、Version 4.0.4に比べて、4.3.1は進化したと言える。Ver.4.0.4では、setTesting(true)メソッドにより、全ての端末がテスト機になってしまう。このメソッドの削除を忘れて出荷した場合、喜劇悲劇が生まれる。

ここで、敢えて、Ver.4.0.4を使い、setTesting(true)を実装させたまま、出荷してしまうってのも、一興である。「削除を忘れたのではありません。敢えて、残しておいたのです」と。(-_-;)

いや、それはダメです。パブリッシャー様向けのガイドラインとポリシーには、「アプリ内広告では SDK のメソッドのみを使用し、SDK の最新のバージョンを使用する必要があります」と書かれています。