2013年1月27日日曜日

画面サイズに応じてAdMobのサイズを変える

画面サイズに応じてAdMobのサイズを変える

従前は、画面が縦位置か横位置かによって、また、使われる端末の画面の大きさによって、AdMobの幅や位置を調整しなければならなかった(しなくても良いのだが)。

だが、これは不便だ。以前私は、このような不便に対する不満をこのブログに書いておいた。AdMobを中央に配置する。
そうしたところ、この要望に応えて頂いたのが、Smart Bannersなのである(私の要望に応えた訳では無いだろうが)。

Smart Bannersを使うことによって、AdMobは自動的に自分でサイズが変わることができる。これにより、プログラマが画面のサイズを取得してAdMobのサイズや位置を調整する必要は無くなった。自動的に、横中央に配置してくれるのである。

sample codeは次のとおりである。
    AdView adView;
    LinearLayout ll;
    AdRequest ar;
        
    //第二番目の引数にSMART_BANNERを指定する。
    //第三番目の引数にはAdMob publisher IDを指定する。
    adView = new AdView(this, AdSize.SMART_BANNER, "xxxxxxx");
    ll = (LinearLayout)findViewById(R.id.AdSettings);  
    ll.addView(adView);
    ar = new AdRequest();
    adView.loadAd(ar);

2013年1月15日火曜日

Localeをsortする

Localeをsortする

Stringをsortすることは、ArrayAdapterのsort()を使ってみるのとおりです。

今回はArrayAdapterを使ってLocaleをsortします。

toString()メソッドを実装しているObjectであればArrayAdapterで取り扱えます。幸いにして、Locale classはtoString()メソッドを実装しています。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ListView lv;
        lv = new ListView(this);
        setContentView(lv);
        
        ArrayAdapter<Locale> aa;
        aa = new ArrayAdapter<Locale>(this, R.layout.activity_main, ls);
        aa.sort(new MyComparator());
        lv.setAdapter(aa);
    }
    
    public class MyComparator implements Comparator<Locale>{
        @Override
        public int compare(Locale l1, Locale l2){
            int iRet;
            iRet = l1.getLanguage().compareTo(l2.getLanguage());
            if(iRet==0){
                return l1.getCountry().compareTo(l2.getCountry());
            }
            else return iRet;
        }
    }

    Locale[] ls = {//ランダムに並んでいる
        Locale.CHINA,
        Locale.FRENCH,
        Locale.CANADA,
        Locale.JAPANESE,
        Locale.GERMANY,
        Locale.ENGLISH,
        Locale.CANADA,
        Locale.GERMAN,
        Locale.JAPAN,
        Locale.US,
        Locale.FRANCE,
        Locale.UK
    };
}

R.layout.activity_mainのxmlは次のとおりです。
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" 
    >
</TextView>

2013年1月13日日曜日

onCreateOptionsMenuが呼び出されない

onCreateOptionsMenuが呼び出されない
onCreateOptionsMenu is not called in 4.0

標題については次のサイトで議論されている。
Options menu not showing in ICS using compatibility library
Android3.0以降 onCreateOptionsMenuが呼び出されない

これらのサイトで書かれているように、AndroidManifest.xmlファイルでTheme関連を全て削除したが、引き続き問題は発生し続けた。

私のプログラムのonCreateメソッドには次の一文があった。
requestWindowFeature(Window.FEATURE_NO_TITLE);
これを削除すると問題が解決した。

これを削除した場合であっても、AndroidManifest.xmlファイルでThemeが設定されていると問題は発生する。
AndroidManifest.xmlファイルでThemeを設定したい場合には次の一文を追加すれば良い。
requestWindowFeature(Window.FEATURE_ACTION_BAR);

以上を整理すると、onCreateメソッドにおいて次のように書けば良い。
public void onCreate(Bundle icicle){
    super.onCreate(icicle);
    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.ICE_CREAM_SANDWICH){
        requestWindowFeature(Window.FEATURE_ACTION_BAR);
    }
    else{
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    }
    setContentView(R.layout.inputbyhand);

参考にしたサイト

2013年1月6日日曜日

動的にオプションメニューを変更する

動的にオプションメニューを変更する
dynamically changing option menu

動的にoption menuを変更する場合、addメソッドやremoveitemメソッドを使いたくなる。

しかしながら、removeitemメソッドを使った後で、addメソッドを使いMenuItemを追加しても、その追加されたMenuItemはMenuの最後尾に配置される。
追加されたMenuItemを任意の位置に配置するコマンドは無いようである。

このため、MenuItem#setVisible()を使うことになる。
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem mi;
        mi = menu.findItem(R.id.Menu_Next);
        if(bDiskFile==true){//必要に応じ
            if(mi.isVisible()==false){
                mi.setVisible(true);
            }
        }
        else{
            if(mi.isVisible()==true){
                mi.setVisible(false);
            }
        }
        return super.onPrepareOptionsMenu(menu);
    }

addメソッドの第一引数にはint groupIdがある。これは、任意の複数のMenuItemを追加する場合に、それらをグループ化するものである。Java codeでaddメソッドを使って、1個1個MenuItemを追加していく場合に使う。
詳細は次のサイトにsample codeがある。
Options Menu in Android (Code)

xmlでgroupを設定するsampleは次のサイトにある。
Android 奔走記 XML で Menu

2013年1月3日木曜日

GalleryからPicasaの画像を取得する

GalleryからPicasaの画像を取得する

