RecyclerView多種佈局封裝優化(雷驚風)

   今天看見一篇文章,講到了RecyclerView一些關於加載多種佈局樣式時通常用法存在的一些問題,下面是文章地址:http://www.jianshu.com/p/c6a44e18badb 這裏先看一下通常大多數人的用法,如下:

package com.jason.recycleview.lylrecycleviewadpdemo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
public class MyAdpter extends RecyclerView.Adapter {
    private int type1 = 1;
    private int type2 = 2;
    private Context mContext;
    private List<Data> mList;

    public MyAdpter(Context con, List<Data> list) {
        this.mContext = con;
        this.mList = list;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0)
            return type1;
        if (position == 1)
            return type2;
        return super.getItemViewType(position);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == type1) {
            return new ViewHolder1();
        } else if (viewType == type2) {
            return new ViewHolder2();
        }
        return new ViewHolder3();
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof ViewHolder1) {
            .....
        }else if(holder instanceof ViewHolder2){
            .....
        }
        else if(holder instanceof ViewHolder3){
            .....
        }
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }
}

據文章上說的這樣寫存在三種弊端:

       1. 類型檢查與類型轉型,由於在onCreateViewHolder根據不同類型創建了不同的ViewHolder,所以在onBindViewHolder需要針對不同類型的ViewHolder進行數據綁定與邏輯處理,這導致需要通過instanceofViewHolder進行類型檢查與類型轉型。引用文章上的一句話:
[]關於Android Adapter,你的實現方式可能一直都有問題中是這樣說的

許多年前,我在我的顯示器上貼了許多的名言。其中的一個來自 Scott Meyers 寫的《Effective C++ 這本書(最好的IT書籍之一),它是這麼說的:
不管什麼時候,只要你發現自己寫的代碼類似於 “ if the object is of type T1, then do something, but if it’s of type T2, then do something else ”,就給自己一耳光。

       2. 我們的這種寫法已經違背了 SOLID 原則中的開閉準則即“對擴展開放,對修改封閉。當我們有新的佈局需要添加進來時我們需要在原來的Adpter中添加type標記,要在getItemViewType()方法中添加兼容,要在onCreateViewHolder()、onBindViewHolder()中添加新的ifelse處理。

       3. 增加了日後的維護成本。隨着我們佈局的不斷增加,我們的Adpter會變得越來越龐大。

 

   針對上邊的三個問題,網上已經有大牛進行了相關優化,在此膜拜一下,表示一下感謝,我也自己寫了一下代碼,後邊會附上代碼的下載地址,那麼下邊看一下是怎麼進行的優化吧。

先看一下代碼:




   對所有與列表相關的Model 層數據進行抽象封裝(BaseValue),當我們在初始化數據源時就能以List<BaseValue>的形式將不同類型的Model集合在列表中;




   將列表類型判斷的相關代碼抽取到ViewTypeFactory中,同時所有列表類型對應的佈局資源都在這個類中進行管理維護,增強了擴展性與可維護性;

   這裏用到了設計模式中的訪問者設計模式,不懂的同學可以先去了解一下相關知識,說一下這裏的應用:我們可以在我們的Apdter中實例化一個BaseViewTypeFactory對象,通過獲取數據源中對應的model類型調用自己的getLayoutId()方法傳入我們的BaseViewTypeFactory對象,看代碼可以知道在我們的model實現類的getLayoutId()方法中傳入的ViewTypeFactory又調用的相應的type()方法,傳入了個自對應的model類型,看Factory實現類中根據不同model重載了相應的type()方法,返回的是model對應的佈局的.xml文件在R文件中的id,也就是說在getViewItemType()方法中經過這一系列的調用以後最終返回的爲對應的layoutId;及在MyAdpterOnCreatViewHolderViewGrop parent,int viewType)中第二個參數不再是我們自己定義的了,而是當前想要生成的ViewHolder中綁定ViewLayoutId;看下面MyAdpter中代碼:


   現在我們可以直接用viewType創建itemView,但是,itemView創建之後,還是需要進行類型判斷,創建不同的ViewHolder,那我們來繼續處理,我們將RecyclerView.ViewHolder進行抽象,每一種佈局對應一個自己的ViewHolder,下面看代碼實現:

public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {
    private SparseArray<View> mArray;
    private View mView;

    public BaseViewHolder(View itemView) {
        super(itemView);
        this.mView = itemView;
        mArray = new SparseArray<>();
    }

    public View getView(int resId) {
        View view = mArray.get(resId);
        if (view == null) {
            view = mView.findViewById(resId);
            mArray.put(resId, view);
        }
        return view;
    }
    public abstract void setUpView(T modle,int position,MyAdpter adpter);

}

public class ViewHolderOne extends BaseViewHolder<Value1> {
    public ViewHolderOne(View itemView) {
        super(itemView);
    }

