Android MultiType第三方庫的基本使用和案例+DiffUtil的簡單用法


1.MultiType簡單介紹

1.1.MultiType用於比較復雜的頁面。

  如下圖,今日頭條用到了MultiType處理各種復雜的頁面。

    

  這種還是比較簡單的類型。因為一個頁面也就這種類型。

  下面看看這個頁面。

  

  這個就比較復雜了,所以這時候MultiType的作用就體現出來了。

  一個頁面用一個單獨的RecyclerView就可以實現。

 

  再比如微博列表頁面:

    

  有純文本的、代轉發原文的,帶圖片的、帶視頻的、帶文章的等等,甚至穿插一條可以橫向滑動的好友推薦條目。

  不同的item類型眾多,而且隨着業務發展,還會更多。

  如果我們使用傳統的開發方式,經常要做一些繁瑣的工作,代碼可能都堆積在一個Adapter中,我們需要復寫  

  RecyclerView.Adapter的getItemViewType方法,羅列一些type整型常量,並且ViewHolder轉型、綁定數據

  也比較麻煩。

  一旦產品需求有變,或者產品設計需要增加一種新的item類型,我們需要去代碼堆里找到我們原來的邏輯去修改,

  或者找到正確的位置去增加代碼。非常繁瑣。

 

  

1.2.現在有了MultiType,簡單來說,MultiType就是一個多類型列表視圖的中間分發框架,它能幫助你快速並且清晰

  地開發一些復雜的列表頁面。它本是為聊天頁面開發的,聊天頁面的消息類型也是有大量不同種類,並且新增頻繁

  而MultiType能夠輕松勝任,代碼模塊化,隨時可擴展新的類型進入列表當中。它內建了類型-View的復用池系統,

  支持RecyclerView,使用簡單靈活,令代碼清晰,適應需求變化。 

 

 

1.3.MultiType也能輕松實現如下頁面。

  

  在github中有相關頁面介紹。

 

1.4.github地址:https://github.com/drakeet/MultiType

    參考文章:Android 復雜的列表視圖新寫法。

  參考文章:Android的基本使用和復雜頁面的寫法實例。 


2.基本使用方法

2.1.引入MultiType==>在build.gradle中加入:

dependencies {
    compile 'me.drakeet.multitype:multitype:3.3.0'
}

  注意:MultiType不支持低於23.0.0的RecyclerView,不過現在基本都沒有用那么低版本的RecyclerView了吧。

 

2.2.小貼士:

  MultiType這個框架使用RecyclerView,但是不需要寫adapter,需要些ItemViewBinder,這是框架里面定義

  的一個類。在多Type的情況下,每一種item對應一個數據模型(一個bean類)+一個ItemViewBinder。

 

2.3.新建一個數據模型bean

public class Title {

    public String title;
    public String url;

}

 

2.4.新建一個類==>繼承ItemViewBinder的一個綁定類。

public class TitleViewBinder extends ItemViewBinder<Title, TitleViewBinder.TitleHolder> {

    @NonNull
    @Override
    protected TitleHolder onCreateViewHolder(@NonNull LayoutInflater inflater, 
                                @NonNull ViewGroup parent) { View view
= inflater.inflate(R.layout.layout_title, parent, false); return new TitleHolder(view); } @Override protected void onBindViewHolder(@NonNull TitleHolder holder, @NonNull Title item) { //填充數據渲染頁面,比如setText setImage等工作 } static class TitleHolder extends RecyclerView.ViewHolder { ImageView imageView; TextView textView; TitleHolder(View itemView) { super(itemView); imageView = (ImageView) itemView.findViewById(R.id.iv_title); textView = (TextView) itemView.findViewById(R.id.tv_title); } } }

  這里R.layout.layout_title,就是定義布局資源,要加載這個布局。

  這個布局中有R.id_iv_title+R.id_tv_title。

  返回的是一個自定義的視圖持有者==>TitleHolder

  這個視圖持有者是定義在這個視圖綁定類里面的類部類。

 

2.5.最后一步==>注冊綁定。

  在Activity中將類和ItemViewBinder注冊綁定。

  其余的工作就和普通的RecyclerView一樣。

  因為MultiType使用了自己的adapter==>MultiTypeAdapter,在里面填充的數據列表應該是List<Object>

  這里就根據自己的需求來設置這個Object類(就是自己定義Bean類)。

public class MainActivtiy extends AppCompatActivity {

    RecyclerView rv_design;
    List<Object> list;
    private MultiTypeAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_new_design);
        rv_design = (RecyclerView) findViewById(R.id.rv_design);

        rv_design.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MultiTypeAdapter();
        adapter.register(Title.class, new TitleViewBinder());
        
        rv_design.setAdapter(adapter);
        list = new ArrayList<>();
        list.add(new Title());//模擬的初始化數據,偽代碼
        adapter.setItems(list);
        adapter.notifyDataSetChanged();

    }

