基於RecyclerView實現的分組顯示信息demo


發布時間:2018-11-07
 
技術:Android Studio 3.1.3+ RecyclerView+sectionRecyclerViewAdapter
 

概述

利用sectionedRecyclerViewAdapter實現分組列表的recyclerView

詳細

 

一、效果

先上效果圖給大家看看,好有一個整體的認識,(

我的github地址:

https://github.com/nbwzlyd/SectionRecyclerViewDemo

當然如果你覺得幫助到你的話,可以以資鼓勵哦

 

123.jpg

二、實現過程

 

效果就是這樣的,但是不僅僅局限於這種布局,事實上只要是三段式布局,都可以通過該demo的學習來實現,什么是三段式布局呢,就是有header -content-footer類型的布局,畫一個圖來解釋

20160823171610462.jpg

 

比如下面這個圖就可以

20160823172440278.jpg

可以看到,用途還是很廣泛的,所以很需要我們去學習一下

 

怎么去實現

gitbub上有一個很牛逼的類,但是貌似知道的人很少,名字叫做SectionedRecyclerViewAdapter ,地址https://github.com/truizlop/SectionedRecyclerView 但是今天我們不去研究她是怎么實現的,我們來研究他怎么用就行了

 

繼承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);
    }
}

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

    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就可以了

比如下圖的效果我們也可以實現

20160823183738974.jpg

 

核心代碼已經解釋完畢,當然最核心的是SectionedRecyclerViewAdapter這個類,這個類好好學習一下,會學到很多,也會實現很多app常見的布局效果,比如設置不同的viewType展現更復雜的布局

三、項目結構

最后,看一下代碼結構:

20160823183129076.jpg

四、其他補充

 

最后啰嗦一句,寫博客比寫代碼難多了。

我的github地址:

https://github.com/nbwzlyd/SectionRecyclerViewDemo

記得給星星哦。



注:本文著作權歸作者,由demo大師發表,拒絕轉載,轉載需要作者授權


免責聲明!

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



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