    @Override
    public void setUpView(final Value1 modle, int position, MyAdpter adpter) {
        final TextView tv = (TextView) getView(R.id.tv1);
        tv.setText(modle.getName());
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(tv.getContext(), modle.getName(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

public class ViewHolderTwo extends BaseViewHolder<Value2> {
    public ViewHolderTwo(View itemView) {
        super(itemView);
    }

    @Override
    public void setUpView(Value2 modle, int position, MyAdpter adpter) {
        TextView tv= (TextView) getView(R.id.tv2);
        tv.setText(modle.getAddress());
    }
}

   我們繼承RecyclerView.ViewHolder做擴展,保存item對應生成的View,通過一個getView()方法用SparseArray做我們要操作的子View的緩存,並且拿到相應的View;這個getView方法是供子類調用的;setUpView()抽象方法子類實現操作自己對應的子View。

定義好了我們的每一種item對應的ViewHolder後,再回到我們的BaseViewTypeFactory

基類添加一個抽象的onCreatViewHolder()方法,ViewTypeFactory類中實現,看下邊代碼

public interface BaseViewTypeFactory {
    int type(Value1 type);
    int type(Value2 type);
    int type(Value3 type);
    BaseViewHolder creatViewHolder(View v,int viewType);
}

public class ViewTypeFactory implements BaseViewTypeFactory {
    private final int oneId = R.layout.viewtype1;
    private final int twoId = R.layout.viewtype2;
    private final int threeId = R.layout.viewtype3;

    @Override
    public int type(Value1 type) {
        return oneId;
    }

    @Override
    public int type(Value2 type) {
        return twoId;
    }

    @Override
    public int type(Value3 type) {
        return threeId;
    }

    @Override
    public BaseViewHolder creatViewHolder(View v, int viewType) {
        BaseViewHolder mv = null;
        if (viewType == oneId)
            mv = new ViewHolderOne(v);
        else if (viewType == twoId)
            mv = new ViewHolderTwo(v);
        else if (viewType == threeId)
            mv = new ViewHolderThree(v);
        return mv;
    }
}

   這樣處理好以後我們就可以在我們adpter類中的onCreatViewHolder()方法中通過我們已經實例好的factory類進行ViewHolder的初始化了看代碼:

@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = View.inflate(parent.getContext(), viewType, null);
    return mFactory.creatViewHolder(v, viewType);
}

   分析一下這段代碼,首先通過viewType生成View,然後調用creatViewHolder()方法,在方法里根據不同的viewType生成對應的ViewHolder返回;這樣一來我們將判斷移出了Adpter類,到這裏我們的onCreatViewHolder()方法就優化完了,就兩行代碼,生成View,調用creatViewHolder()生成ViewHolder。是不是感覺Adpter中清新了不少。

然後就是onBindViewHolder()方法,這就簡單了,還是看代碼:

@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
    holder.setUpView(mList.get(position), position, this);

}

就一行代碼,因爲我們在每一個ViewHolder中實現了自己的setUpView()方法,先拿一個看一下:

@Override
public void setUpView(final Value1 modle, int position, MyAdpter adpter) {
    final TextView tv = (TextView) getView(R.id.tv1);
    tv.setText(modle.getName());
    tv.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(tv.getContext(), modle.getName(), Toast.LENGTH_SHORT).show();
        }
    });
}

   這ViewHolder1中的setUpView實現,分析一下:我們在Adpter中的數據源中拿到model與對應的position位置信息,調用setUpView(),在ViewHolder1中通過父類中的getView()方法得到要操作的子View,從model中得到值進行操作。

   到目前爲止我們的多佈局RecyclerView就優化完了,看一下我們的Adpter是不是很清晰:

@Override
public int getItemViewType(int position) {
    return mList.get(position).getLayoutId(mFactory);
}

@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = View.inflate(parent.getContext(), viewType, null);
    return mFactory.creatViewHolder(v, viewType);
}

@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
    holder.setUpView(mList.get(position), position, this);

}

  下面我們把我們activity中的代碼與Adpter中代碼也附在下邊,佈局文件沒什麼好說的,就是一個RecyclerView,沒什麼特殊的:

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerview = (RecyclerView) findViewById(R.id.recyclerview);
        recyclerview.setLayoutManager(new LinearLayoutManager(this));
        recyclerview.setAdapter(new MyAdpter(this));
    }
}

public class MyAdpter extends RecyclerView.Adapter<BaseViewHolder> {
    private Context mContext;
    private List<BaseValue> mList;
    private BaseViewTypeFactory mFactory;

    public MyAdpter(Context con) {
        this.mContext = con;
        mFactory = new ViewTypeFactory();
        mList = new ArrayList<>();
        mList.add(new Value1("劉大"));
        mList.add(new Value3("8"));
        mList.add(new Value1("王二"));
        mList.add(new Value1("張三"));
        mList.add(new Value2("北京"));
        mList.add(new Value3("11"));
        mList.add(new Value1("李四"));
        mList.add(new Value2("上海"));
        mList.add(new Value1("陳五"));
        mList.add(new Value3("32"));
        mList.add(new Value1("週六"));
        mList.add(new Value2("深圳"));
        mList.add(new Value1("小明"));
        mList.add(new Value1("小光"));
        mList.add(new Value1("小紅"));
        mList.add(new Value3("48"));
        mList.add(new Value2("天津"));
        mList.add(new Value2("廣州"));

    }

    @Override
    public int getItemViewType(int position) {
        return mList.get(position).getLayoutId(mFactory);
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = View.inflate(parent.getContext(), viewType, null);
        return mFactory.creatViewHolder(v, viewType);
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        holder.setUpView(mList.get(position), position, this);

    }

    @Override
    public int getItemCount() {
        return mList.size();
    }
}

我們在adpter創建的時候載入了不同類型的數據,下邊看一下運行結果:



當我們有新的類型佈局進來後,看看我們需要改哪些地方:

1.新建對應model。

2.Factory兼容。

3.新建新的ViewHolder,實現setUpView()方法就行了。

是不是很簡單,我們的Adpter不用動了,是不是擴展性比原來好多了,而且沒有了類型的對比轉換,維護性也好了,不用管Adpter了,只需做以上三步,就完成了我們新類型佈局的添加。好的,這篇文章就寫到這裏,如果你感覺說的不是很清楚,可以下載源碼閱讀源碼,希望對你有所幫助!

 

這裏附上Demo的下載地址:http://download.csdn.net/detail/liuyonglei1314/973648

發佈了29 篇原創文章 · 獲贊 29 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章