2014-11-12

今回のAndroidプロジェクトでハマったこと学んだこと7つ


1.HashMapの代わりにSparseArrayを使う

keyがintのHashMapを使用したら以下の様なWarningがでた。
Use new SparseArray<Bitmap>(...) instead for better performance

パフォーマンスがイイカンジだからSparseArrayを使ってねと言っている。
SparseArrayは、keyにintしか取らないことで、
メモリ使用量やパフォーマンスの点でHashMapより有利だと謳っている。
valueがBoolean, Integerのときは更に
SparseBooleanArray、SparseIntegerArrayを使うこともできる。
リソースにやさしいデータ型でステキ。

SparseArray
http://developer.android.com/reference/android/util/SparseArray.html

AndroidのSparseArrayは本当に速いのか測定してみた
http://thinking-megane.blogspot.jp/2012/06/androidsparsearray.html

SparseArrayメモ
http://noxi515.blogspot.jp/2012/02/sparsearray.html



2.ScrollView内のListViewの高さが1行ぶんしかない

ScrollView内にListViewを表示させるということをやった。
リストには3行の要素を含んでいたが、いざ表示させてみると
リストは1行分の高さしかなく、2行目以降の要素が隠れてしまい表示されない。

後で知ったが、そもそもこういったViewの組み立て方がよろしくない様子。
とはいえリストの要素分の高さで表示しなければならない。
今回は、以下の様なコードで実現した。

int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
    View item = adapter.getView(i, null, listView);
    item.measure(MeasureSpec.UNSPECIFIED,
        MeasureSpec.UNSPECIFIED);
    totalHeight += item.getMeasuredHeight();
}
ViewGroup.LayoutParams layoutParams = listView.getLayoutParams();
layoutParams.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
listView.setLayoutParams(layoutParams);
 
ScrollView内にListViewを入れてはいけない
http://blog.yagni.jp/archives/294
Android: ScrollView 内にある ListView の高さが 1行になるのを回避する
http://bigchu.com/android-scrollview-listview-line-1-measure.html



3.AQueryをFragment内で使う

AQueryは、Androidアプリ内でJQureyのようにViewを操作できるライブラリ。

シンプルなActivityのときは

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.unko);
    AQuery $ = new AQuery(this);

}

のようにして、setContentView()したViewを扱うことができる。
Fragment内では明示的にviewを指定すればOK。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    view = inflater.inflate(R.layout.fragment_unko,
        container, false);
    AQuery $ = new AQuery(view);
    return view;
}
AQueryを使って書くコードは簡潔で楽しい。



4.drawableRightの便利さ

テキストの隣にアイコンを添えて表示したいことがある。
このようなとき、普通ならTextViewとImageViewの2つを並べれば実現できる。

しかし、drawableRightを指定すれば、
ImageViewを使わずにこれを実現できてしまう。

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:drawableRight="@drawable/icon_hoge"
    android:text="@string/hoge_text" />


ちなみにdrawableLeft,  drawableTop, drawableBottomもあり、
drawablePaddingを使った位置調整も可能。
TextViewに限らず、Buttonなどでも使える。ビュー定義がスッキリして便利。

ボタンにアイコンを表示する
http://neta-abc.blogspot.jp/2012/04/blog-post.html



5.bitmapImageが自動でリサイズされないようにする

bitmapImageで画像を描画したが、期待した大きさで表示されない。
若干拡大されて表示されてしまう。

これを回避するためには、画像を読み込むときに自動リサイズを無効にすればよい。

Options options = new BitmapFactory.Options();
options.inScaled = false;
BitmapFactory.decodeResource(
    getContext().getResources(), R.id.hoge_img, options);
I don't want Android to resize my bitmap Automatically
http://stackoverflow.com/questions/6805355/i-dont-want-android-to-resize-my-bitmap-automatically




6.HTMLに%が含まれるものをloadDataするとAndroid2.xでwebViewが表示されない

これまでは、本問題に遭遇した際には
%を%25のようにURLエンコードすることで対応していたが、
今回の開発ではそれでは対応しきれず、loadDataWithBaseURLを使うことで根本的に解決。
webView.loadDataWithBaseURL("http://hoge", htmlString,
"text/html", "utf-8", null);
% -> %25のようにURLエンコードする必要はない。
また、http://hogeの部分はダミーURLで構わない。

Android: WebView unable to show '%' percentage sign
http://stackoverflow.com/questions/5097483/android-webview-unable-to-show-percentage-sign



7.webView内のiframeからイベントが上がってこない

webView内のiframe内にリンク画像があり、
タップするとリンク先ページを外部ブラウザに開くということをやった。
が、
実際にタップしてみると、
iframeの中だけで該当ページへと遷移してしまう。
これでは、遷移先ページの表示があまりに小さくて意味が無い。

これの対策としては、webView側で、リンククリックのイベントを検知して、
iframe内ではなく、外部ブラウザでページを表示するようにすればよい。
このときに使うのがwebViewのshouldOverrideUrlLoading()メソッドだ。

ところが、webView内でのリンククリックはイベントとしてwebViewに上がってきて、
shouldOverrideUrlLoadingでハンドリングできるが、
webView内のiframe内でのリンクの場合は、クリックイベントが上がってこない。

クリックイベントが上がってこないとどうしようもなさそうに見えるが、onLoadResourceを使うことで対応できる。
onLoadResourceは、webView内で発生する全てのhttpリクエスト毎にコールされるイベントハンドラで、
画面遷移時のみならず画像, css, jsの読み込みなど、すごい勢いでコールされまくる。
なので、あまり重い処理を書くことは避けたい。
今回は、iframe内のリンクURLの時だけ外部ブラウザでページを開くようにした。

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onLoadResource(WebView view, String url) {
        // iframe内のリンクURLのときだけ処理する
        if (url.equals("http://in.iframe.url/")) {
            Uri uri = Uri.parse(url);
            Intent i = new Intent(Intent.ACTION_VIEW, uri);
            startActivity(i);
        }
    }
});




まとめ

為せば成る
きっとあなたの案件もなんとかなる!