  活動的布局為==>R.layout.activity_new_design

  活動里面的RecyclerView==>R.id.rv_design

  RecyclerView設置布局管理器

  新建一個MultiTypeAdapter()適配器

  將MultiTypeAdapter注冊Bean+綁定類(Object+繼承ItemViewBinder類)

  RecyclerView設置這個MultiTypeAdapter適配器。

  

  新建一個ArrayList<>()

  這個ArrayList用自定義的Bean類填充。

  將MultiTypeAdapter適配器來設置數據,adapter.setItems(list)

  最后調用MultiTypeAdapter的刷新方法。

 


3.以一個復雜頁面為例

3.1.例子來源於簡書:MultiType的基本使用和復雜頁面的寫法實例。

 

 

3.2.需要實現的頁面:

  

 

 

3.3.布局分析:

  第一行是一個橫屏大圖。

  第二行是5個分類的入口。

  第三塊是2個入口。

  .................

  

  怎么寫這個布局呢?

  第一行作為一個Item

  第二行5個入口作為5個Item

  第三行2個入口作為2個Item

 

  如何將第一行的一個Item的排列轉接到第二行的5個item轉接到第三行的2個Item呢?

  可以參考一下官方文檔:實現線性布局和網格布局混排列表。

 

 

3.4.關鍵實現技術

  這里使用了GridLayoutManager。

  其實GridLayoutManager和LinearLayoutManager是差不多的,最大的區別就是其中的getSpanSize方法。

  GridLayoutManager的構造方法是:

GridLayoutManager layoutManager = new GridLayoutManager(this, total);

  其中total是一行的網格個數。

  所以可以利用getSpanSize方法動態改變total的大小。如下面的代碼:

     /**每一行占的個數不固定的例子
         * 比如有一行1個,5個,2個 同時存在
         * 全部相乘是10
         * 第一種 SpanSize=10/1
         * 第二種 SpanSize=10/5
         * 第三種 SpanSize=10/2
         * 為什么呢?因為要保證10/1 10/5 10/2 都是整數。
         */
        GridLayoutManager layoutManager = new GridLayoutManager(this, 10);//10=1*5*2
        layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {

            @Override
            public int getSpanSize(int position) {
                if (list.get(position) instanceof Title) {
                    return 10/1;//一行占1個
                }
                if (list.get(position) instanceof Ad) {
                    return 10/5;//一行占5個
                }
                if (list.get(position) instanceof EmptyValue) {
                    return 10/2;//一行占2個
                }
                return 10;//默認一行占1個
            }
        });

  如果拿到了list數據,然后判斷當前的list中的當前項的類型是什么(這個類型就是我們之前定義的Bean類)。

  所以這里可以將所有的Bean冗雜在一起,然后這里判斷是哪個類型,再選擇不同入口就行了。

  

 

3.5.如果要求一對多,就是如果就是第一行有時候要標題或者分割線怎么辦?  

  這里假設我們的需求不確定,就是第一行有時候要標題,有時候要分割線。

  處理方式==>參考這篇文章:一個類型對應多個ItemViewBinder。

  

  在注冊Bean類的時候就只用一個空類,我們暫且叫做EmptyValue。它可以對應我們需要的多種類型。

  從某個角度,有點像泛型了。

  

  寫法如下:

