Is there an addHeaderView equivalent for RecyclerView?

后端 未结 19 1602
独厮守ぢ
独厮守ぢ 2020-11-21 23:35

I\'m looking for an equivalent to addHeaderView for a recycler view. Basically I want to have an image with 2 buttons be added as a header to the listview. Is there a differ

相关标签:
19条回答
  • 2020-11-22 00:30

    You can achieve it using the library SectionedRecyclerViewAdapter, it has the concept of "Sections", where which Section has a Header, Footer and Content (list of items). In your case you might only need one Section but you can have many:

    1) Create a custom Section class:

    class MySection extends StatelessSection {
    
        List<String> myList = Arrays.asList(new String[] {"Item1", "Item2", "Item3" });
    
        public MySection() {
            // call constructor with layout resources for this Section header, footer and items 
            super(R.layout.section_header, R.layout.section_footer,  R.layout.section_item);
        }
    
        @Override
        public int getContentItemsTotal() {
            return myList.size(); // number of items of this section
        }
    
        @Override
        public RecyclerView.ViewHolder getItemViewHolder(View view) {
            // return a custom instance of ViewHolder for the items of this section
            return new MyItemViewHolder(view);
        }
    
        @Override
        public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
            MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
    
            // bind your view here
            itemHolder.tvItem.setText(myList.get(position));
        }
    }
    

    2) Create a custom ViewHolder for the items:

    class MyItemViewHolder extends RecyclerView.ViewHolder {
    
        private final TextView tvItem;
    
        public MyItemViewHolder(View itemView) {
            super(itemView);
    
            tvItem = (TextView) itemView.findViewById(R.id.tvItem);
        }
    }
    

    3) Set up your ReclyclerView with the SectionedRecyclerViewAdapter

    // Create an instance of SectionedRecyclerViewAdapter 
    SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
    
    MySection mySection = new MySection();
    
    // Add your Sections
    sectionAdapter.addSection(mySection);
    
    // Set up your RecyclerView with the SectionedRecyclerViewAdapter
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
    recyclerView.setAdapter(sectionAdapter);
    
    0 讨论(0)
  • 2020-11-22 00:30

    If you want the header to be easily reused across multiple lists, take a look at the version 1.2.0 of recyclerview library. It introduces ConcatAdapter class which concatenates multiple adapters into a single one. So you can create a header adapter and easily combine it with any other adapter, like:

    myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter)
    

    The announcement article contains a sample how to display a loading progress in header and footer using ConcatAdapter.

    For the moment when I post this answer the version 1.2.0 of the library is in alpha stage, so the api might change. You can check the status here.

    0 讨论(0)
  • 2020-11-22 00:32

    Easy and reusable ItemDecoration

    Static headers can easily be added with an ItemDecoration and without any further changes.

    // add the decoration. done.
    HeaderDecoration headerDecoration = new HeaderDecoration(/* init */);
    recyclerView.addItemDecoration(headerDecoration);
    

    The decoration is also reusable since there is no need to modify the adapter or the RecyclerView at all.

    The sample code provided below will require a view to add to the top which can just be inflated like everything else. It can look like this:

    Why static?

    If you just have to display text and images this solution is for you—there is no possibility for user interaction like buttons or view pagers, since it will just be drawn to top of your list.

    Empty list handling

    If there is no view to decorate, the decoration will not be drawn. You will still have to handle an empty list yourself. (One possible workaround would be to add a dummy item to the adapter.)

    The code

    You can find the full source code here on GitHub including a Builder to help with the initialization of the decorator, or just use the code below and supply your own values to the constructor.

    Please be sure to set a correct layout_height for your view. e.g. match_parent might not work properly.

    public class HeaderDecoration extends RecyclerView.ItemDecoration {
    
        private final View mView;
        private final boolean mHorizontal;
        private final float mParallax;
        private final float mShadowSize;
        private final int mColumns;
        private final Paint mShadowPaint;
    
        public HeaderDecoration(View view, boolean scrollsHorizontally, float parallax, float shadowSize, int columns) {
            mView = view;
            mHorizontal = scrollsHorizontally;
            mParallax = parallax;
            mShadowSize = shadowSize;
            mColumns = columns;
    
            if (mShadowSize > 0) {
                mShadowPaint = new Paint();
                mShadowPaint.setShader(mHorizontal ?
                        new LinearGradient(mShadowSize, 0, 0, 0,
                                new int[]{Color.argb(55, 0, 0, 0), Color.argb(55, 0, 0, 0), Color.argb(3, 0, 0, 0)},
                                new float[]{0f, .5f, 1f},
                                Shader.TileMode.CLAMP) :
                        new LinearGradient(0, mShadowSize, 0, 0,
                                new int[]{Color.argb(55, 0, 0, 0), Color.argb(55, 0, 0, 0), Color.argb(3, 0, 0, 0)},
                                new float[]{0f, .5f, 1f},
                                Shader.TileMode.CLAMP));
            } else {
                mShadowPaint = null;
            }
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);
            // layout basically just gets drawn on the reserved space on top of the first view
            mView.layout(parent.getLeft(), 0, parent.getRight(), mView.getMeasuredHeight());
    
            for (int i = 0; i < parent.getChildCount(); i++) {
                View view = parent.getChildAt(i);
                if (parent.getChildAdapterPosition(view) == 0) {
                    c.save();
                    if (mHorizontal) {
                        c.clipRect(parent.getLeft(), parent.getTop(), view.getLeft(), parent.getBottom());
                        final int width = mView.getMeasuredWidth();
                        final float left = (view.getLeft() - width) * mParallax;
                        c.translate(left, 0);
                        mView.draw(c);
                        if (mShadowSize > 0) {
                            c.translate(view.getLeft() - left - mShadowSize, 0);
                            c.drawRect(parent.getLeft(), parent.getTop(), mShadowSize, parent.getBottom(), mShadowPaint);
                        }
                    } else {
                        c.clipRect(parent.getLeft(), parent.getTop(), parent.getRight(), view.getTop());
                        final int height = mView.getMeasuredHeight();
                        final float top = (view.getTop() - height) * mParallax;
                        c.translate(0, top);
                        mView.draw(c);
                        if (mShadowSize > 0) {
                            c.translate(0, view.getTop() - top - mShadowSize);
                            c.drawRect(parent.getLeft(), parent.getTop(), parent.getRight(), mShadowSize, mShadowPaint);
                        }
                    }
                    c.restore();
                    break;
                }
            }
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            if (parent.getChildAdapterPosition(view) < mColumns) {
                if (mHorizontal) {
                    if (mView.getMeasuredWidth() <= 0) {
                        mView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
                                View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
                    }
                    outRect.set(mView.getMeasuredWidth(), 0, 0, 0);
                } else {
                    if (mView.getMeasuredHeight() <= 0) {
                        mView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
                                View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
                    }
                    outRect.set(0, mView.getMeasuredHeight(), 0, 0);
                }
            } else {
                outRect.setEmpty();
            }
        }
    }
    

    Please note: The GitHub project is my personal playground. It is not thorougly tested, which is why there is no library yet.

    What does it do?

    An ItemDecoration is additional drawing to an item of a list. In this case, a decoration is drawn to the top of the first item.

    The view gets measured and laid out, then it is drawn to the top of the first item. If a parallax effect is added it will also be clipped to the correct bounds.

    0 讨论(0)
  • 2020-11-22 00:38

    Maybe wrap header and recyclerview into a coordinatorlayout:

    <android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:elevation="0dp">
    
        <View
            android:id="@+id/header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll" />
    
    </android.support.design.widget.AppBarLayout>
    
    <android.support.v7.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    

    0 讨论(0)
  • 2020-11-22 00:38

    Probably http://alexzh.com/tutorials/multiple-row-layouts-using-recyclerview/ will help. It uses only RecyclerView and CardView. Here is an adapter:

    public class DifferentRowAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private List<CityEvent> mList;
        public DifferentRowAdapter(List<CityEvent> list) {
            this.mList = list;
        }
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view;
            switch (viewType) {
                case CITY_TYPE:
                    view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_city, parent, false);
                    return new CityViewHolder(view);
                case EVENT_TYPE:
                    view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_event, parent, false);
                    return new EventViewHolder(view);
            }
            return null;
        }
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            CityEvent object = mList.get(position);
            if (object != null) {
                switch (object.getType()) {
                    case CITY_TYPE:
                        ((CityViewHolder) holder).mTitle.setText(object.getName());
                        break;
                    case EVENT_TYPE:
                        ((EventViewHolder) holder).mTitle.setText(object.getName());
                        ((EventViewHolder) holder).mDescription.setText(object.getDescription());
                        break;
                }
            }
        }
        @Override
        public int getItemCount() {
            if (mList == null)
                return 0;
            return mList.size();
        }
        @Override
        public int getItemViewType(int position) {
            if (mList != null) {
                CityEvent object = mList.get(position);
                if (object != null) {
                    return object.getType();
                }
            }
            return 0;
        }
        public static class CityViewHolder extends RecyclerView.ViewHolder {
            private TextView mTitle;
            public CityViewHolder(View itemView) {
                super(itemView);
                mTitle = (TextView) itemView.findViewById(R.id.titleTextView);
            }
        }
        public static class EventViewHolder extends RecyclerView.ViewHolder {
            private TextView mTitle;
            private TextView mDescription;
            public EventViewHolder(View itemView) {
                super(itemView);
                mTitle = (TextView) itemView.findViewById(R.id.titleTextView);
                mDescription = (TextView) itemView.findViewById(R.id.descriptionTextView);
            }
        }
    }
    

    And here's an entity:

    public class CityEvent {
        public static final int CITY_TYPE = 0;
        public static final int EVENT_TYPE = 1;
        private String mName;
        private String mDescription;
        private int mType;
        public CityEvent(String name, String description, int type) {
            this.mName = name;
            this.mDescription = description;
            this.mType = type;
        }
        public String getName() {
            return mName;
        }
        public void setName(String name) {
            this.mName = name;
        }
        public String getDescription() {
            return mDescription;
        }
        public void setDescription(String description) {
            this.mDescription = description;
        }
        public int getType() {
            return mType;
        }
        public void setType(int type) {
            this.mType = type;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:38

    you can create addHeaderView and use

    adapter.addHeaderView(View).

    This code build the addHeaderView for more then one header. the headers should have:

    android:layout_height="wrap_content"

    public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private static final int TYPE_ITEM = -1;
        public class MyViewSHolder extends RecyclerView.ViewHolder {
            public MyViewSHolder (View view) {
                super(view);
            }
            // put you code. for example:
            View mView;
            ...
        }
    
        public class ViewHeader extends RecyclerView.ViewHolder {
            public ViewHeader(View view) {
                super(view);
            }
        }
    
        private List<View> mHeaderViews = new ArrayList<>();
        public void addHeaderView(View headerView) {
            mHeaderViews.add(headerView);
        }
    
        @Override
        public int getItemCount() {
           return ... + mHeaderViews.size();
        }
    
        @Override
        public int getItemViewType(int position) {
            if (mHeaderViews.size() > position) {
                return position;
            }
    
            return TYPE_ITEM;
        }
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType != TYPE_ITEM) {
                //inflate your layout and pass it to view holder
                return new ViewHeader(mHeaderViews.get(viewType));
            }
            ...
        }
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int basePosition1) {
            if (holder instanceof ViewHeader) {
                return;
            }
            int basePosition = basePosition1 -  mHeaderViews.size();
            ...
        }
    }
    
    0 讨论(0)
提交回复
热议问题