GridView和ListView有許多的相似之處,不過也有一個顯著的不同:沒有header和footer。現在它們兩者都可以用RecyclerView實作,我想看看如何在grid上添加header。
GridLayoutManager
我用GridLayoutManager建立了一個spanCount為2的RecylcerView。
注:spanCount即列數。這裡GridLayoutManager的第二個參數就是spanCount。
RecyclerView recyclerView = (RecyclerView) findViewById(
R.id.recycler_view);
recyclerView.addItemDecoration(new MarginDecoration(this));
//如果内容更改不會更改RecyclerView 的布局大小,請使用此設定來提高性能;
//将其設定為true并不意味着RecyclerView大小是固定的,隻是意味着它不會因擴充卡内容的更改而改變。
//當您在RecyclerView中添加或删除項目,并且不更改其高度或寬度時,請将setHasFixedSize設定為true避免不必要的布局傳遞。
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new GridLayoutManager(this, ));
recyclerView.setAdapter(new NumberedAdapter());
NumberedAdapter以字元串的形式顯示了每個item的position,在點選的時候顯示一個toast。

GridLayoutActivity.java
可變的span size
在上面的基本設定中,我們的spanCount為2,每個item的span size為1,是以一個header需要的span size則為2。在我嘗試着添加header之前,我想先看看如何設定 span size。其實很簡單。
注:span size表示一個item的跨度,跨度了多少個span。
GridLayoutManager manager = new GridLayoutManager(this, );
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return ( - position % );
}
});
recyclerView.setLayoutManager(manager);
setSpanSizeLookup可以讓你根據position來設定 span size,上面代碼中的公式所得到的 span size依次是3, 2, 1, 3, 2, 1…
GridLayoutVariableSpanSizeActivity.java
頭部(header)
現在讓我們來添加一個header!我們需要一個提供兩種view類型的adapter,一個為header一個為普通的item。可以看看HeaderNumberedAdapter,它在構造函數中把一個view作為header,然後把它存在一個成員變量中。
package com.sqisland.android.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class HeaderNumberedAdapter extends RecyclerView.Adapter<TextViewHolder> {
private static final int ITEM_VIEW_TYPE_HEADER = ;
private static final int ITEM_VIEW_TYPE_ITEM = ;
private final View header;
private final List<String> labels;
public HeaderNumberedAdapter(View header, int count) {
if (header == null) {
throw new IllegalArgumentException("header may not be null");
}
this.header = header;
this.labels = new ArrayList<String>(count);
for (int i = ; i < count; ++i) {
labels.add(String.valueOf(i));
}
}
public boolean isHeader(int position) {
return position == ;
}
@Override
public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_VIEW_TYPE_HEADER) {
return new TextViewHolder(header);
}
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new TextViewHolder(view);
}
@Override
public void onBindViewHolder(final TextViewHolder holder, final int position) {
if (isHeader(position)) {
return;
}
final String label = labels.get(position - ); // Subtract 1 for header
holder.textView.setText(label);
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(
holder.textView.getContext(), label, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public int getItemViewType(int position) {
return isHeader(position) ? ITEM_VIEW_TYPE_HEADER : ITEM_VIEW_TYPE_ITEM;
}
@Override
public int getItemCount() {
return labels.size() + ;
}
}
其中TextViewHolder的代碼為:
package com.sqisland.android.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
public class TextViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public TextViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
}
}
在RecyclerView建立一個view的時候,如果處于header的位置,我們用view holder來封裝這個header。onBindViewHolder中不必對header做任何事情,因為它的邏輯是在activity中處理的。
回到activity。我們需要用一個header來初始化HeaderNumberedAdapter,同時重寫setSpanSizeLookup,讓header橫跨所有列。
final GridLayoutManager manager = new GridLayoutManager(this, );
recyclerView.setLayoutManager(manager);
View header = LayoutInflater.from(this).inflate(
R.layout.header, recyclerView, false);
header.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), R.string.grid_layout_header,
Toast.LENGTH_SHORT).show();
}
});
final HeaderNumberedAdapter adapter
= new HeaderNumberedAdapter(header, );
recyclerView.setAdapter(adapter);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return adapter.isHeader(position) ? manager.getSpanCount() : ;
}
});
我們inflate header,定義它的點選事件,使用它去構造adapter。然後再setSpanSizeLookup中,我們在header所處的位置傳回和span count (列數)相等的 span size。
總結
為了用RecyclerView建立一個帶header的grid:
- 定義一個具有兩種view類型的adapter,一個為header一個為普通item。
- nflate一個header,把它傳遞給adapter。
- 重寫GridLayoutManager中的setSpanSizeLookup,在header所處的位置傳回和span count(列數)相等的 span size。
源碼: https://github.com/chiuki/android-recyclerview
ps:在我的g+上有人評論說如果你不需要RecyclerView的功能,比如animation,reordering,staggering等,你也可以從AOSP中拷貝HeaderGridView.java。
利用ViewType和setSpanSizeLookup()方法實作混合布局
有時候我們可能需要實作一個上面是GridLayout下面是LinearLayout的效果;
如果之前你用過ListView實作過此功能,那麼你一定對下面這兩個方法并不陌生;
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
@Override
public int getViewTypeCount() {
return super.getViewTypeCount();
}
其中getItemViewType方法是用來擷取目前項Item(position參數)是哪種類型的布局,getViewTypeCount方法是用來擷取目前listview總共有多少種類型的布局。
如果你用RecyclerView,你會發現getViewTypeCount這個方法沒有了,隻有一個getItemViewType方法,用法和listview沒有任何差別,這裡要注意的就是這個函數onCreateViewHolder(ViewGroup parent, int viewType)這裡的第二個參數就是View的類型,可以根據這個類型判斷去建立不同item的ViewHolder。
ApplicationAdapter.java :
public class ApplicationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
//枚舉類,兩個變量代表兩種布局方式
public enum ITEM_TYPE {
ITEM_TYPE_TOP,
ITEM_TYPE_BOTTOM
}
private LayoutInflater mLayoutInflater = null;
private Context mContext = null;
//不同的資料源
private List<ApplicationModel> models;
private List<MessageModel> messages;
public ApplicationAdapter(Context context, List<ApplicationModel> list, List<MessageModel> messageList){
mContext = context;
models = list;
messages = messageList;
mLayoutInflater = LayoutInflater.from(context);
}
/*
* 我們在這個方法中得到viewType,這個參數就是getItemViewType(int position)方法傳回的值
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType==ITEM_TYPE.ITEM_TYPE_TOP.ordinal()){
return new ApplicationHolder(mLayoutInflater.inflate(R.layout.application_item_layout,parent,false));
}else{
return new MessageHolder(mLayoutInflater.inflate(R.layout.message_item_layout,parent,false));
}
}
/*
* 根據不同的viewType,為不同的布局各自填充資料源
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(holder instanceof ApplicationHolder){
((ApplicationHolder) holder).mApplicationIcon.setImageResource(models.get(position).getDrawableResId());
((ApplicationHolder) holder).mApplicationName.setText(models.get(position).getName());
}else if(holder instanceof MessageHolder){
((MessageHolder) holder).mMessageIcon.setImageResource(messages.get(position).getmResoueceIcon());
((MessageHolder) holder).mMessageName.setText(messages.get(position).getmName());
((MessageHolder) holder).mMessageContent.setText(messages.get(position).getmContent());
}
}
//前八個布局按grid顯示
@Override
public int getItemViewType(int position) {
return position < ? ITEM_TYPE.ITEM_TYPE_TOP.ordinal() : ITEM_TYPE.ITEM_TYPE_BOTTOM.ordinal();
}
@Override
public int getItemCount() {
return models.size();
}
private class ApplicationHolder extends RecyclerView.ViewHolder {
private ImageView mApplicationIcon;
private TextView mApplicationName;
public ApplicationHolder(View inflate) {
super(inflate);
mApplicationIcon = (ImageView) inflate.findViewById(R.id.item_application_right_data_view_icon_iv);
mApplicationName = (TextView) inflate.findViewById(R.id.item_application_right_data_view_name_tv);
}
}
private class MessageHolder extends RecyclerView.ViewHolder {
private ImageView mMessageIcon;
private TextView mMessageName;
private TextView mMessageContent;
public MessageHolder(View inflate) {
super(inflate);
mMessageIcon = (ImageView) inflate.findViewById(R.id.list_item_info_image_view);
mMessageName = (TextView) inflate.findViewById(R.id.list_item_info_title_text_view);
mMessageContent = (TextView) inflate.findViewById(R.id.list_item_info_date_text_view);
}
}
}
其實代碼不難了解。
MainActivity.java :
public class MainActivity extends AppCompatActivity{
private RecyclerView mRecyclerView;
private List<ApplicationModel> applicationModels = new ArrayList<>();//應用資料集合
private List<MessageModel> messageModels = new ArrayList<>();//資訊資料集合
private ApplicationAdapter mAdapter;
public int[] resources = new int[]{R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher};
public int[] icons = new int[]{R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,
R.mipmap.app_icon_err,};
public String[] names = new String[]{"Name1","Name2","Name3","Name4","Name5","Name6","Name7",
"Name8","Name9","Name10","Name11","Name12"};
public String[] ntitles = new String[]{"Title1","Title2","Title3","Title4","Title1","Title2","Title3",
"Title1","Title2","Title3","Title1","Title2",};
public String[] contents = new String[]{"111","222","333","444","555","666","777",
"888","999","110","111","112"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
GridLayoutManager manager = new GridLayoutManager(this,);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if(position<)
return ;
else
return ;
}
});
mRecyclerView.setLayoutManager(manager);
// mRecyclerView.setLayoutManager(new GridLayoutManager(this,4));
initData();
mAdapter = new ApplicationAdapter(this,applicationModels,messageModels);
mRecyclerView.setAdapter(mAdapter);
}
private void initData() {
for(int i =;i<names.length;i++){
MessageModel message = new MessageModel(icons[i],ntitles[i],contents[i]);
messageModels.add(message);
}
for(int j =;j<names.length;j++){
ApplicationModel model = new ApplicationModel(resources[j],names[j]);
applicationModels.add(model);
}
}
}
效果圖如下:
但是這種方法的實作效果并不好,因為下面的Linearlayout的資料源是從第九個開始加載的,那麼前面8個等于是浪費掉了。
參考:http://blog.csdn.net/danfengw/article/details/53117182