adapter.register(EmptyValue.class)
                .to(new GoodTitleViewBinder(),
                        new LineViewBinder())
                .withClassLinker(new ClassLinker<EmptyValue>() {
                    @NonNull
                    @Override
                    public Class<? extends ItemViewBinder<EmptyValue, ?>> 
                      index(@NonNull EmptyValue emptyValue) {
if (emptyValue.type == EmptyValue.TYPE_GOODTITLE) { return GoodTitleViewBinder.class; } if (emptyValue.type == EmptyValue.TYPE_LINE) { return LineViewBinder.class; } return LineViewBinder.class; } });

  這里使用emptyVaule.type來應對不同的類型。

  我們在一對多的時候需要區分這個Item是標題還是分割線。 

/**
 * EmptyValue
 * 占一整行而無數據的類型,適用於分割線、寫死的標題等等。
 */

public class EmptyValue {

    public static final int TYPE_GOODTITLE = 1;
    public static final int TYPE_LINE = 2;

    public int type;

    public EmptyValue(int type) {
        this.type = type;
    }
}

 

3.6.效果預覽

  

 

3.7.分析代碼

  

  加入的測試數據:

  

  關鍵主活動代碼:

public class NewDesignActivity extends AppCompatActivity {

    RecyclerView rv_design;
    List<Object> list;
    private MultiTypeAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_new_design);
        rv_design = (RecyclerView) findViewById(R.id.rv_design);

        final int num_banner = 1;
        final int num_title = 5;
        final int num_ad = 2;
        final int num_good_title = 1;
        final int num_good = 4;
        final int num_hot = 1;
        final int num_main_game = 1;
        final int num_new_title = 1;
        final int num_new_game = 2;

        int[] types = new int[]{num_banner, num_title, num_ad, num_good_title, num_good, num_hot};
        final int total = MockData.getTotal(types);

        /**每一行占的個數不固定的例子
         * 比如有一行1個,5個,2個 同時存在
         * 全部相乘是10
         * 第一種 SpanSize=10/1
         * 第二種 SpanSize=10/5
         * 第三種 SpanSize=10/2
         * 為什么呢?因為要保證10/1 10/5 10/2 都是整數。
         */
        GridLayoutManager layoutManager = new GridLayoutManager(this, total);
        layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {

            @Override
            public int getSpanSize(int position) {
                if (list.get(position) instanceof Title) {
                    return total / num_title;
                }
                if (list.get(position) instanceof Ad) {
                    return total / num_ad;
                }
                if (list.get(position) instanceof EmptyValue) {
                    return total / num_good_title;
                }
                if (list.get(position) instanceof Good) {
                    return total / num_good;
                }
                if (list.get(position) instanceof HotList) {
                    return total / num_hot;
                }
                if (list.get(position) instanceof MainGame) {
                    return total / num_main_game;
                }
                return total;
            }
        });
        rv_design.setLayoutManager(layoutManager);

        adapter = new MultiTypeAdapter();
        adapter.register(Banner.class, new BannerViewBinder());
        adapter.register(Title.class, new TitleViewBinder());
        adapter.register(Ad.class, new ADViewBinder());
        adapter.register(EmptyValue.class)
                .to(new GoodTitleViewBinder(),
                        new LineViewBinder())
                .withClassLinker(new ClassLinker<EmptyValue>() {
                    @NonNull
                    @Override
                    public Class<? extends ItemViewBinder<EmptyValue, ?>> index(@NonNull EmptyValue emptyValue) {
                        if (emptyValue.type == EmptyValue.TYPE_GOODTITLE) {
                            return GoodTitleViewBinder.class;
                        }
                        if (emptyValue.type == EmptyValue.TYPE_LINE) {
                            return LineViewBinder.class;
                        }
                        return LineViewBinder.class;
                    }
                });
        adapter.register(Good.class, new GoodViewBinder());
        adapter.register(HotList.class, new HotViewBinder());
        adapter.register(MainGame.class, new MainGameViewBinder());
        rv_design.setAdapter(adapter);
        list = new ArrayList<>();
        MockData.init(list);
        adapter.setItems(list);
        adapter.notifyDataSetChanged();

    }

}
View Code

  圖解:

  


4.今日頭條真實案例