Galleryを起動するには次のようにする。
    Intent it = new Intent();
    it.setType("image/*");
    it.setAction(Intent.ACTION_PICK);
    startActivityForResult(it, ID_Result_Gallery);

Galleryから戻ってくると、onActivityResultメソッドで値を取得する。
    protected void onActivityResult(int r, int resultCode, Intent data) {
    if(r==ID_Result_Gallery){
        if(resultCode==RESULT_OK){
            Uri u = data.getData();
            //この記事は、この場所で何をするのかという内容です
        }
        else{//Galleryの操作途中でキャンセルした場合
        }

Galleryから取得したUriのデータ内容が端末に存在する画像ファイルを指す場合、そのUriのデータを、フルパスファイル名(String型)に変換できる。次がそのprogramです。
    ContentResolver cr = getContentResolver();
    String[] columns = { MediaColumns.DATA };
    Cursor c = cr.query(u, columns, null, null, null);
    if(c==null) return;
    c.moveToFirst();
    i = c.getColumnIndex(MediaColumns.DATA);
    s = c.getString(i);
    c.close();

しかし、Uriのデータ内容が、Picasaの画像を指す場合、そのUriのデータからフルパスファイル名(String型)には変換できない。Cursor#getString()の戻値はnullになる。
ただし、次のようにprogramすれば表示用の名前(実行上はファイル名)を取得できる。
    ContentResolver cr = getContentResolver();
    String[] columns = { MediaColumns.DATA,  MediaColumns.DISPLAY_NAME};
    Cursor c = cr.query(u, columns, null, null, null);
    if(c==null) return;
    c.moveToFirst();
    i = c.getColumnIndex(MediaColumns.DISPLAY_NAME);
    s = c.getString(i);
    c.close();

Picasaの画像データは端末に存在しないため、ファイルのパス名を取得できない仕掛けになっているのであろう。

上記プログラムで知り得たUriの値をメモしておいて、その値を使って、onCreate()の時点で、Picasaの画像を呼び出すと、アプリがクラッシュする。面白いので、読者諸兄も下記のプログラムを実行してみよう。読者諸兄をショウモナイことに誘う。
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Uri u;
        Uri.Builder ub = new Uri.Builder();
        ub.scheme(ContentResolver.SCHEME_CONTENT);
        
        // * test pattern 1 Picasaのファイル
        ub.authority("com.google.android.gallery3d.provider");
        ub.path("/picasa/item/0000000000000000000");
        
        // * test pattern 2 端末内のファイル
        //ub.authority("media");
        //ub.path("/external/images/media/142");
        
        u = ub.build();
        
        ContentResolver cr = getContentResolver();
        
        //pattern 1 の場合次の行でクラッシュする。
        Cursor c = cr.query(u, null, null, null, null);

Picasaの画像データは「保護されている状態」にあるのかもしれない。プログラムで直接参照しに行くと死に行くことになる(シャレ)。
Galleryから戻って来た後であれば、当該Uriを指定して当該画像を取得することはできる。

ただし、ある画像ファイルをGalleryで選択した後で、(Picasaの)別の画像ファイルをプログラムで直接参照しに行くと死に行くことになる(再度シャレ)。
つまり、端末の利用者がGalleryでPicasaの画像を選択した後でなければ、そのPicasaの画像をプログラムで取り扱えないのである。

利用者がGalleryで選択しなければ当該画像を取得できない。Uriのデータだけを使えばPicasaデータを取得できるというものではない。端末に存在する画像ファイルであればUriのデータだけで取得できるのに。
利用者にいちいちGalleryで選択して頂くのは面倒である。このため、アプリを構築する場合には、取得した画像を自分の端末に保存することになるであろう。


次に留意点を書いておく。
androidのバージョンが4.0以上である場合
Uriの<scheme>にはcontentが代入されている。
画像データを取得するにはContentResolver#openInputStream()を使う。
AndroidManifestファイルにpermissionを設定する必要は無い。

androidのバージョンが2.3の場合
Uriの<scheme>にはhttpsが代入されている。
画像データを取得するにはHttpGet等を使う。
HttpGet等を使うのであるから、AndroidManifestファイルにInternet関係のpermissionを設定する必要がある。

4.0はpermissionを設定する必要がないので優れていると評価できる。私の手元には3.0の端末が無いため検証はできない。

sample programは次のサイトに掲載した。
GalleryからPicasaの画像を取得する

以下はファイル名の話である
PicasaにおけるMediaColumns.DISPLAY_NAMEのデータも保存しておく必要はある。このデータをアプリのタイトルバーに表示するのである。また、「保存した画像を選択する」時に表示する文字列としても利用する。

MediaColumns.DISPLAY_NAMEはDISPLAYの名前であって、(実行上はファイルの名前ではあるが)正確にはファイルの名前では無い。もしかして、nullである可能性もある。このため、nullの場合には"unknown"という文字列をprogramで挿入すれば良い。
ファイル名として取り扱えない文字が含まれている可能性を考慮しなければならない。何故考慮しなければならないのか。何故なら、私は、この仕様の詳細を知らないからである(ここで笑う)。
このため、MediaColumns.DISPLAY_NAMEで取得した文字列は、あくまで表示用として使い、ファイル名に応用するのはやめた方が良い。

不確実な前提を基に、適当な情報の提供をしているが、この情報が諸兄のお役に立てれば不幸中の幸いである(再度ここで笑う)。