美文网首页RecyclerViewAndroidRecyclerView
RecyclerView从入门到入神—为RecyclerView

RecyclerView从入门到入神—为RecyclerView

作者: Android埋坑的艺术 | 来源:发表于2017-02-17 00:14 被阅读2740次
    cover.jpg

    上一篇 初识RecyclerView,是RecylerView的入门篇,主要讲解了什么是RecylerView,RecylerView的优势以及三种布局管理器的区别,我们对RecyclerVIew有了初步了解

    本篇是Recylerview的进阶篇,我将一步步带领大家为RecylerView添加HeaderView, FooterView, EmptyView, 以及完成对GloriousRecyclerView的封装,使我们的开发更加便捷

    为RecyclerView添加HeaderView


    参考ListView

    我们使用ListView的时候知道,要为ListView添加HeaderView是非常方便的,我们只需要调用ListView的addHeaderView(View v)方法即可。

    于是,果断翻看RecyclerView的源码

    RecyclerView_SourceCode_1.png RecyclerView_SourceCode_2.png

    遗憾的是,我们并没有找到添加HeaderView的方法,退而求其次

    从LayoutManager入手

    我们找到在RecyclerView的内部静态抽象类LayoutManager中有addView()方法

    RecyclerView_SourceCode_3.png

    上一篇我们已经知道,RecyclerView已经实现了三种布局管理器,这里,我们就用最简单的LinearLayoutManager来尝试下添加HeaderView

    mLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mLayoutManager.addView(LayoutInflater.from(this).inflate(R .layout.layout_header, null, false), 0);
    
    

    不幸的是,在运行时爆出空指针异常

    RecyclerView_Header_Error_1.png

    查看RecyclerView 7075行,发现holder为空

    RecyclerView_SourceCode_4.png

    而holder由7074行得来

    final ViewHolder holder  = getChildViewHolderInt(child);
    

    继续查看getChildViewHolderInt(child)方法

    RecyclerView_SourceCode_5.png

    holder是由子View的LayoutParams得来

    这个LayoutParams是RecyclerView内部静态内,里面包含了一个ViewHolder mViewHolder 成员变量

    由于我们添加的HeaderView是普通的 View / ViewGroup ,所以并没有什么ViewHolder, 于此,此路不通耶

    从ViewGroup入手


    本着不抛弃,不放弃的精神,让我们来大开脑洞吧,由于RecyclerView是继承自ViewGroup的,我们知道ViewGroup有addView(View child, int index)方法,那我们试试不妨

    mLayoutManager = new LinearLayoutManager(this, orientation, false);
    mRecyclerView.setLayoutManager(mLayoutManager);
    
    View header = LayoutInflater.from(this).inflate(R .layout.layout_header, null, false);
    mRecyclerView.addView(header, 0);
    

    悲剧的是,在运行时依然爆出空指针异常

    RecyclerView_Header_Error_2.png

    继续查看源码(这里我就不贴了,有兴趣的可以自己翻看),发现依然是缺少ViewHolder的缘故,由此看来,这条路也不通了

    从Adapter入手

    思路

    屡次受挫,确实是有点动摇军心,仿佛看不到希望,但是有句话叫做 Nerver say Nerver, 好吧,至少我们知道了一点,我们要想在RecylerView中添加任何子View,那么这个View必须要有ViewHolder

    我们在上一篇中讲到:RecyclerView的Adapter默认要求使用ViewHolder ,嗯,似乎我们找到了正道。

    我们创建RecyclerView的Adapter时,必须要实现三个方法

    RecyclerView_SourceCode_6.png

    其中

    onCreateViewHolder(ViewGroup parent, int viewType)
    

    第二个参数,int viewType,由名字我们猜测是Item的类型,如果没错的话,那么我们的Header和正常的Item就是两种类型了,那么,这个类型是怎么得来的呢?

    查看源码,叫我们参考getItemViewType(int)方法:

    public int getItemViewType(int position) {
        return 0;
    }
    

    源码里,传入一个position,默认返回0

    这里我们得到了启发,我们在自己的Adapter里:

    实现
    1. 首先定义:
    private int ITEM_TYPE_NORMAL = 0;
    private int ITEM_TYPE_HEADER = 1;
    
    1. getItemViewType()中,假如position传入0,我们的返回值返回 ITEM_TYPE_HEADER,其他的position,我们返回 ITEM_TYPE_NORMAL,这样就区分了viewType

    2. onCreateViewHolder()中,我们根据不同的viewType返回不同的ViewHolder

    3. onBindViewHolder()中,我们首先根据positon调用getItemViewType(int position)方法,得到不同的viewType,如果得到 ITEM_TYPE_HEADER ,我们直接return,如果得到 ITEM_TYPE_NORMAL,那么,由于有Header的存在,我们在设置Item的数据时,应该把position -1

    4. getItemCount()中,由于我们多了HeaderView,所以要在真实数据个数中+1

    效果展示
    RecyclerView_With_Header.png

    为RecyclerView添加EmptyView, FooterView


    为RecyclerView添加FooterView,EmptyView 其实和添加HeaderView是类似的,这里就不多言了,直接把同时添加Header,Footer和Empty View的源码贴上

    public class DemoAdapter extends RecyclerView.
            Adapter<RecyclerView.ViewHolder> {
    
        private List<String> mDatas = new ArrayList<>();
        private Context mContext;
        private View mHeaderView;
        private View mFooterView;
        private View mEmptyView;
    
        private int ITEM_TYPE_NORMAL = 0;
        private int ITEM_TYPE_HEADER = 1;
        private int ITEM_TYPE_FOOTER = 2;
        private int ITEM_TYPE_EMPTY = 3;
    
    
        public DemoAdapter(Context context) {
            mContext = context;
        }
    
        public void setDatas(List<String> datas) {
            mDatas = datas;
            notifyDataSetChanged();
        }
    
        // 创建视图
        @Override
        public RecyclerView.ViewHolder
        onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == ITEM_TYPE_HEADER) {
                return new ViewHolder(mHeaderView);
            } else if (viewType == ITEM_TYPE_EMPTY) {
                return new ViewHolder(mEmptyView);
            } else if (viewType == ITEM_TYPE_FOOTER) {
                return new ViewHolder(mFooterView);
            } else {
                View v = LayoutInflater.from(mContext)
                        .inflate(
                                R.layout.layout_recyclerview_item_view,
                                parent,
                                false);
                return new ViewHolder(v);
            }
        }
    
        @Override
        public int getItemViewType(int position) {
            if (null != mHeaderView && position == 0) {
                return ITEM_TYPE_HEADER;
            }
            if (null != mFooterView
                    && position == getItemCount() - 1) {
                return ITEM_TYPE_FOOTER;
            }
            if (null != mEmptyView && mDatas.size() == 0){
                return ITEM_TYPE_EMPTY;
            }
            return ITEM_TYPE_NORMAL;
    
        }
    
        // 为Item绑定数据
        @Override
        public void onBindViewHolder
        (RecyclerView.ViewHolder holder, int position) {
            int type = getItemViewType(position);
            if (type == ITEM_TYPE_HEADER
                    || type == ITEM_TYPE_FOOTER
                    || type == ITEM_TYPE_EMPTY) {
                return;
            }
            int realPos = getRealItemPosition(position);
            ((DemoAdapter.ViewHolder) holder)
                    .mTextView
                    .setText(mDatas.get(realPos));
        }
    
        private int getRealItemPosition(int position) {
            if (null != mHeaderView) {
                return position - 1;
            }
            return position;
        }
    
        @Override
        public int getItemCount() {
            int itemCount = mDatas.size();
            if (null != mEmptyView && itemCount == 0) {
                itemCount++;
            }
            if (null != mHeaderView) {
                itemCount++;
            }
            if (null != mFooterView) {
                itemCount++;
            }
            return itemCount;
        }
    
        public void addHeaderView(View view) {
            mHeaderView = view;
            notifyItemInserted(0);
        }
    
        public void addFooterView(View view) {
            mFooterView = view;
            notifyItemInserted(getItemCount() - 1);
        }
    
        public void setEmptyView(View view) {
            mEmptyView = view;
            notifyDataSetChanged();
        }
    
        class ViewHolder extends RecyclerView.ViewHolder {
            TextView mTextView;
    
            ViewHolder(View v) {
                super(v);
                mTextView = (TextView)
                        v.findViewById(R.id.item_title);
            }
        }
    }
    

    Activity中

    View footer = LayoutInflater.from(this).inflate(R.layout.layout_footer, mRecyclerView, false);
    View header = LayoutInflater.from(this).inflate(R.layout.layout_header, mRecyclerView, false);
    View empty = LayoutInflater.from(this).inflate(R.layout.layout_empty, mRecyclerView, false);
    
    adapter.addHeaderView(header);
    adapter.addFooterView(footer);
    adapter.setEmptyView(empty);
    

    下图展示的是同时添加了Header和Footer View

    RecyclerView_With_Header_Footer.png

    下图展示的是Empty View

    RecyclerView_With_Empty.png

    封装


    其实上面的代码在一般情况对添加HeaderView ,FooterView ,Empty View已经够用了,不过麻烦的是,我们在不同的地方,需要重复Copy一些代码,显然,这是不能容忍的

    那么,我们能不能像ListView那样 把什么addHeaderView(),addFooterView(),setEmptyView()直接封装在RecyclerView里呢?答案是肯定的!

    技巧

    这里我们用到了装饰模式,我们用自定义的RecyclerView中的内部类Adapter来装饰原始从Activity传入的Adapter,我们可以毫无影响之前的逻辑来添加这些额外的Header,Footer,Empty View

    实现

    废话不多说,直接上源码,拿走,不谢

    GloriousRecyclerView

    /*
     * Copyright (C) 2017 CXP 277371483@qq.com
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.xpc.recylerviewdemo;
    
    import android.content.Context;
    import android.support.annotation.Nullable;
    import android.support.v7.widget.RecyclerView;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    
    /**
     * Created on 17-2-14
     *
     * @author cxp
     */
    
    public class GloriousRecyclerView extends RecyclerView {
    
        private View mHeaderView;
        private View mFooterView;
        private View mEmptyView;
        private GloriousAdapter mGloriousAdapter;
    
        public GloriousRecyclerView(Context context) {
            super(context);
        }
    
        public GloriousRecyclerView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public GloriousRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public void addHeaderView(View view) {
            mHeaderView = view;
            mGloriousAdapter.notifyItemInserted(0);
        }
    
        public void addFooterView(View view) {
            mFooterView = view;
            mGloriousAdapter.notifyItemInserted(mGloriousAdapter.getItemCount() - 1);
        }
    
        public void setEmptyView(View view) {
            mEmptyView = view;
            mGloriousAdapter.notifyDataSetChanged();
        }
    
        @Override
        public void setAdapter(Adapter adapter) {
            if (adapter != null) {
                mGloriousAdapter = new GloriousAdapter(adapter);
            }
            super.setAdapter(mGloriousAdapter);
        }
    
        private class GloriousAdapter extends RecyclerView.Adapter<ViewHolder> {
    
            private Adapter mOriginalAdapter;
            private int ITEM_TYPE_NORMAL = 0;
            private int ITEM_TYPE_HEADER = 1;
            private int ITEM_TYPE_FOOTER = 2;
            private int ITEM_TYPE_EMPTY = 3;
    
            //聪明的人会发现我们这里用了一个装饰模式
            public GloriousAdapter(Adapter originalAdapter) {
                mOriginalAdapter = originalAdapter;
            }
    
            @Override
            public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                if (viewType == ITEM_TYPE_HEADER) {
                    return new GloriousViewHolder(mHeaderView);
                } else if (viewType == ITEM_TYPE_EMPTY) {
                    return new GloriousViewHolder(mEmptyView);
                } else if (viewType == ITEM_TYPE_FOOTER) {
                    return new GloriousViewHolder(mFooterView);
                } else {
                    return mOriginalAdapter.onCreateViewHolder(parent, viewType);
                }
            }
    
            @Override
            public void onBindViewHolder(ViewHolder holder, int position) {
                int type = getItemViewType(position);
                if (type == ITEM_TYPE_HEADER || type == ITEM_TYPE_FOOTER || type == ITEM_TYPE_EMPTY) {
                    return;
                }
                int realPosition = getRealItemPosition(position);
                mOriginalAdapter.onBindViewHolder(holder, realPosition);
            }
    
            @Override
            public int getItemCount() {
                int itemCount = mOriginalAdapter.getItemCount();
                //加上其他各种View
                if (null != mEmptyView && itemCount == 0) itemCount++;
                if (null != mHeaderView) itemCount++;
                if (null != mFooterView) itemCount++;
                return itemCount;
            }
    
            @Override
            public int getItemViewType(int position) {
                if (null != mHeaderView && position == 0) return ITEM_TYPE_HEADER;
                if (null != mFooterView && position == getItemCount() - 1) return ITEM_TYPE_FOOTER;
                if (null != mEmptyView && mOriginalAdapter.getItemCount() == 0) return ITEM_TYPE_EMPTY;
                return ITEM_TYPE_NORMAL;
            }
    
            private int getRealItemPosition(int position) {
                if (null != mHeaderView) {
                    return position - 1;
                }
                return position;
            }
    
            /**
             * ViewHolder 是一个抽象类
             */
            class GloriousViewHolder extends ViewHolder {
    
                GloriousViewHolder(View itemView) {
                    super(itemView);
                }
            }
        }
    }
    

    Activity

    public class GloriousActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_glorious_recycler_view);
            GloriousRecyclerView recyclerView = (GloriousRecyclerView) findViewById(R.id.recycler_view);
            RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this,     RecyclerView.VERTICAL, false);
            recyclerView.setLayoutManager(layoutManager);
            
            NormalAdapter adapter = new NormalAdapter(this);
            adapter.setDatas(constructTestDatas());
            
            View footer = LayoutInflater.from(this).inflate(R.layout.layout_footer, recyclerView, false);
            View header = LayoutInflater.from(this).inflate(R.layout.layout_header, recyclerView, false);
            View empty = LayoutInflater.from(this).inflate(R.layout.layout_empty, recyclerView, false);
            
            recyclerView.setAdapter(adapter);
            recyclerView.addHeaderView(header);
            recyclerView.addFooterView(footer);
            recyclerView.setEmptyView(empty);
        }
    
        private List<String> constructTestDatas() {
            List<String> datas = new ArrayList<>();
            datas.add("刘一");
            //...
            datas.add("郑十");
            return datas;
        }
    }
    

    layout

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#888">
    
        <com.xpc.recylerviewdemo.GloriousRecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    
    </RelativeLayout>
    

    总结


    本章一步步带领大家为RecylerView添加HeaderView,FooterView,EmptyView,以及完成了对GloriousRecyclerView的封装,使我们的开发更加的便捷。

    网上或许有类似的解决方案,但是都没有讲解为什么要这样做,正所谓知其然不知其所以然。如果你认真读了这篇文章,相信会对你有所帮助。

    上一篇

    RecyclerView从入门到入神——初识RecyclerView(一)

    下一篇

    RecyclerView从入门到入神——为RecyclerView添加下拉刷新和上拉加载更多(三)

    相关文章

      网友评论

      • LiuK1003:java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
        at java.util.ArrayList.get(ArrayList.java:411)
        at com.chad.library.adapter.base.BaseQuickAdapter.onBindViewHolder(BaseQuickAdapter.java:818)
        at com.chad.library.adapter.base.BaseQuickAdapter.onBindViewHolder(BaseQuickAdapter.java:67)
        at .widget.GloriousRecyclerView$GloriousAdapter.onBindViewHolder(GloriousRecyclerView.java:92)
        LiuK1003:@Android埋坑的艺术 嗯嗯 谢谢 问题已经解决了,是用了那个适配器造成的 谢谢
        Android埋坑的艺术:可以看看我的github:
        https://github.com/titanchen2000/GloriousRecyclerView
      • lesliefang:学习了
      • 闲鱼尼克:来个github地址就更棒了
        Android埋坑的艺术:@闲鱼尼克 https://github.com/titanchen2000/GloriousRecyclerView
      • 悬浮的木马:超神啊
      • 阳光的小四:牛皮啊 老哥
      • 我该怎么:请问如何处理那些警告信息
        Android埋坑的艺术: //noinspection unchecked
        mOriginalAdapter.onBindViewHolder(holder, realPosition);

        前面加那个注释就没警告了
        我该怎么: @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
        int type = getItemViewType(position);
        if (type == ITEM_TYPE_HEADER || type == ITEM_TYPE_FOOTER || type == ITEM_TYPE_EMPTY) {
        return;
        }
        int realPosition = getRealItemPosition(position);
        mOriginalAdapter.onBindViewHolder(holder, realPosition);
        }这个方法最后一行 会有警告信息,不影响使用,就是有点看着不太爽,有没有方法去掉
        Android埋坑的艺术: @我该怎么 举个例子呢
      • 七月流火_9405:6666,很强势
      • 我该怎么:大赞!
      • lsys:赞

      本文标题:RecyclerView从入门到入神—为RecyclerView

      本文链接:https://www.haomeiwen.com/subject/enghwttx.html