4.1.今日頭條的段子頁面布局為:R.item_joke_content.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="4dp"
    android:layout_marginTop="4dp"
    android:background="@color/viewBackground"
    app:cardElevation="1dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="?attr/selectableItemBackground"
        android:foreground="?attr/selectableItemBackground"
        android:orientation="vertical"
        android:padding="16dp">

        <LinearLayout
            android:id="@+id/header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <com.jasonjan.headnews.widget.CircleImageView
                android:id="@+id/iv_avatar"
                android:layout_width="22dp"
                android:layout_height="22dp"
                android:layout_gravity="center"/>

            <TextView
                android:id="@+id/tv_username"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginLeft="8dp"
                android:layout_marginStart="8dp"
                android:ellipsize="end"
                android:maxLength="30"
                android:maxLines="1"
                android:textAppearance="@style/TextAppearance.AppCompat.Caption"
                tools:text="小恢恢的帽子"/>

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <ImageView
                    android:id="@+id/iv_dots"
                    android:layout_width="22dp"
                    android:layout_height="22dp"
                    android:layout_alignParentEnd="true"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:padding="4dp"
                    android:scaleType="center"
                    app:srcCompat="@drawable/ic_dots_horizontal_grey500_24dp"
                    tools:ignore="ContentDescription"/>
            </RelativeLayout>

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/header"
            android:layout_marginTop="4dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="昨天和閨蜜出去逛街,閨密問她老公要錢,她老公坐在沙發上,翹着二郎腿抽着煙問:“20行嗎?”閨密想了想,溫柔的點點頭,我正驚訝她老公能把她管制的服服貼貼,只見她老公從錢包里掏出20,然后把錢包遞給了媳婦……"/>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="20dp"
                android:layout_gravity="bottom"
                android:layout_marginTop="6dp"
                android:gravity="bottom"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/tv_digg_count"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    tools:text="53"/>

                <ImageView
                    android:layout_width="16dp"
                    android:layout_height="16dp"
                    android:layout_marginLeft="4dp"
                    android:layout_marginStart="4dp"
                    app:srcCompat="@drawable/ic_like_gray_24dp"
                    tools:ignore="ContentDescription"/>

                <TextView
                    android:id="@+id/tv_bury_count"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="16dp"
                    android:layout_marginStart="16dp"
                    tools:text="11"/>

                <ImageView
                    android:layout_width="16dp"
                    android:layout_height="16dp"
                    android:layout_marginLeft="4dp"
                    android:layout_marginStart="4dp"
                    app:srcCompat="@drawable/ic_dislike_gray_24dp"
                    tools:ignore="ContentDescription"/>

                <TextView
                    android:id="@+id/tv_comment_count"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="end"
                    tools:text="48評論"/>

            </LinearLayout>

        </LinearLayout>

    </RelativeLayout>
</android.support.v7.widget.CardView>
View Code

  預覽圖+實際預覽圖

        

 

 

4.2.段子視圖綁定==>JokeContentViewBinder

  邏輯處理都在這里面進行。

  包括什么點擊事件啊,設置真實數據的顯示情況啊。

  這里就有可能用到很多圖片加載的第三方庫等,圓形圖片等,不過都是一些顯示問題了。

 

 

4.3.加載完畢所有視圖綁定==>LoadingEndViewBinder

  這里的布局很簡單。也可以很復雜,有些做的花里胡哨的,都是這里做一些手腳的。

  R.item_loading_end.xml==>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:layout_marginBottom="1dp"
    android:gravity="center_horizontal"
    android:orientation="horizontal">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:text="@string/no_more_content"
        android:textSize="16sp"/>

</LinearLayout>

  實圖預覽:

  

 

 

4.4.正在加載視圖綁定==>LoadingViewBinder

  這里正在加載的話,應該有一個進度條加載框(andorid自帶的是PregressBar)

  這里的布局文件為:R.layout.item_loading==>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:layout_marginBottom="1dp"
    android:gravity="center_horizontal"
    android:orientation="horizontal">

    <ProgressBar
        android:id="@+id/progress_footer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:visibility="visible"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginStart="8dp"
        android:gravity="center_vertical"
        android:text="@string/loading"/>

</LinearLayout>

  預覽圖片為:

  

 

 

4.5.在段子頁面==>JokeContentView執行如下代碼: 

