天天看點

RecyclerView:實作帶header的grid(ViewType和setSpanSizeLookup()方法)

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。

RecyclerView:實作帶header的grid(ViewType和setSpanSizeLookup()方法)

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…

RecyclerView:實作帶header的grid(ViewType和setSpanSizeLookup()方法)

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(ViewType和setSpanSizeLookup()方法)

總結

為了用RecyclerView建立一個帶header的grid:

  1. 定義一個具有兩種view類型的adapter,一個為header一個為普通item。
  2. nflate一個header,把它傳遞給adapter。
  3. 重寫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);
        }

    }

}
           

效果圖如下:

RecyclerView:實作帶header的grid(ViewType和setSpanSizeLookup()方法)

但是這種方法的實作效果并不好,因為下面的Linearlayout的資料源是從第九個開始加載的,那麼前面8個等于是浪費掉了。

參考:http://blog.csdn.net/danfengw/article/details/53117182