Is there an addHeaderView equivalent for RecyclerView?

后端 未结 19 1603
独厮守ぢ
独厮守ぢ 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:20

    There isn't an easy way like listview.addHeaderView() but you can achieve this by adding a type to your adapter for header.

    Here is an example

    public class HeaderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private static final int TYPE_HEADER = 0;
        private static final int TYPE_ITEM = 1;
        String[] data;
    
        public HeaderAdapter(String[] data) {
            this.data = data;
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == TYPE_ITEM) {
                //inflate your layout and pass it to view holder
                return new VHItem(null);
            } else if (viewType == TYPE_HEADER) {
                //inflate your layout and pass it to view holder
                return new VHHeader(null);
            }
    
            throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (holder instanceof VHItem) {
                String dataItem = getItem(position);
                //cast holder to VHItem and set data
            } else if (holder instanceof VHHeader) {
                //cast holder to VHHeader and set data for header.
            }
        }
    
        @Override
        public int getItemCount() {
            return data.length + 1;
        }
    
        @Override
        public int getItemViewType(int position) {
            if (isPositionHeader(position))
                return TYPE_HEADER;
    
            return TYPE_ITEM;
        }
    
        private boolean isPositionHeader(int position) {
            return position == 0;
        }
    
        private String getItem(int position) {
            return data[position - 1];
        }
    
        class VHItem extends RecyclerView.ViewHolder {
            TextView title;
    
            public VHItem(View itemView) {
                super(itemView);
            }
        }
    
        class VHHeader extends RecyclerView.ViewHolder {
            Button button;
    
            public VHHeader(View itemView) {
                super(itemView);
            }
        }
    }
    

    link to gist -> here

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

    Going to show you to make header with items in a Recycler view.

    Step 1- Add dependency into your gradle file.

    compile 'com.android.support:recyclerview-v7:23.2.0'
    // CardView
    compile 'com.android.support:cardview-v7:23.2.0'
    

    Cardview is used for decoration purpose.

    Step2- Make three xml files. One for main activity.Second for Header layout.Third for list item layout.

    activity_main.xml

    <android.support.v7.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    

    header.xml

    <android.support.v7.widget.CardView
        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="wrap_content"
        app:cardElevation="2dp">
    
        <TextView
            android:id="@+id/txtHeader"
            android:gravity="center"
            android:textColor="#000000"
            android:textSize="@dimen/abc_text_size_large_material"
            android:background="#DCDCDC"
            android:layout_width="match_parent"
            android:layout_height="50dp" />
    
    </android.support.v7.widget.CardView>
    

    list.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        xmlns:app="http://schemas.android.com/tools"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:cardElevation="1dp">
    
            <TextView
                android:id="@+id/txtName"
                android:text="abc"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
    
        </android.support.v7.widget.CardView>
    
    </LinearLayout>
    

    Step 3- Create three bean classes.

    Header.java

    public class Header extends ListItem {
        private String header;
    
        public String getHeader() {
            return header;
        }
        public void setHeader(String header) {
            this.header = header;
        }
    }
    

    ContentItem.java

    public class ContentItem extends ListItem {
    
        private String name;
        private String rollnumber;
    
        @Override
        public String getName() {
            return name;
        }
    
        @Override
        public void setName(String name) {
            this.name = name;
        }
    
        public String getRollnumber() {
            return rollnumber;
        }
    
        public void setRollnumber(String rollnumber) {
            this.rollnumber = rollnumber;
        }
    }
    

    ListItem.java

    public class ListItem {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        private int id;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    }
    

    Step 4- Create an adapter named MyRecyclerAdapter.java

    public class MyRecyclerAdapter extends  RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private static final int TYPE_HEADER = 0;
        private static final int TYPE_ITEM = 1;
    
        //Header header;
        List<ListItem> list;
        public MyRecyclerAdapter(List<ListItem> headerItems) {
            this.list = headerItems;
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            if (viewType == TYPE_HEADER) {
                View v = inflater.inflate(R.layout.header, parent, false);
                return  new VHHeader(v);
            } else {
                View v = inflater.inflate(R.layout.list, parent, false);
                return new VHItem(v);
            }
            throw new IllegalArgumentException();
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (holder instanceof VHHeader) {
               // VHHeader VHheader = (VHHeader)holder;
                Header  currentItem = (Header) list.get(position);
                VHHeader VHheader = (VHHeader)holder;
                VHheader.txtTitle.setText(currentItem.getHeader());
            } else if (holder instanceof VHItem) 
                ContentItem currentItem = (ContentItem) list.get(position);
                VHItem VHitem = (VHItem)holder;
                VHitem.txtName.setText(currentItem.getName());
            }
        }
    
        @Override
        public int getItemViewType(int position) {
            if (isPositionHeader(position))
                return TYPE_HEADER;
            return TYPE_ITEM;
        }
    
        private boolean isPositionHeader(int position) {
            return list.get(position) instanceof Header;
        }
    
        @Override
        public int getItemCount() {
            return list.size();
        }
    
        class VHHeader extends RecyclerView.ViewHolder{
            TextView txtTitle;
            public VHHeader(View itemView) {
                super(itemView);
                this.txtTitle = (TextView) itemView.findViewById(R.id.txtHeader);
            }
        }
        class VHItem extends RecyclerView.ViewHolder{
            TextView txtName;
            public VHItem(View itemView) {
                super(itemView);
                this.txtName = (TextView) itemView.findViewById(R.id.txtName);
            }
        }
    }
    

    Step 5- In MainActivity add the following code:

    public class MainActivity extends AppCompatActivity {
        RecyclerView recyclerView;
        List<List<ListItem>> arraylist;
        MyRecyclerAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            recyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
            LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
            adapter = new MyRecyclerAdapter(getList());
            recyclerView.setLayoutManager(linearLayoutManager);
            recyclerView.setAdapter(adapter);
        }
    
        private ArrayList<ListItem> getList() {
            ArrayList<ListItem> arrayList = new ArrayList<>();
            for(int j = 0; j <= 4; j++) {
                Header header = new Header();
                header.setHeader("header"+j);
                arrayList.add(header);
                for (int i = 0; i <= 3; i++) {
                    ContentItem item = new ContentItem();
                    item.setRollnumber(i + "");
                    item.setName("A" + i);
                    arrayList.add(item);
                }
            }
            return arrayList;
        }
    
    }
    

    The function getList() is dynamically generating the data for the headers and for list items.

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

    You can just place your header and your RecyclerView in a NestedScrollView:

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            >
    
            <include
                layout="@layout/your_header"/>
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/list_recylclerview"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                />
    
        </LinearLayout>
    
    </android.support.v4.widget.NestedScrollView>
    

    In order for scrolling to work correctly, you need to disable nested scrolling on your RecyclerView:

    myRecyclerView.setNestedScrollingEnabled(false);
    
    0 讨论(0)
  • 2020-11-22 00:23

    I have implemented the same approach proposed by EC84B4 answer, but I abstracted RecycleViewAdapter and make it easily resuable by means of interfaces.

    So in order to use my approach you should add following base classes and interfaces to your project:

    1) Interface that provides data for Adapter (collection of generic type T, and additional parameters (if needed) of generic type P)

    public interface IRecycleViewListHolder<T,P>{
                P getAdapterParameters();
                T getItem(int position);
                int getSize();
        }
    

    2) Factory for binding your items (header/item):

    public interface IViewHolderBinderFactory<T,P> {
            void bindView(RecyclerView.ViewHolder holder, int position,IRecycleViewListHolder<T,P> dataHolder);
    }
    

    3) Factory for viewHolders (header/items):

    public interface IViewHolderFactory {
        RecyclerView.ViewHolder provideInflatedViewHolder(int viewType, LayoutInflater layoutInflater,@NonNull ViewGroup parent);
    }
    

    4) Base class for Adapter with Header:

    public class RecycleViewHeaderBased<T,P> extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    
        public final static int HEADER_TYPE = 1;
        public final static int ITEM_TYPE = 0;
        private final IRecycleViewListHolder<T, P> dataHolder;
        private final IViewHolderBinderFactory<T,P> binderFactory;
        private final IViewHolderFactory viewHolderFactory;
    
        public RecycleViewHeaderBased(IRecycleViewListHolder<T,P> dataHolder, IViewHolderBinderFactory<T,P> binderFactory, IViewHolderFactory viewHolderFactory) {
            this.dataHolder = dataHolder;
            this.binderFactory = binderFactory;
            this.viewHolderFactory = viewHolderFactory;
        }
    
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
            return viewHolderFactory.provideInflatedViewHolder(viewType,layoutInflater,parent);
        }
    
        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
                binderFactory.bindView(holder, position,dataHolder);
        }
    
        @Override
        public int getItemViewType(int position) {
            if(position == 0)
                return HEADER_TYPE;
            return ITEM_TYPE;
        }
    
        @Override
        public int getItemCount() {
            return dataHolder.getSize()+1;
        }
    }
    

    Usage example:

    1) IRecycleViewListHolder implementation:

    public class AssetTaskListData implements IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> {
        private List<Map.Entry<Integer, Integer>> assetCountList;
        private GroupedRecord record;
    
        public AssetTaskListData(Map<Integer, Integer> assetCountListSrc, GroupedRecord record) {
            this.assetCountList =  new ArrayList<>();
            for(Object  entry: assetCountListSrc.entrySet().toArray()){
                Map.Entry<Integer,Integer> entryTyped = (Map.Entry<Integer,Integer>)entry;
                assetCountList.add(entryTyped);
            }
            this.record = record;
        }
    
        @Override
        public GroupedRecord getAdapterParameters() {
            return record;
        }
    
        @Override
        public Map.Entry<Integer, Integer> getItem(int position) {
            return assetCountList.get(position-1);
        }
    
        @Override
        public int getSize() {
            return assetCountList.size();
        }
    }
    

    2) IViewHolderBinderFactory implementation:

    public class AssetTaskListBinderFactory implements IViewHolderBinderFactory<Map.Entry<Integer, Integer>, GroupedRecord> {
        @Override
        public void bindView(RecyclerView.ViewHolder holder, int position, IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> dataHolder) {
            if (holder instanceof AssetItemViewHolder) {
                Integer assetId = dataHolder.getItem(position).getKey();
                Integer assetCount = dataHolder.getItem(position).getValue();
                ((AssetItemViewHolder) holder).bindItem(dataHolder.getAdapterParameters().getRecordId(), assetId, assetCount);
            } else {
                ((AssetHeaderViewHolder) holder).bindItem(dataHolder.getAdapterParameters());
            }
        }
    }
    

    3) IViewHolderFactory implementation:

    public class AssetTaskListViewHolderFactory implements IViewHolderFactory {
        private IPropertyTypeIconMapper iconMapper;
        private ITypeCaster caster;
    
        public AssetTaskListViewHolderFactory(IPropertyTypeIconMapper iconMapper, ITypeCaster caster) {
            this.iconMapper = iconMapper;
            this.caster = caster;
        }
    
        @Override
        public RecyclerView.ViewHolder provideInflatedViewHolder(int viewType, LayoutInflater layoutInflater, @NonNull ViewGroup parent) {
            if (viewType == RecycleViewHeaderBased.HEADER_TYPE) {
                AssetBasedHeaderItemBinding item = DataBindingUtil.inflate(layoutInflater, R.layout.asset_based_header_item, parent, false);
                return new AssetHeaderViewHolder(item.getRoot(), item, caster);
            }
            AssetBasedListItemBinding item = DataBindingUtil.inflate(layoutInflater, R.layout.asset_based_list_item, parent, false);
            return new AssetItemViewHolder(item.getRoot(), item, iconMapper, parent.getContext());
        }
    }
    

    4) Deriving adapter

    public class AssetHeaderTaskListAdapter extends RecycleViewHeaderBased<Map.Entry<Integer, Integer>, GroupedRecord> {
       public AssetHeaderTaskListAdapter(IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> dataHolder,
                                          IViewHolderBinderFactory binderFactory,
                                          IViewHolderFactory viewHolderFactory) {
            super(dataHolder, binderFactory, viewHolderFactory);
        }
    }
    

    5) Instantiate adapter class:

    private void setUpAdapter() {
            Map<Integer, Integer> objectTypesCountForGroupedTask = groupedTaskRepository.getObjectTypesCountForGroupedTask(this.groupedRecordId);
            AssetTaskListData assetTaskListData = new AssetTaskListData(objectTypesCountForGroupedTask, getGroupedRecord());
            adapter = new AssetHeaderTaskListAdapter(assetTaskListData,new AssetTaskListBinderFactory(),new AssetTaskListViewHolderFactory(iconMapper,caster));
            assetTaskListRecycler.setAdapter(adapter);
        }
    

    P.S.: AssetItemViewHolder, AssetBasedListItemBinding, etc. my application own structures that should be swapped by your own, for your own purposes.

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

    I made an implementation based on @hister's one for my personal purposes, but using inheritance.

    I hide the implementation details mechanisms (like add 1 to itemCount, subtract 1 from position) in an abstract super class HeadingableRecycleAdapter, by implementing required methods from Adapter like onBindViewHolder, getItemViewType and getItemCount, making that methods final, and providing new methods with hidden logic to client:

    • onAddViewHolder(RecyclerView.ViewHolder holder, int position),
    • onCreateViewHolder(ViewGroup parent),
    • itemCount()

    Here are the HeadingableRecycleAdapter class and a client. I left the header layout a bit hard-coded because it fits my needs.

    public abstract class HeadingableRecycleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        private static final int HEADER_VIEW_TYPE = 0;
    
        @LayoutRes
        private int headerLayoutResource;
        private String headerTitle;
        private Context context;
    
        public HeadingableRecycleAdapter(@LayoutRes int headerLayoutResourceId, String headerTitle, Context context) {
            this.headerLayoutResource = headerLayoutResourceId;
            this.headerTitle = headerTitle;
            this.context = context;
        }
    
        public Context context() {
            return context;
        }
    
        @Override
        public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == HEADER_VIEW_TYPE) {
                return new HeaderViewHolder(LayoutInflater.from(context).inflate(headerLayoutResource, parent, false));
            }
            return onCreateViewHolder(parent);
        }
    
        @Override
        public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            int viewType = getItemViewType(position);
            if (viewType == HEADER_VIEW_TYPE) {
                HeaderViewHolder vh = (HeaderViewHolder) holder;
                vh.bind(headerTitle);
            } else {
                onAddViewHolder(holder, position - 1);
            }
        }
    
        @Override
        public final int getItemViewType(int position) {
            return position == 0 ? 0 : 1;
        }
    
        @Override
        public final int getItemCount() {
            return itemCount() + 1;
        }
    
        public abstract int itemCount();
    
        public abstract RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);
    
        public abstract void onAddViewHolder(RecyclerView.ViewHolder holder, int position);
    
    }
    
    
    
    @PerActivity
    public class IngredientsAdapter extends HeadingableRecycleAdapter {
        public static final String TITLE = "Ingredients";
        private List<Ingredient> itemList;
    
    
        @Inject
        public IngredientsAdapter(Context context) {
            super(R.layout.layout_generic_recyclerview_cardified_header, TITLE, context);
        }
    
        public void setItemList(List<Ingredient> itemList) {
            this.itemList = itemList;
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
            return new ViewHolder(LayoutInflater.from(context()).inflate(R.layout.item_ingredient, parent, false));
        }
    
        @Override
        public void onAddViewHolder(RecyclerView.ViewHolder holder, int position) {
            ViewHolder vh = (ViewHolder) holder;
            vh.bind(itemList.get(position));
        }
    
        @Override
        public int itemCount() {
            return itemList == null ? 0 : itemList.size();
        }
    
        private String getQuantityFormated(double quantity, String measure) {
            if (quantity == (long) quantity) {
                return String.format(Locale.US, "%s %s", String.valueOf(quantity), measure);
            } else {
                return String.format(Locale.US, "%.1f %s", quantity, measure);
            }
        }
    
    
        class ViewHolder extends RecyclerView.ViewHolder {
            @BindView(R.id.text_ingredient)
            TextView txtIngredient;
    
            ViewHolder(View itemView) {
                super(itemView);
                ButterKnife.bind(this, itemView);
            }
    
            void bind(Ingredient ingredient) {
                String ingredientText = ingredient.getIngredient();
                txtIngredient.setText(String.format(Locale.US, "%s %s ", getQuantityFormated(ingredient.getQuantity(),
                        ingredient.getMeasure()), Character.toUpperCase(ingredientText.charAt(0)) +
                        ingredientText
                                .substring(1)));
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:28

    Based on this post, I created a subclass of RecyclerView.Adapter that supports an arbitrary number of headers and footers.

    https://gist.github.com/mheras/0908873267def75dc746

    Although it seems to be a solution, I also think this thing should be managed by the LayoutManager. Unfortunately, I need it now and I don't have time to implement a StaggeredGridLayoutManager from scratch (nor even extend from it).

    I'm still testing it, but you can try it out if you want. Please let me know if you find any issues with it.

    0 讨论(0)
提交回复
热议问题