@Override
    protected void initView(View view) {
        super.initView(view);
        adapter = new MultiTypeAdapter(oldItems);
        Register.registerJokeContentItem(adapter);
        recyclerView.setAdapter(adapter);
        recyclerView.addOnScrollListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                if(NetWorkUtil.isNetworkConnected(InitApp.AppContext)){
                    if (canLoadMore) {
                        canLoadMore = false;
                        presenter.doLoadMoreData();
                    }
                }else{
                    presenter.doShowNetError();
                }

            }
        });
    }

  這里監聽了recyclerView是否滑動最低端了,用處理器的doLoadMoreData方法來加載下一頁。

 

  這里注冊的類型,自己封裝了一個類Register。

public class Register {
    public static void registerJokeContentItem(@NonNull MultiTypeAdapter adapter) {
        adapter.register(JokeContentBean.DataBean.GroupBean.class, new JokeContentViewBinder());
        adapter.register(LoadingBean.class, new LoadingViewBinder());
        adapter.register(LoadingEndBean.class, new LoadingEndViewBinder());
    }
}

  如果當前頁面可見的話,將會執行fetchData方法,這個是懶加載碎片中定義的一個抽象方法。

  執行了onLoadData()方法來

@Override
    public void onLoadData() {
        onShowLoading();
        presenter.doLoadData();
    }

  先展示加載框。

  然后用處理器的一個doLoadData()方法來加載數據。

  所以這個處理器中一定有一個適配器,而且就是這里面定義的適配器。

 

  所以進入JokeContentPresenter處理器中,發現這里面並沒有前面定義adapter啊!

  但是這里數據在處理器中加載完畢,相當於執行了一個回調,到JokeContentView中的onSetAdapter中添加。

  @Override
    public void doLoadData(){
        if(NetWorkUtil.isNetworkConnected(InitApp.AppContext)) {
            //這里需要更改!!!
            Map<String, String> map = ToutiaoUtil.getAsCp();

            RetrofitFactory.getRetrofit().create(IJokeApi.class).getJokeContent(time, map.get(Constant.AS), map.get(Constant.CP))
                    .subscribeOn(Schedulers.io())
                    .map(new Function<JokeContentBean, List<JokeContentBean.DataBean.GroupBean>>() {
                        @Override
                        public List<JokeContentBean.DataBean.GroupBean> apply(@NonNull JokeContentBean jokeContentBean) throws Exception {
                            List<JokeContentBean.DataBean> data = jokeContentBean.getData();
                            for (JokeContentBean.DataBean dataBean : data) {
                                groupList.add(dataBean.getGroup());
                            }
                            time = jokeContentBean.getNext().getMax_behot_tim() + "";
                            return groupList;
                        }
                    })
                    .compose(view.<List<JokeContentBean.DataBean.GroupBean>>bindToLife())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<List<JokeContentBean.DataBean.GroupBean>>() {
                        @Override
                        public void accept(@NonNull List<JokeContentBean.DataBean.GroupBean> groupBeen) throws Exception {
                            if (groupBeen.size() > 0) {
                                doSetAdapter();
                            } else {
                                doShowNoMore();
                            }
                        }
                    }, ErrorAction.error());
        }else{
            doShowNetError();
        }
    }

  這是主要加載數據的方法。將所有數據放到一個groupList中。

  然后執行doSetAdapter回調方法。

 @Override
    public void doSetAdapter(){
        view.onSetAdapter(groupList);
        view.onHideLoading();
    }

  這里調用了一個接口的方法,所以必然要在JokeContentView中實現這個接口,才有這個回調。

 @Override
    public void onSetAdapter(List<?> list) {
        Items newItems = new Items(list);
        newItems.add(new LoadingBean());
        DiffCallback.notifyDataSetChanged(oldItems, newItems, DiffCallback.JOKE, adapter);
        oldItems.clear();
        oldItems.addAll(newItems);
        canLoadMore = true;
    }

  這里先將 list保存到一個newItems,就是從API請求到的數據。

  這里將新增的Items動態添加在oldItems中,執行了一個DiffCallback自定義類來添加。

  最后將oldItems重寫。

  設置canLoadMore為true。

  這個oldItems定義在BaseListFragment中。

