読者です 読者をやめる 読者になる 読者になる

今更遅れてDataBinding事始め(5)[コピペ用]

いままでのまとめ

現在通常の開発で data-bindingを使っていますが、今回は作業をする上でのチートシート的なものをまとめておこうかと

なんでここらへんのコピペシートが必要なの?

  • data-bindingは、
    • layout.xmlベースでエラーになった時、一切の補完が効かなくなるため
    • 変数やクラスのスペリングミスで、補完&プロジェクトが全て真っ赤になる(R.XXX 系が全滅)
    • APTを下位に使っているので、それを利用しているライブラリ型はすべて生成エラーになる
      • APTのコードを毎回生成し直しているので、致命的に遅くなっている原因な気がする*1

アプリの構造的設計な話

自分の場合は

no 役割 対応クラス/ファイル 具体的な操作
1 Controler Activity/Fragment/ListAdapter RestAPIで一覧等を取得
2 View layout.xml 値のマッピング、表示
3 ViewModel Utilクラス★ 単純マッピングできない表示、挙動の加工

★に関しては、

  • BindingAdapter を記述しているクラス
    • 画像読み込み関数*2
    • layout.xml上では書けないような複雑な文字列操作
    • Widgetのattlibute属性を弄るもの
  • 通信やDB保存を隠蔽するManger

普通のQittaとかの例のように、それ専用の ViewModelクラスを作ってそれぞれに書く必要はない

自分の場合だと

  • CommonAdapterみたいな一個のクラスに BindingAdapter の関数を箇条書きにしています。
    • 理由としてはdata-bindingのクラスにおいてバラバラに書いても全ソースファイルスキャンされるから
    • 逆にバラして書くと若干コンパイルが遅くなる

build.grade 編

  • app/build/gralde
android{
    dataBinding{
        enabled = true
    }
}

dependencies{
    debugCompile 'com.android.support:multidex:1.0.1'
}

layout.xml

  • 基本的に値代入しかできない
  • 式判定は、Boolean判定がベター
  • 複雑な判定式、処理はBindingAdapterで対応する

import文の定番

<layout>
  <data>
     <import type="android.view.View"/>
     <import type="android.text.TextUtils"/>
  </data>   

namespaceの定番

  • layoutの行で下記のワードタイピングで挿入
    • appNs
    • toolsNs
<layout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto" //★
  xmlns:tools="http://schemas.android.com/tools">     //★

bind対象の変数宣言

<layout>
  <data>
    <variable name="model"
      type="com.twitter.sdk.android.core.models.Tweet" />
  </data>

判定式の書き方

  • 複雑な判定式を書こうとするとlayout解釈で失敗しやすい
  • どうしても書きたい場合はBindingAdapterの方で定義して作業する
<ImageView 
    android:visibility="@{TextUtils.isEmpty(model.imageUrl) ? View.GONE:View.VISIBLE}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:imageUrl="@{model.imageUrl}" />

includeレイアウト

  • include先も勿論data-bindingのレイアウトであること
  • idをふらないとinclude先のレイアウトにアクセスできない
  • 単純に表示だけであれば、NGパターンでも問題ないが、その用途だとそもそも受け渡すのがいらない気がする

  • NGパターン

<include
    layout="@layout/include_item_article"
    app:article="@{article}"/>
  • OKパターン
<include
   android:id="@+id/item_article"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    layout="@layout/include_item_article"
    app:article="@{article}"/>

idを指定した時点で、layout_width/layout_height の指定が強制される


BindingAdapter編

  • 一時期 “bind:imageUrl” みたいな書き方が書けたが、現在はLintエラーにされる

引数一つ

  • 関数自体は

    • TargetView
    • 引数1
  • CommonAdapter.java

@BindingAdapter("imageUrl")
public static void loadImage(ImageView view, String url) {
   Picasso.with(view.getContext()).load(url).into(view);
}
<ImageView 
    android:visibility="@{TextUtils.isEmpty(model.imageUrl) ? View.GONE:View.VISIBLE}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:imageUrl="@{model.imageUrl}" />

引数2つ

  • 関数自体は

    • TargetView
    • 引数1
    • 引数2
  • CommonAdapter.java

