RecyclerView實現分組展現信息

extends:http://blog.csdn.net/wzlyd1/article/details/52292548android

前言

一直在鴻洋大神的安卓羣裏水羣,漸漸的loader和安卓弟等人都成長了起來,還記得當初他們清純的模樣。小L在羣裏不水了,安卓弟成長爲CTO了,只有我依然默默無聞,因而決定再寫博客了,以前不寫,一是由於工做比較忙,二是由於我水平有限,簡單的不想寫,由於寫了也沒用,網上demo不少,難的本身也沒多高的造詣,寫也寫不出來,因此一直都是處於「半荒廢狀態」,固然說到底其實仍是由於懶,因而今天我再次執筆,將學到的東西所有記錄下來。git

效果

先上效果圖給你們看看,好有一個總體的認識github

這裏寫圖片描述

效果就是這樣的,可是不只僅侷限於這種佈局,事實上只要是三段式佈局,均可以經過該demo的學習來實現,什麼是三段式佈局呢,就是有header -content-footer類型的佈局,畫一個圖來解釋算法

這裏寫圖片描述 
好比下面這個圖就能夠 
這裏寫圖片描述json

能夠看到,用途仍是很普遍的,因此很須要咱們去學習一下數據結構

怎麼去實現

gitbub上有一個很牛逼的類,可是貌似知道的人不多,名字叫作SectionedRecyclerViewAdapter ,可是今天咱們不去研究她是怎麼實現的,咱們來研究他怎麼用就好了app

  1. 繼承SectionedRecyclerViewAdapter
/**
 * Created by lyd10892 on 2016/8/23.
 */

public class HotelEntityAdapter extends SectionedRecyclerViewAdapter<HeaderHolder, DescHolder, RecyclerView.ViewHolder> {

    public ArrayList<HotelEntity.TagsEntity> allTagList;
    private Context mContext;
    private LayoutInflater mInflater;
    private SparseBooleanArray mBooleanMap;//記錄下哪一個section是被打開的

    public HotelEntityAdapter(Context context) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mBooleanMap = new SparseBooleanArray();
    }

    public void setData(ArrayList<HotelEntity.TagsEntity> allTagList) {
        this.allTagList = allTagList;
        notifyDataSetChanged();
    }

    @Override
    protected int getSectionCount() {
        return HotelUtils.isEmpty(allTagList) ? 0 : allTagList.size();
    }

    @Override
    protected int getItemCountForSection(int section) {
        int count = allTagList.get(section).tagInfoList.size();
        if (count >= 8 && !mBooleanMap.get(section)) {
            count = 8;
        }

        return HotelUtils.isEmpty(allTagList.get(section).tagInfoList) ? 0 : count;
    }

    //是否有footer佈局
    @Override
    protected boolean hasFooterInSection(int section) {
        return false;
    }

    @Override
    protected HeaderHolder onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType) {
        return new HeaderHolder(mInflater.inflate(R.layout.hotel_title_item, parent, false));
    }

    @Override
    protected RecyclerView.ViewHolder onCreateSectionFooterViewHolder(ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    protected DescHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
        return new DescHolder(mInflater.inflate(R.layout.hotel_desc_item, parent, false));
    }

    @Override
    protected void onBindSectionHeaderViewHolder(final HeaderHolder holder, final int section) {
        holder.openView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                boolean isOpen = mBooleanMap.get(section);
                String text = isOpen ? "展開" : "關閉";
                mBooleanMap.put(section, !isOpen);
                holder.openView.setText(text);
                notifyDataSetChanged();
            }
        });

        holder.titleView.setText(allTagList.get(section).tagsName);
        holder.openView.setText(mBooleanMap.get(section) ? "關閉" : "展開");

    }

    @Override
    protected void onBindSectionFooterViewHolder(RecyclerView.ViewHolder holder, int section) {

    }

    @Override
    protected void onBindItemViewHolder(DescHolder holder, int section, int position) {
        holder.descView.setText(allTagList.get(section).tagInfoList.get(position).tagName);

    }
}

 

這裏面有幾個很重要的方法也是須要咱們必須重寫的,是咱們實現效果的關鍵ide

    protected abstract int getSectionCount();

    protected abstract int getItemCountForSection(int section);

    protected abstract boolean hasFooterInSection(int section);

    protected abstract H  onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType);

    protected abstract F  onCreateSectionFooterViewHolder(ViewGroup parent, int viewType);

    protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);

    protected abstract void onBindSectionHeaderViewHolder(H holder, int section);

    protected abstract void onBindSectionFooterViewHolder(F holder, int section);

   protected abstract void onBindItemViewHolder(VH holder, int section, int position); 

 

接下來咱們詳細分析這幾個方法存在的具體意義 
不過在說以前咱們須要看一下咱們的數據結構,這個也很重要佈局

public class HotelEntity {
    /**
     * 要注意這個類的數據結構,很重要,直接決定了咱們能不能實現分組展現
     */

    public ArrayList<TagsEntity> allTagsList;

    public class TagsEntity {
        public String tagsName;
        public ArrayList<TagInfo> tagInfoList;

        public class TagInfo {
            public String tagName;
        }
    }

}

 

這個方法主要是用來計算咱們一共有多少個section須要展現,返回值是咱們最外稱list的大小,在咱們的示例圖中,對應的爲熱門品牌—商業區—熱門景點 等,對應的數據是咱們的allTagList學習

protected abstract int getSectionCount();

 

這個方法是用來展現content內容區域,返回值是咱們須要展現多少內容,在本例中,咱們超過8條數據只展現8條,點擊展開後就會展現所有數據,邏輯就在這裏控制。 對應數據結構爲tagInfoList

protected abstract int getItemCountForSection(int section);

 

判斷是否須要底部footer佈局,在該例中,咱們並不須要顯示footer,因此默認返回false就能夠,若是你對應的section須要展現footer佈局,那麼就在對應的section返回true就好了

protected abstract boolean hasFooterInSection(int section);

 

咱們要單獨說一下這個方法,這裏有一個section和position ,有些人可能會弄混 
section是區域,也就是咱們最外層的index,position是每一個section對應的內容數據的position

@Override
    protected void onBindItemViewHolder(DescHolder holder, int section, int position) {
        holder.descView.setText(allTagList.get(section).tagInfoList.get(position).tagName);

    } 

 

至於下面的onCreateViewHolder ,onBindViewHolder很少作解釋了,若是你用過recyclerView,使用方法是同樣的,無非是渲染布局,綁定數據

  • 展現數據 
    基本上,若是上面的adapter邏輯寫完,咱們的佈局算是完成了,首頁代碼以下
public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private HotelEntityAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        mAdapter = new HotelEntityAdapter(this);
        GridLayoutManager manager = new GridLayoutManager(this,4);//咱們須要網格式的佈局
        //設置header佔據的空間
        manager.setSpanSizeLookup(new SectionedSpanSizeLookup(mAdapter,manager));
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);
        HotelEntity entity = JsonUtils.analysisJsonFile(this,"json");
        mAdapter.setData(entity.allTagsList);
    }
}

 

代碼裏有一段很重要的註釋,設置header佔據的空間,沒錯,由於咱們要仿造header的效果,咱們設置的manager是GridLayoutManager,設置的每一行item數量是4,若是不重寫該方法,那麼header顯示就會出錯,核心代碼以下:

/**
 * A SpanSizeLookup to draw section headers or footer spanning the whole width of the RecyclerView
 * when using a GridLayoutManager
 *
 * 這個類是用來自定義每一個item須要佔據的空間
 *
 *
 */
public class SectionedSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {

    protected SectionedRecyclerViewAdapter<?, ?, ?> adapter = null;
    protected GridLayoutManager layoutManager = null;

    public SectionedSpanSizeLookup(SectionedRecyclerViewAdapter<?, ?, ?> adapter, GridLayoutManager layoutManager) {
        this.adapter = adapter;
        this.layoutManager = layoutManager;
    }

    @Override
    public int getSpanSize(int position) {

        //header和footer佔據的是全屏
        if(adapter.isSectionHeaderPosition(position) || adapter.isSectionFooterPosition(position)){
            return layoutManager.getSpanCount();
        }else{
            return 1;//其餘默認1
        }

    }
} 

 

最重要的是getSpanSize方法,只要是header或者是footer就返回咱們設置的網格數,也就是4,表明header和footer佔據4個網格的空間,其餘佔據1個 
這樣,咱們就能夠完美的展現咱們須要的佈局了 
當前咱們的demo是網格佈局的,你也能夠設置流式佈局,只須要設置不一樣的layoutmanager就能夠了 
好比下圖的效果咱們也能夠實現 
這裏寫圖片描述

核心代碼已經解釋完畢,固然最核心的是SectionedRecyclerViewAdapter這個類,這個類好好學習一下,會學到不少,也會實現不少app常見的佈局效果,好比設置不一樣的viewType展示更復雜的佈局 
最後,看一下代碼結構: 
這裏寫圖片描述 
最後囉嗦一句,寫博客比寫代碼難多了。 
demo已經上傳到github了,歡迎fork 
https://github.com/nbwzlyd/SectionRecyclerViewDemo

相關文章
相關標籤/搜索