protected Items oldItems = new Items();
 @Override
    public void onShowNoMore() {
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (oldItems.size() > 0) {
                    Items newItems = new Items(oldItems);
                    newItems.remove(newItems.size() - 1);
                    newItems.add(new LoadingEndBean());
                    adapter.setItems(newItems);
                    adapter.notifyDataSetChanged();
                } else if (oldItems.size() == 0) {
                    oldItems.add(new LoadingEndBean());
                    adapter.setItems(oldItems);
                    adapter.notifyDataSetChanged();
                }
                canLoadMore = false;
            }
        });
    }

  如果沒有更多了,要先判斷一下。

  如果之前沒有數據,直接在尾巴加一個LoadingEndBean。

  如果之前有數據,先移除最后一個數據,將一個LoadingEndBean加到尾巴。

  最后將canLoadMore為false。

  其實這里是處理沒有更多的情況。 


5.自定義DiffUtil簡單用法

5.1..如何利用DiffCallback將adapter填充了Items數據的?

  參考文章:Android詳解7.0帶來的新工具類DiffUtil。

  用處:在RecyclerView刷新時,不再無腦adapter.notifyDataSetChanged()。

     RecyclerVIew刷新不會觸發RecyclerView的動畫(刪除、新增、位移、change動畫)

     性能較低,畢竟是無腦的刷新了一遍整個RecyclerView,極端情況下,新老數據集一樣,效率最低。

  它會自動計算新老數據集的差異,並根據差異情況,自動調用以下四個方法。 

adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);

  實現增量更新效果。

 

5.2.僅僅需要額外編寫一個Callback回調類。

  它的意義是:定義了一些用來比較新老Item是否相等的契約(Contract)、規則(Rule)的類。

  DiffUtil.Callback抽象類如下: 

 public abstract static class Callback {
        public abstract int getOldListSize();//老數據集size

        public abstract int getNewListSize();//新數據集size
        //新老數據集在同一個postion的Item是否是一個對象?(可能內容不同,如果這里返回true,會調用下面的方法)
        public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
        //這個方法僅僅是上面方法返回ture才會調用,我的理解是只有notifyItemRangeChanged()才會調用,判斷item的內容是否有變化
        public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);

        //該方法在DiffUtil高級用法中用到 ,暫且不提
        @Nullable
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return null;
        }
    }

 

 

5.3.以某個項目的DiffCallback為例。 

public class DiffCallback extends DiffUtil.Callback {
    public static final int JOKE = 1;
    private List oldList, newList;
    private int type;

    public DiffCallback(List oldList, List newList, int type) {
        this.oldList = oldList;
        this.newList = newList;
        this.type = type;
    }

    public static void notifyDataSetChanged(List oldList, List newList, int type, RecyclerView.Adapter adapter) {
        DiffCallback diffCallback = new DiffCallback(oldList, newList, type);
        DiffUtil.DiffResult result = DiffUtil.calculateDiff(diffCallback, true);
        result.dispatchUpdatesTo(adapter);
    }

    @Override
    public int getOldListSize() {
        return oldList != null ? oldList.size() : 0;
    }

    @Override
    public int getNewListSize() {
        return newList != null ? newList.size() : 0;
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition,int newItemPosition){
        try{
            switch(type){
                case JOKE:
                    return ((JokeContentBean.DataBean.GroupBean) oldList.get(oldItemPosition)).getContent().equals(
                            ((JokeContentBean.DataBean.GroupBean) newList.get(newItemPosition)).getContent());

            }
        }catch(Exception e){

        }
        return false;
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        try {
            switch (type) {
                case JOKE:
                    return ((JokeContentBean.DataBean.GroupBean) oldList.get(oldItemPosition)).getShare_url().equals(
                            ((JokeContentBean.DataBean.GroupBean) newList.get(newItemPosition)).getShare_url());
           }
        } catch (Exception e) {

        }
        return false;
    }
}
View Code

  具體分析:

  構造函數不用多說,一個老的List,一個新的List,一個類型。

public DiffCallback(List oldList, List newList, int type) {
        this.oldList = oldList;
        this.newList = newList;
        this.type = type;
    }

  為什么要傳入一個類型呢?==>因為這里可能數據很多,類型也很多,所以先設置type鋪墊一下。

  

  然后是一個靜態函數,供外部調用。