@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView 
    android:visibility="@{TextUtils.isEmpty(model.imageUrl) ? View.GONE:View.VISIBLE}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:imageUrl="@{model.imageUrl}" 
    app:error="@{ @drawable/ic_error_24dp }" //use material icon
/>

自分がよく追加しているやつ

  • CommonAdapter.java

  • 画像読込処理

    @BindingAdapter("imageUrl")
    public static void loadImage(final ImageView view, final String url) {
        Log.d(TAG,"loadImage=" + url);
        Glide.with(view.getContext()).load(url).placeholder(R.drawable.c_3d_rotation_black_24dp).into(view);
    }

   @BindingAdapter("drawableLeft")
    public static void drawableLeft(final TextView view ,final  String url){
        if(TextUtils.isEmpty(url)){
            return;
        }
        Log.d(TAG,"drawableLeft=" + url);

        final Context context = view.getContext();
        Glide.with(context)
                .load(url)
                .asBitmap()
                .into(new SimpleTarget<Bitmap>(150, 150) {
                    @Override
                    public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                        view.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(context.getResources(),bitmap),null,null,null);
                    }

                    @Override
                    public void onLoadFailed(Exception e, Drawable errorDrawable) {
                        super.onLoadFailed(e, errorDrawable);
                    }
                });

    }

  • 汎用的な処理

@BindingAdapter("background")
public static void setBackground(View view, Drawable Drawable) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        view.setBackground(drawable);
    } else {
        view.setBackgroundDrawable(drawable);
    }
}

@BindingAdapter("tint")
public static void setTint(View view, int color) {
    Drawable Drawable = view,getBackground();
    DrawableCompat.wrap(drawable);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        DrawableCompat.setTint(drawable, color);
        DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_IN);
    } else {
        drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
    }
    setBackground(view,drawable);
}
<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:tint="@{@color/colorRed}" />

ちなみに

の話は後で知りました(汗

TextViewのtintだと大変なんだな・・ 自分はImageViewとかでしか使ったことないです

使って便利だったやつ

twitter kit の com.twitter.sdk.android.core.models.Tweet って

なぜかメンバーがすべてfinalでして、表示の加工をするためには @BindingAdapter が必須でした

を抽出後リンクを貼る

  • CommonAdapter.java
@BindingAdapter("message")
public static void setMessage(TextView textView, Tweet tweet) {
    final Context context = textView.getContext();
    String text = tweet.text;
    
        final SparseArray<String> links = new SparseArray<String>();

        //=== ハッシュタグの抽出処理 ====
        int cnt = 0;
        if(text.indexOf("#") != -1) {
            int len = text.length();

            int st = 0;
            while (st < len) {
                st = text.indexOf("#", st);
                if (st == -1) {
                    break;
                }
                int ed = text.indexOf(" ", st);
                String tag = ed == -1 ? text.substring(st) : text.substring(st, ed);
                ;
                links.append(cnt, tag);
                cnt++;
                st = ed;
                if (ed == -1) {
                    break;
                }
            }
     }

    OnLinkClickListener listener = new OnLinkClickListener() {
        @Override
        public void onLinkClick(int textId) {
            String tag = links.get(textId);
            if(tag.startWith("#")){
                tag = tag.substring(1);
                String tweetUrl = "twitter://search?query=%23" + tag;
                Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(tweetUrl));
                context.startActivity(i);
            }
            
        }
    };

    textView.setText(TextLinker.getLinkableText(text, links, listener));
    textView.setMovementMethod(LinkMovementMethod.getInstance());
}

まあ投稿するなら下記なんでしょうけど、twitter連携 で検索するとこちらばかり引っかかるんだよな。。

http://twitter.com/share?text=[共有したいテキスト]
https://twitter.com/intent/tweet?hashtags=[#なしのハッシュタグ]
Parameter Description Example
text Pre-populated text highlighted in the Tweet composer. custom share text
url URL included with the Tweet. https://dev.twitter.com/web/tweet-button
hashtags A comma-separated list of hashtags to be appended to default Tweet text. example,demo
via Attribute the source of a Tweet to a Twitter username. twitterdev
related A comma-separated list of accounts related to the content of the shared URI. twitterapi,twitter

通信やDB保存を隠蔽するManger

これは次回に回します

*1:ADTのときは編集したソースしか変換しなかったのにorz

*2:Picasso/Glide