Intermediate Level Data Binding

Intermediate Level Data Binding

shibuya.apk #13

85cab5fdf09afe3ee78ce3667681915a?s=128

Keisuke Kobayashi

March 24, 2017
Tweet

Transcript

  1. Intermediate Level 
 Data Binding Keisuke Kobayashi / @kobakei shibuya.apk

    #13
  2. About me • Keisuke Kobayashi • GitHub, Qiita: @kobakei •

    Twitter: @ksk_kbys • Android, Rails, React
  3. ຊൃදʹ͍ͭͯ • σʔλόΠϯσΟϯάॳڃऀ޲͚ʹɺҰาઌ΁ߦ͘࢖͍ํΛ ঺հ͠·͢ • ެࣜΨΠυʹࡌͬͯͳ͍಺༰΋ؚΈ·͢ • https://developer.android.com/topic/libraries/data- binding/index.html •

    ιʔείʔυ • https://github.com/kobakei/AndroidDataBindingSample
  4. ΞδΣϯμ • σʔλόΠϯσΟϯάͱ͸ • BindingAdapterͷ࢖͍ํ • Ϧετͱͷ࿈ܞ • ٧·Γͦ͏ͳϙΠϯτ

  5. Ξϯέʔτ

  6. σʔλόΠϯσΟϯάΛ
 ۀ຿Ͱ࢖ͬͯΔਓ✋

  7. @BindingAdapterΛ
 ࢖ͬͨ͜ͱ͕͋Δਓ✋

  8. @InverseBinding***Λ
 ࢖ͬͨ͜ͱ͕͋Δਓ✋

  9. ObservableListΛ
 ࢖ͬͨ͜ͱ͕͋Δਓ✋

  10. σʔλόΠϯσΟϯά

  11. Android data binding • σʔλΦϒδΣΫτͱUIΛඥ෇͚ɺ
 σʔλͷ஋ΛࣗಈతʹUIʹ൓өͨ͠Γɺ
 ϢʔβʔͷೖྗΛࣗಈతʹσʔλʹ൓ө͢Δ
 GoogleެࣜϥΠϒϥϦ

  12. app/build.gradle android { //... dataBinding { enabled true } }

  13. Data: ObservableField public class MainViewModel { public ObservableField<String> name =

    new ObservableField<>(); public ObservableBoolean visible = new ObservableBoolean(true); }
  14. Data: BaseObservable public class MainViewModel extends BaseObservable { private String

    name; @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } }
  15. Data: BaseObservable public class MainViewModel extends BaseObservable { private String

    name; @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } }
  16. Data: BaseObservable public class MainViewModel extends BaseObservable { private String

    name; @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } }
  17. Data -> UI <layout> <data> <import type=“android.view.View” /> <variable name="viewModel"

    type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name}" android:visibility="@{viewModel.visible ? View.VISIBLE : View.GONE}"/> </RelativeLayout> </layout>
  18. Data -> UI <layout> <data> <import type=“android.view.View” /> <variable name="viewModel"

    type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name}" android:visibility="@{viewModel.visible ? View.VISIBLE : View.GONE}"/> </RelativeLayout> </layout>
  19. <layout> <data> <import type=“android.view.View” /> <variable name="viewModel" type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout

    android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name}" android:visibility="@{viewModel.visible ? View.VISIBLE : View.GONE}"/> </RelativeLayout> </layout> Data -> UI <data></data>
  20. <layout> <data> <import type=“android.view.View” /> <variable name="viewModel" type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout

    android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name}" android:visibility="@{viewModel.visible ? View.VISIBLE : View.GONE}"/> </RelativeLayout> </layout> Data -> UI <variable> ࢀর͢ΔΦϒδΣΫτ
  21. <layout> <data> <import type=“android.view.View” /> <variable name="viewModel" type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout

    android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name}" android:visibility="@{viewModel.visible ? View.VISIBLE : View.GONE}"/> </RelativeLayout> </layout> Data -> UI
  22. <layout> <data> <import type=“android.view.View” /> <variable name="viewModel" type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout

    android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name}" android:visibility="@{viewModel.visible ? View.VISIBLE : View.GONE}"/> </RelativeLayout> </layout> Data -> UI ࢖༻͢ΔΫϥεΛΠϯϙʔτ
 ʢjava.lang.* ͸ෆཁʣ
  23. <layout> <data> <import type=“android.view.View” /> <variable name="viewModel" type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout

    android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name}" android:visibility="@{viewModel.visible ? View.VISIBLE : View.GONE}"/> </RelativeLayout> </layout> Data -> UI
  24. Inflate layout @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //

    Before // setContentView(R.layout.main_activity); // After MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity); MainViewModel viewModel = new MainViewModel(); binding.setViewModel(viewModel); }
  25. ૒ํ޲
 σʔλόΠϯσΟϯά

  26. UI -> Data <data> <variable name="viewModel" type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout android:layout_width="match_parent"

    android:layout_height="match_parent"> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={viewModel.name}"/> </RelativeLayout>
  27. UI -> Data <data> <variable name="viewModel" type="io.github.kobakei.dbsample.MainViewModel"/> </data> <RelativeLayout android:layout_width="match_parent"

    android:layout_height="match_parent"> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={viewModel.name}"/> </RelativeLayout>
  28. ࢖͑ΔଐੑϦετ • ओͳ΋ͷ͸େମఆٛ͞Ε͍ͯΔ • http://qiita.com/kobakei/items/ 5e902dd24005e6768ac4 • setHoge͸ɺapp:hoge=“@{…}” ͱॻ͚Δ •

    SwipeRefreshLayout#setRefreshing
 -> app:refreshing=“@{viewModel.refreshing}”
  29. BindingAdapterͷ
 ࢖͍ํ

  30. BindingAdapter • σʔλόΠϯσΟϯάͰ࢖͑ΔଐੑΛࣗ෼Ͱ ఆٛͰ͖Δ • set, get྆ํՄೳ

  31. Lv1. ImageViewʹ
 αʔόʔ্ͷը૾Λදࣔ͢Δ

  32. JavaͰී௨ʹॻ͘ͱ ImageView imageView = (ImageView) findViewById(R.id.imageView); Picasso.with(context).load(url).into(imageView);

  33. BindingAdapter public class BindingAdapterUtil { @BindingAdapter(value = {"imageUrl"}) public static

    void setImageUrl(ImageView imageView, String url) { Picasso.with(context).load(url).into(imageView); } }
  34. BindingAdapter public class BindingAdapterUtil { @BindingAdapter(value = {"imageUrl"}) public static

    void setImageUrl(ImageView imageView, String url) { Picasso.with(context).load(url).into(imageView); } } XMLଐੑ໊
  35. XML <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" app:imageUrl="@{viewModel.url}"/>

  36. Lv2: TextViewʹଧͪফ͠ઢ Λ෇͚Δ

  37. JavaͰී௨ʹॻ͘ͱ TextView textView = (TextView) findViewById(R.id.textView); TextPaint paint = textView.getPaint();

    paint.setFlags(TextPaint.STRIKE_THRU_TEXT_FLAG); paint.setAntiAlias(true);
  38. BindingAdapter public class BindingAdapterUtil { @BindingAdapter(value = {"strike"}) public static

    void setTextStrike(TextView textView, boolean strike) { if (strike) { TextPaint paint = textView.getPaint(); paint.setFlags(TextPaint.STRIKE_THRU_TEXT_FLAG); paint.setAntiAlias(true); } } }
  39. XML <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={viewModel.name}" app:strike="@{true}"/>

  40. XML <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={viewModel.name}" app:strike="@{true}"/> @{}ͰғΉඞཁ͋Γ

  41. ͋ΕΕʔʁ σʔλΛόΠϯσΟϯάͯ͠ ͳ͍ͧʔʁ

  42. ϝϞ • BindingAdapter͸ɺ
 ୯ʹXMLͷଐੑͷ௥Ճʹ΋࢖͑Δ • ࠓ·ͰJavaͰॻ͍ͯͨϏϡʔૢ࡞Λɺ
 ڞ௨Խͯ͠XMLʹԡ͠ग़ͤΔ • @{}ͰғΉͷΛ͓๨Εͳ͘

  43. Lv3: RecyclerViewͷ
 εΫϩʔϧΠϕϯτ

  44. JavaͰී௨ʹॻ͘ͱ RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override

    public void onScrollStateChanged(RecyclerView recyclerView, int newState) { // do something } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { // do something } });
  45. BindingAdapter (1) public interface OnScrollStateChanged { void onScrollStateChanged(RecyclerView recyclerView, int

    newState); } public interface OnScrolled { void onScrolled(RecyclerView recyclerView, int dx, int dy); } 1 method 1 interface
  46. BindingAdapter (2) @BindingAdapter(value = {"onScrollStateChanged", "onScrolled"}, requireAll = false) public

    static void setRecyclerViewScroll(RecyclerView recyclerView, final OnScrollStateChanged onScrollStateChanged, final OnScrolled onScrolled) { recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (onScrollStateChanged != null) { onScrollStateChanged.onScrollStateChanged(recyclerView, newState); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (onScrolled != null) { onScrolled.onScrolled(recyclerView, dx, dy); } } }); }
  47. BindingAdapter (2) @BindingAdapter(value = {"onScrollStateChanged", "onScrolled"}, requireAll = false) public

    static void setRecyclerViewScroll(RecyclerView recyclerView, final OnScrollStateChanged onScrollStateChanged, final OnScrolled onScrolled) { recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (onScrollStateChanged != null) { onScrollStateChanged.onScrollStateChanged(recyclerView, newState); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (onScrolled != null) { onScrolled.onScrolled(recyclerView, dx, dy); } } }); }
  48. BindingAdapter (2) @BindingAdapter(value = {"onScrollStateChanged", "onScrolled"}, requireAll = false) public

    static void setRecyclerViewScroll(RecyclerView recyclerView, final OnScrollStateChanged onScrollStateChanged, final OnScrolled onScrolled) { recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (onScrollStateChanged != null) { onScrollStateChanged.onScrollStateChanged(recyclerView, newState); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (onScrolled != null) { onScrolled.onScrolled(recyclerView, dx, dy); } } }); } ݸผʹ࢖༻ՄೳʢσϑΥϧτ͸trueʣ
  49. XML <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" app:onScrolled="@{viewModel::onScrolled}"/>

  50. Data class public class MainViewModel { public void onScrolled(RecyclerView recyclerView,

    int dx, int dy) { // do something } }
  51. Lv4: Multichoice ListViewͷ νΣοΫҐஔΛऔಘ

  52. None
  53. JavaͰී௨ʹॻ͘ͱ ListView listView = (ListView) findViewById(R.id.listView); SparseBooleanArray positions = listView.getCheckedItemPositions();

  54. InverseBindingMethod @InverseBindingMethods({ @InverseBindingMethod( type = ListView.class, attribute = "checkedItemPositions", event

    = “checkedItemPositionsAttrChanged", method = "getCheckedItemPositions" ) }) public class BindingAdapterUtil { // ... }
  55. InverseBindingMethod @InverseBindingMethods({ @InverseBindingMethod( type = ListView.class, attribute = "checkedItemPositions", event

    = “checkedItemPositionsAttrChanged", method = "getCheckedItemPositions" ) }) public class BindingAdapterUtil { // ... } σʔλ͕ߋ৽͞ΕΔΠϕϯτ໊
  56. InverseBindingMethod @InverseBindingMethods({ @InverseBindingMethod( type = ListView.class, attribute = "checkedItemPositions", event

    = “checkedItemPositionsAttrChanged", method = "getCheckedItemPositions" ) }) public class BindingAdapterUtil { // ... } σʔλऔಘͰݺ͹ΕΔϝιου
  57. InverseBindingMethod @InverseBindingMethods({ @InverseBindingMethod( type = ListView.class, attribute = "checkedItemPositions" )

    }) public class BindingAdapterUtil { // ... } event, method͸লུՄೳ
  58. BindingAdapter: setter @BindingAdapter(value = "checkedItemPositions") public static void setListViewCheckedItemPositions(ListView listView,

    SparseBooleanArray positions) { if (positions != null) { for (int i = 0; i < positions.size(); i++) { listView.setItemChecked(positions.keyAt(i), positions.valueAt(i)); } } }
  59. BindingAdapter: setter @BindingAdapter(value = "checkedItemPositions") public static void setListViewCheckedItemPositions(ListView listView,

    SparseBooleanArray positions) { if (positions != null) { for (int i = 0; i < positions.size(); i++) { listView.setItemChecked(positions.keyAt(i), positions.valueAt(i)); } } } InverseBindingMethodʹҰக
  60. BindingAdapter: event @BindingAdapter(value = "checkedItemPositionsAttrChanged") public static void setCheckedItemPositionsAttrChanged(ListView listView,

    final InverseBindingListener listener) { listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (listener != null) { listener.onChange(); } } }); }
  61. BindingAdapter: event @BindingAdapter(value = "checkedItemPositionsAttrChanged") public static void setCheckedItemPositionsAttrChanged(ListView listView,

    final InverseBindingListener listener) { listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (listener != null) { listener.onChange(); } } }); } InverseBindingMethodʹҰக
  62. BindingAdapter: event @BindingAdapter(value = "checkedItemPositionsAttrChanged") public static void setCheckedItemPositionsAttrChanged(ListView listView,

    final InverseBindingListener listener) { listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (listener != null) { listener.onChange(); } } }); } ߲໨ΫϦοΫ࣌ʹ InverseBindingListenerΛ ݺͼग़͢
  63. XML <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" android:choiceMode="multipleChoice" app:checkedItemPositions="@={viewModel.checkedItemPositions}"/>

  64. Data class public class ListChoiceActivityViewModel { public ObservableList<Item> items; public

    ObservableField<SparseBooleanArray> checkedItemPositions; public ListChoiceActivityViewModel() { this.items = new ObservableArrayList<>(); this.checkedItemPositions = new ObservableField<>(); } }
  65. Data bindingͱListͷ࿈ܞ

  66. ΍Γ͍ͨ͜ͱ • Ϧετͷཁૉ͕૿ݮͨ͠ΒɺListView / RecyclerViewͷཁૉ਺΋࿈ಈͯ͠ཉ͍͠

  67. ObservableList • ObservableFieldͷϦετ൛ • ཁૉ਺มԽͳͲͷΠϕϯτΛड͚औΔϦεφ Λొ࿥Ͱ͖Δ • Πϯελϯε࡞੒࣌ʹ͸ɺ ObservableArrayListΛ࢖͏

  68. ListActivityViewModel public class ListActivityViewModel { public ObservableList<Item> items = new

    ObservableArrayList(); }
  69. class ObservableRecyclerAdapter extends RecyclerView.Adapter<ViewHolder> { private final Context context; private

    final ObservableList<Item> items; ObservableRecyclerAdapter(Context context, ObservableList<Item> items) { this.context = context; this.items = items; // Add listener to ObservableList items.addOnListChangedCallback(new ObservableList.OnListChangedCallback<ObservableList<Item>>() { @Override public void onChanged(ObservableList<Item> items) { notifyDataSetChanged(); } @Override public void onItemRangeChanged(ObservableList<Item> items, int i, int i1) { notifyItemRangeChanged(i, i1); } @Override public void onItemRangeInserted(ObservableList<Item> items, int i, int i1) { notifyItemRangeInserted(i, i1); } @Override public void onItemRangeMoved(ObservableList<Item> items, int i, int i1, int i2) { notifyItemMoved(i, i1) } @Override public void onItemRangeRemoved(ObservableList<Item> items, int i, int i1) { notifyItemRangeRemoved(i, i1); } }); } //... }
  70. class ObservableRecyclerAdapter extends RecyclerView.Adapter<ViewHolder> { private final Context context; private

    final ObservableList<Item> items; ObservableRecyclerAdapter(Context context, ObservableList<Item> items) { this.context = context; this.items = items; // Add listener to ObservableList items.addOnListChangedCallback(new ObservableList.OnListChangedCallback<ObservableList<Item>>() { @Override public void onChanged(ObservableList<Item> items) { notifyDataSetChanged(); } @Override public void onItemRangeChanged(ObservableList<Item> items, int i, int i1) { notifyItemRangeChanged(i, i1); } @Override public void onItemRangeInserted(ObservableList<Item> items, int i, int i1) { notifyItemRangeInserted(i, i1); } @Override public void onItemRangeMoved(ObservableList<Item> items, int i, int i1, int i2) { notifyItemMoved(i, i1) } @Override public void onItemRangeRemoved(ObservableList<Item> items, int i, int i1) { notifyItemRangeRemoved(i, i1); } }); } //... } มߋΠϕϯτൃੜ࣌ʹɺAdapterͷnotifyΛݺͿ
  71. ObservableMap (ObservableArrayMap) • Ϛοϓ൛ • ࢖ͬͨ͜ͱͳ͍ͷͰ঺հ͸ׂѪ

  72. ͦͷଞॳ৺ऀ͕٧·Γͦ͏ͳ ϙΠϯτ

  73. UIͷߋ৽λΠϛϯά • ஋ͷมߋͷ࣍ͷϑϨʔϜʹͳΔ • ଈ࣌Ͱߋ৽͍ͨ͠৔߹ɺ ViewDataBinding#executePendingBindings() ΛݺͿ

  74. εϨου • ίϨΫγϣϯҎ֎͸ɺσʔλϞσϧΛόοΫ άϥ΢ϯυεϨουͰมߋՄೳ • ObservableList, ObservableMap͸஫ҙ

  75. ·ͱΊ

  76. ·ͱΊ • BindingAdapterͷ࢖͍ํΛ঺հͨ͠ • RecyclerViewͱObservableListͷ૊Έ߹Θͤ ํΛ঺հͨ͠ • Let's try data

    binding
  77. Thanks!