public static void notifyDataSetChanged(List oldList, List newList, int type, 
                            RecyclerView.Adapter adapter) { DiffCallback diffCallback
= new DiffCallback(oldList, newList, type); DiffUtil.DiffResult result = DiffUtil.calculateDiff(diffCallback, true); result.dispatchUpdatesTo(adapter); }

  耦合度非常低,外部只需要傳入兩個List,一個自己自願加的type,一個關鍵的adapter。

  所以外部調用這個方法后,會執行這個抽象函數DiffCallback(oldList,newList,type)。

 

  DiffUtil.DiffResult result=DiffUtil.calculateDiff(diffCallback,true)怎么理解呢?==> 

  在將newDatas 設置給Adapter之前,先調用DiffUtil.calculateDiff()方法,
計算出新老數據集轉化的最小更新集,就是DiffUtil.DiffResult對象。 DiffUtil.calculateDiff()方法定義如下: 第一個參數是DiffUtil.Callback對象, 第二個參數代表是否檢測Item的移動,改為false算法效率更高,按需設置,我們這里是true。

 

  result.dispatchUpdatesTo(adapter)怎么理解呢?

利用DiffUtil.DiffResult對象的dispatchUpdatesTo()方法,傳入RecyclerView的Adapter,
替代普通青年才用的mAdapter.notifyDataSetChanged()方法。 查看源碼可知,該方法內部,就是根據情況調用了adapter的下面的四大定向刷新方法。
  public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
            dispatchUpdatesTo(new ListUpdateCallback() {
                @Override
                public void onInserted(int position, int count) {
                    adapter.notifyItemRangeInserted(position, count);
                }

                @Override
                public void onRemoved(int position, int count) {
                    adapter.notifyItemRangeRemoved(position, count);
                }

                @Override
                public void onMoved(int fromPosition, int toPosition) {
                    adapter.notifyItemMoved(fromPosition, toPosition);
                }

                @Override
                public void onChanged(int position, int count, Object payload) {
                    adapter.notifyItemRangeChanged(position, count, payload);
                }
            });
        }

 

  關於重寫的areItemsTheSame(int oldItemPosition,int newItemPosition)方法的理解。

 /**
     * 被DiffUtil調用,用來判斷 兩個對象是否是相同的Item。
     * 例如,如果你的Item有唯一的id字段,這個方法就 判斷id是否相等。
     * 本例判斷name字段是否一致,不一定全部是這樣!!!!
     */
    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldDatas.get(oldItemPosition).getName().equals
                      (mNewDatas.get(newItemPosition).getName()); }

 

  關於重寫的areContentsTheSame(int oldItemPositon,int newItemPosition)方法的理解。

 /**
     * 被DiffUtil調用,用來檢查 兩個item是否含有相同的數據
     * DiffUtil用返回的信息(true false)來檢測當前item的內容是否發生了變化
     * DiffUtil 用這個方法替代equals方法去檢查是否相等。
     * 所以你可以根據你的UI去改變它的返回值
     * 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要返回Item的視覺表現是否相同。
     * 這個方法僅僅在areItemsTheSame()返回true時,才調用。
     */
    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        TestBean beanOld = mOldDatas.get(oldItemPosition);
        TestBean beanNew = mNewDatas.get(newItemPosition);
        if (!beanOld.getDesc().equals(beanNew.getDesc())) {
            return false;//如果有內容不同,就返回false
        }
        if (beanOld.getPic() != beanNew.getPic()) {
            return false;//如果有內容不同,就返回false
        }
        return true; //默認兩個data內容是相同的
    }

  

  小貼士:

  我一開始想找adapter在哪里設置數據的,因為DiffCallback只是比較差異,並沒有自己將數據加載進去。

  所以我找了半天都不知道這個方法在哪里。

  后來,我發現,根本不用setDatas。

  因為我用了MultiType第三方庫,所以它自己有一個方法,率先將數據綁定到適配器了。 

 adapter = new MultiTypeAdapter(oldItems);

  就是這個方法,oldItems變化的時候,adapter也隨之添加了這個數據。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM