Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Epoxy 介紹

Epoxy 介紹

Epoxy 介紹
Android Taipei 07/2017

Chien Shuo (Kros)

July 27, 2017
Tweet

More Decks by Chien Shuo (Kros)

Other Decks in Programming

Transcript

  1. Outline • What is Epoxy • When to use •

    How to use • Detail • Future • Summary
  2. What is Epoxy • Airbnb 開源專案 • ⽤用來來取代傳統 Adapter •

    可建立複雜的 RecyclerView Adapter • 內建儲存 view state, ⾃自動 diff
  3. Adapter with view type @Override
 public int getItemViewType(int position) {


    if (0 == position) {
 return TYPE_1;
 } else if (1 == position) {
 return TYPE_2;
 } else {
 return TYPE_3;
 }
 } @Override
 public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
 switch (viewType) {
 case TYPE_1: case TYPE_2: case TYPE_3: } }
  4. Epoxy Controller • The EpoxyController encourages usage similar to the

    popular Model-View-ViewModel and Model-View-Intent patterns. • 類似現在的 Model-View-ViewMode 改念念,把建立 RecyclerView Items 的邏輯放在 Controller 裡⾯面
  5. Epoxy Controller • 所⾒見見即所得 @Override
 protected void buildModels() {
 add(new

    AvatarItemViewModel_());
 add(new BannerItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); } 在 EpoxyController 中使⽤用的⽅方式
  6. Epoxy Controller • 所⾒見見即所得 @Override
 protected void buildModels() {
 add(new

    AvatarItemViewModel_());
 add(new BannerItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); }
  7. Epoxy Controller • 所⾒見見即所得 @Override
 protected void buildModels() {
 add(new

    AvatarItemViewModel_());
 add(new BannerItemViewModel_()); add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); }
  8. Epoxy Controller • 所⾒見見即所得 @Override
 protected void buildModels() {
 add(new

    AvatarItemViewModel_());
 add(new BannerItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); }
  9. Epoxy Controller • 所⾒見見即所得 @Override
 protected void buildModels() {
 add(new

    AvatarItemViewModel_());
 add(new BannerItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); }
  10. Epoxy Controller • 所⾒見見即所得 @Override
 protected void buildModels() {
 add(new

    AvatarItemViewModel_());
 add(new BannerItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); }
  11. Epoxy Controller • 所⾒見見即所得 @Override
 protected void buildModels() {
 add(new

    BannerItemViewModel_()); add(new AvatarItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); } 加入順序 == 顯⽰示順序
  12. Epoxy Controller • 所⾒見見即所得 @Override
 protected void buildModels() {
 add(new

    BannerItemViewModel_()); add(new AvatarItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); add(new SettingsItemViewModel_());
 add(new AdItemViewModel_()); } 新增 Item 非常容易易
  13. How to use • 使⽤用三步驟 • 建立 EpoxyController • 建立

    EpoxyModel • 設定 RecyclerView 的 Adapter
  14. Detail 
 private void setUpController() { // ...
 controller =

    new MyEpoxyController(); // ... } // 繼承 EpoxyController public class MyEpoxyController extends EpoxyController { }
  15. Epoxy Model • Epoxy uses EpoxyModel objects to know which

    views to display and how to bind data to them. This is similar to the popular ViewModel pattern. You should subclass EpoxyModel to specify what layout your model uses and how to bind data to that view. • EpoxyModel 裡設定 View 為何,需顯⽰示的 Data 為何,有點像現在的 ViewModel 概念念
  16. View Annotations • 建立 View • 設定 Annotation • 設定

    Property (為了了 equals and hashCode) • 就會⾃自動產⽣生 EpoxyModel 了了!
  17. SettingsItemViewModel @Override
 protected void buildModels() {
 add(new AvatarItemViewModel_());
 add(new BannerItemViewModel_());


    add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_());
 add(new SettingsItemViewModel_()); }
  18. 
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView) TextView titleTextView;


    
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 
 }
  19. @ModelView(defaultLayout = R.layout.model_settings_item_view)
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView)

    TextView titleTextView;
 
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 
 }
  20. @ModelView(defaultLayout = R.layout.model_settings_item_view)
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView)

    TextView titleTextView;
 
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 
 } 指定 Model 要 Inflate 的 View 為何
  21. @ModelView(defaultLayout = R.layout.model_settings_item_view)
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView)

    TextView titleTextView;
 
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 
 }
  22. @ModelView(defaultLayout = R.layout.model_settings_item_view)
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView)

    TextView titleTextView;
 
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 @ModelProp
 public void setTitle(@StringRes int resId) {
 titleTextView.setText(resId);
 }
 
 @ModelProp(options = Option.DoNotHash)
 public void setClickListener(@Nullable OnClickListener clickListener) {
 this.setOnClickListener(clickListener);
 }
 }
  23. @ModelView(defaultLayout = R.layout.model_settings_item_view)
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView)

    TextView titleTextView;
 
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 @ModelProp
 public void setTitle(@StringRes int resId) {
 titleTextView.setText(resId);
 }
 
 @ModelProp(options = Option.DoNotHash)
 public void setClickListener(@Nullable OnClickListener clickListener) {
 this.setOnClickListener(clickListener);
 }
 } 設定 Property 讓 View 顯⽰示對應的資料
  24. @ModelView(defaultLayout = R.layout.model_settings_item_view)
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView)

    TextView titleTextView;
 
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 @ModelProp
 public void setTitle(@StringRes int resId) {
 titleTextView.setText(resId);
 }
 
 @ModelProp(options = Option.DoNotHash)
 public void setClickListener(@Nullable OnClickListener clickListener) {
 this.setOnClickListener(clickListener);
 }
 } 設定 ClickListener
  25. @ModelView(defaultLayout = R.layout.model_settings_item_view)
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView)

    TextView titleTextView;
 
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 @ModelProp
 public void setTitle(@StringRes int resId) {
 titleTextView.setText(resId);
 }
 
 @ModelProp(options = Option.DoNotHash)
 public void setClickListener(@Nullable OnClickListener clickListener) {
 this.setOnClickListener(clickListener);
 }
 } Interface 沒有 equal and hash 所以使⽤用 Option.DoNotHash 標記
  26. @ModelView(defaultLayout = R.layout.model_settings_item_view)
 public class SettingsItemView extends LinearLayout {
 @BindView(R.id.titleTextView)

    TextView titleTextView;
 
 public SettingsItemView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 
 private void init() {
 inflate(getContext(), R.layout.view_settings_item, this);
 ButterKnife.bind(this);
 }
 
 @ModelProp
 public void setTitle(@StringRes int resId) {
 titleTextView.setText(resId);
 }
 
 @ModelProp(options = Option.DoNotHash)
 public void setClickListener(@Nullable OnClickListener clickListener) {
 this.setOnClickListener(clickListener);
 }
 }
  27. Using EpoxyModel public class SimpleController extends EpoxyController { 
 @Override


    protected void buildModels() {
 add(new SettingsItemViewModel_()
 .id(3)
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 );
 } }
  28. Using EpoxyModel public class SimpleController extends EpoxyController { 
 @Override


    protected void buildModels() {
 add(new SettingsItemViewModel_()
 .id(3)
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 );
 } } ⼿手動 new ⼀一個 EpoxyModel
  29. Using EpoxyModel public class SimpleController extends EpoxyController { 
 @Override


    protected void buildModels() {
 add(new SettingsItemViewModel_()
 .id(3)
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 );
 } } ⾃自動產⽣生的 EpoxyModel
  30. Using EpoxyModel public class SimpleController extends EpoxyController { 
 @Override


    protected void buildModels() {
 add(new SettingsItemViewModel_()
 .id(3)
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 );
 } } 透過 EpoxyController 的 method 加入到 Controller 裡
  31. Using EpoxyModel public class SimpleController extends EpoxyController { 
 @Override


    protected void buildModels() {
 add(new SettingsItemViewModel_()
 .id(3)
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 );
 } } 若若是⼿手動建立 要設定 Unique ID
  32. Using EpoxyModel public class SimpleController extends EpoxyController { 
 @Override


    protected void buildModels() {
 add(new SettingsItemViewModel_()
 .id(3)
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 );
 } } 剛剛設定的 Properties
  33. Using EpoxyModel public class SimpleController extends EpoxyController { @AutoModel SettingsItemViewModel_

    settingsItemViewModel1; 
 @Override
 protected void buildModels() {
 
 }
 } 使⽤用 Annotation
  34. Using EpoxyModel public class SimpleController extends EpoxyController { @AutoModel SettingsItemViewModel_

    settingsItemViewModel1; 
 @Override
 protected void buildModels() {
 settingsItemViewModel1
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 .addTo(this);
 }
 }
  35. Using EpoxyModel public class SimpleController extends EpoxyController { @AutoModel SettingsItemViewModel_

    settingsItemViewModel1; 
 @Override
 protected void buildModels() {
 settingsItemViewModel1
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 .addTo(this);
 }
 } 透過 Annotation 建立的 EpoxyModel 會⾃自動設定 Unique ID
  36. Using EpoxyModel public class SimpleController extends EpoxyController { @AutoModel SettingsItemViewModel_

    settingsItemViewModel1; 
 @Override
 protected void buildModels() {
 settingsItemViewModel1
 .title(R.string.settings_1)
 .clickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
 
 }
 })
 .addTo(this);
 }
 } 加入到 Controller 裡
  37. public class ListController extends TypedEpoxyController<List<Comment>> {
 
 
 
 


    @Override
 protected void buildModels(List<Comment> data) {
 
 
 }
 }
  38. public class ListController extends TypedEpoxyController<List<Comment>> {
 
 @AutoModel HeaderItemViewModel_ headerItemViewModel;


    @AutoModel FooterItemViewModel_ footerItemViewModel;
 
 @Override
 protected void buildModels(List<Comment> data) {
 
 headerItemViewModel
 .title(R.string.header_1)
 .addTo(this);
 
 
 
 footerItemViewModel
 .title(R.string.footer_1)
 .addTo(this);
 }
 }
  39. public class ListController extends TypedEpoxyController<List<Comment>> {
 
 @AutoModel HeaderItemViewModel_ headerItemViewModel;


    @AutoModel FooterItemViewModel_ footerItemViewModel;
 
 @Override
 protected void buildModels(List<Comment> data) {
 
 headerItemViewModel
 .title(R.string.header_1)
 .addTo(this);
 
 for (Comment comment : data) {
 new CommentItemModel_()
 .id(comment.getId())
 .comment(comment.getComment())
 .clickListener(//...)
 })
 .addTo(this);
 }
 
 footerItemViewModel
 .title(R.string.footer_1)
 .addTo(this);
 }
 }
  40. Using EpoxyHolder @EpoxyModelClass(layout = R.layout.model_comment_item_view)
 public abstract class CommentItemModel extends

    EpoxyModelWithHolder<CommentItemHolder> {
 @EpoxyAttribute String comment;
 @EpoxyAttribute(DoNotHash) OnClickListener clickListener;
 
 @Override
 public void bind(CommentItemHolder holder) {
 holder.itemView.setOnClickListener(clickListener);
 holder.commentTextView.setText(comment);
 }
 
 @Override
 public void unbind(CommentItemHolder holder) {
 holder.itemView.setOnClickListener(null);
 }
 
 public static class CommentItemHolder extends BaseEpoxyHolder {
 @BindView(R.id.itemView) View itemView;
 @BindView(R.id.commentTextView) TextView commentTextView;
 }
 }
  41. Using EpoxyHolder @EpoxyModelClass(layout = R.layout.model_comment_item_view)
 public abstract class CommentItemModel extends

    EpoxyModelWithHolder<CommentItemHolder> {
 @EpoxyAttribute String comment;
 @EpoxyAttribute(DoNotHash) OnClickListener clickListener;
 
 @Override
 public void bind(CommentItemHolder holder) {
 holder.itemView.setOnClickListener(clickListener);
 holder.commentTextView.setText(comment);
 }
 
 @Override
 public void unbind(CommentItemHolder holder) {
 holder.itemView.setOnClickListener(null);
 }
 
 public static class CommentItemHolder extends BaseEpoxyHolder {
 @BindView(R.id.itemView) View itemView;
 @BindView(R.id.commentTextView) TextView commentTextView;
 }
 } 繼承 EpoxyModelWithHolder 繼承 BaseEpoxyHolder https://github.com/airbnb/epoxy/wiki/Epoxy-Models#view-holders
  42. Using EpoxyHolder @EpoxyModelClass(layout = R.layout.model_comment_item_view)
 public abstract class CommentItemModel extends

    EpoxyModelWithHolder<CommentItemHolder> {
 @EpoxyAttribute String comment;
 @EpoxyAttribute(DoNotHash) OnClickListener clickListener;
 
 @Override
 public void bind(CommentItemHolder holder) {
 holder.itemView.setOnClickListener(clickListener);
 holder.commentTextView.setText(comment);
 }
 
 @Override
 public void unbind(CommentItemHolder holder) {
 holder.itemView.setOnClickListener(null);
 }
 
 public static class CommentItemHolder extends BaseEpoxyHolder {
 @BindView(R.id.itemView) View itemView;
 @BindView(R.id.commentTextView) TextView commentTextView;
 }
 } 類似 ViewHolder 的 lifecycle
  43. public class ListController extends TypedEpoxyController<List<Comment>> {
 
 @AutoModel HeaderItemViewModel_ headerItemViewModel;


    @AutoModel FooterItemViewModel_ footerItemViewModel;
 
 @Override
 protected void buildModels(List<Comment> data) {
 
 headerItemViewModel
 .title(R.string.header_1)
 .addTo(this);
 
 for (Comment comment : data) {
 new CommentItemModel_()
 .id(comment.getId())
 .comment(comment.getComment())
 .clickListener(//...)
 })
 .addTo(this);
 }
 
 footerItemViewModel
 .title(R.string.footer_1)
 .addTo(this);
 }
 }
  44. public class ListController extends TypedEpoxyController<List<Comment>> {
 
 @AutoModel HeaderItemViewModel_ headerItemViewModel;


    @AutoModel FooterItemViewModel_ footerItemViewModel; 
 @Override
 protected void buildModels(List<Comment> data) {
 
 headerItemViewModel
 .title(R.string.header_1)
 .addTo(this);
 
 for (Comment comment : data) {
 new CommentItemModel_()
 .id(comment.getId())
 .comment(comment.getComment())
 .clickListener(//...)
 })
 .addTo(this);
 }
 
 loadMoreViewModel
 .addTo(true, this);
 }
 }
  45. public class ListController extends TypedEpoxyController<List<Comment>> {
 
 @AutoModel HeaderItemViewModel_ headerItemViewModel;


    @AutoModel FooterItemViewModel_ footerItemViewModel; 
 @Override
 protected void buildModels(List<Comment> data) {
 
 headerItemViewModel
 .title(R.string.header_1)
 .addTo(this);
 
 for (Comment comment : data) {
 new CommentItemModel_()
 .id(comment.getId())
 .comment(comment.getComment())
 .clickListener(//...)
 })
 .addTo(this);
 }
 
 loadMoreViewModel
 .addTo(true, this);
 }
 } Add with condition, 可以設定條件(true/false) 決定是否要加入到 Controller
  46. public class ListController extends TypedEpoxyController<List<Comment>> {
 
 @AutoModel HeaderItemViewModel_ headerItemViewModel;


    @AutoModel FooterItemViewModel_ footerItemViewModel; 
 @Override
 protected void buildModels(List<Comment> data) {
 
 headerItemViewModel
 .title(R.string.header_1)
 .addTo(this);
 
 for (Comment comment : data) {
 new CommentItemModel_()
 .id(comment.getId())
 .comment(comment.getComment())
 .clickListener(//...)
 })
 .addTo(this);
 }
 
 loadMoreViewModel
 .addTo(false, this);
 }
 }
  47. Other Features • EpoxyModelGroup • Saving View state • Payloads

    • Data binding layouts • Litho Components • Kotlin Support • Grid Suport
  48. Summary • 若若 RecyclerView 有兩兩種以上的 view type,可以考慮⽤用 Epoxy • 想要動態增減

    RecyclerView 內容,可以考慮⽤用 Epoxy • ScrollView 太長,可以考慮⽤用 Epoxy • 需要 Header, Footer,可以考慮⽤用 Epoxy • 不想⾃自⼰己實作 diff, save state,可以考慮⽤用 Epoxy