YCGroupAdapter
- 01. Frontier description
- 1.1 Case demonstration effect
- 1.2 Functions and advantages of the library
- 1.3 Related Classes
- 02. How to use it
- 2.1 Introduction
- 2.2 Easiest to use
- 2.3 Suggestions
- 03. Commonly used API
- 3.1 Customizing an Adapter
- 3.2 notify relevant
- 3.3 Click the Event listener
- 04. Implementation steps
- 4.1 Business Requirement analysis
- 4.2 Adapter Implements multiple types
- 4.3 Disadvantages of this writing
- 4.4 Grouping entity beans
- 4.5 Building an Encapsulation Adapter
- 05. Optimize correlation
- 06. Reference
- 07. Other instructions
01. Frontier description
1.1 Case demonstration effect
-
The renderings in the demo
-
Renderings of actual projects
1.2 Functions and advantages of the library
- Custom Adapter divided by group, a recyclerView can fulfill powerful group+children type of business needs.
- Each group supports adding headers, footers, and children, and each group supports setting multiple types of views.
- Support local insert refresh, local remove refresh, that is, data can be inserted or removed by group, or by an unknown child in the group.
- Supports custom click events for header, Footer and Child views. And return the specific index!
- Common usage scenarios: Imitation car emperor, car Home group picture viewer; QQ contact group, can be folded and extended; And complex grouped pages…
- Add object synchronous lock processing adapter data add, obtain and remove methods, effectively avoid multithreading or other operations caused by data dislocation or accidental fast-fail.
02. How to use it
2.1 Introduction
- As shown below.
implementation 'cn. Yc: GroupAdapterLib: 1.0.3' Copy the code
2.2 Easiest to use
- The required three-step code is shown below
mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new GroupedSecondAdapter(this, list); mRecyclerView.setAdapter(mAdapter); Copy the code
- How to achieve the function of QQ group
/** * determine whether the current group is expanded ** @param groupPosition * @return */ public boolean isExpand(int groupPosition) { GroupEntity entity = mGroups.get(groupPosition); returnentity.isExpand(); } @param groupPosition */ public void expandGroup(int groupPosition) {expandGroup(groupPosition,false); } @animate */ public void expandGroup(int groupPosition, boolean animate) { GroupEntity entity = mGroups.get(groupPosition); entity.setExpand(true); if (animate) { notifyChildrenInserted(groupPosition); } else{ notifyDataChanged(); Public void collapseGroup(int groupPosition) {collapseGroup(groupPosition, view)false); Public void collapseGroup(int collapse position) boolean animate) { GroupEntity entity = mGroups.get(groupPosition); entity.setExpand(false); if (animate) { notifyChildrenRemoved(groupPosition); } else{ notifyDataChanged(); }} /** * all groups */ public voidcollapseGroup() { for (int i=0 ; i<mGroups.size() ; i++){ GroupEntity entity = mGroups.get(i); entity.setExpand(false); } notifyDataChanged(); } Copy the code
03. Commonly used API
3.1 Customizing an Adapter
- The code is shown below
public class GroupedSecondAdapter extends AbsGroupedAdapter { private List<GroupEntity> mGroups; public GroupedSecondAdapter(Context context, List<GroupEntity> groups) { super(context); mGroups = groups; } @Override public int getGroupCount() { return mGroups == null ? 0 : mGroups.size(); } @Override public int getChildrenCount(int groupPosition) { if(mGroups! =null){ ArrayList<ChildEntity> children = mGroups.get(groupPosition).getChildren();return children == null ? 0 : children.size(); } return 0; } @Override public boolean hasHeader(int groupPosition) { return true; } @Override public boolean hasFooter(int groupPosition) { return true; } @Override public int getHeaderLayout(int viewType) { return R.layout.item_text_header; } @Override public int getFooterLayout(int viewType) { return R.layout.item_text_footer; } @Override public int getChildLayout(int viewType) { return R.layout.item_content_view; } @Override public void onBindHeaderViewHolder(GroupViewHolder holder, int groupPosition) { } @Override public void onBindFooterViewHolder(GroupViewHolder holder, int groupPosition) { } @Override public void onBindChildViewHolder(GroupViewHolder holder, int groupPosition, int childPosition) { } } Copy the code
- How do you control whether a header or footer is displayed in a group?
- Return true for display, false for not display… It’s that simple
@Override public boolean hasHeader(int groupPosition) { return true; } @Override public boolean hasFooter(int groupPosition) { return true; } Copy the code
3.2 notify relevant
- Insert data
/ / notice to insert a set of data mAdapter. NotifyGroupInserted (1); / / notice an item to insert mAdapter group. The notifyChildInserted (1, 3); / / notify multiple items in a set of inserts mAdapter. NotifyChildRangeInserted,2,10 (1); / / notice. All the items in the a set of insert mAdapter notifyChildrenInserted (1); . / / notify multiple sets of data into mAdapter notifyGroupRangeInserted (1, 3); / / notification group head insert mAdapter notifyHeaderInserted (1); / / notification group tail insert mAdapter. NotifyFooterInserted (1);Copy the code
- To remove data
Inform all data / / delete mAdapter. NotifyDataRemoved (); / / to inform a set of data to delete, including group head, tail and children mAdapter. NotifyGroupRemoved (1); Group/delete/notify multiple sets of data, including head, tail and children mAdapter. NotifyGroupRangeRemoved (1, 3); / / notify group head delete mAdapter notifyHeaderRemoved (1); / / notice group tail delete mAdapter. NotifyFooterRemoved (1); / / notice. Some items in a group to delete mAdapter notifyChildRemoved (1, 3); / / notification in a group of multiple items to delete mAdapter. NotifyChildRangeRemoved (1 and 4); / / notice. All the items in the a group delete mAdapter notifyChildrenRemoved (1);Copy the code
3.3 Click the Event listener
- Set the group header click event
mAdapter.setOnHeaderClickListener(new OnHeaderClickListener() { @Override public void onHeaderClick(AbsGroupedAdapter adapter, GroupViewHolder holder, int groupPosition) { Toast.makeText(SecondActivity.this, "Group header: groupPosition ="+ groupPosition,Toast.LENGTH_LONG).show(); }});Copy the code
- Set the group footer click event
mAdapter.setOnFooterClickListener(new OnFooterClickListener() { @Override public void onFooterClick(AbsGroupedAdapter adapter, GroupViewHolder holder, int groupPosition) { Toast.makeText(SecondActivity.this, "End of group: groupPosition ="+ groupPosition,Toast.LENGTH_LONG).show(); }});Copy the code
- Set the children click event in the group
mAdapter.setOnChildClickListener(new OnChildClickListener() { @Override public void onChildClick(AbsGroupedAdapter adapter, GroupViewHolder holder, int groupPosition, int childPosition) { Toast.makeText(SecondActivity.this,"Subitem: groupPosition =" + groupPosition + ", childPosition = "+ childPosition,Toast.LENGTH_LONG).show(); }});Copy the code
04. Implementation steps
4.1 Business Requirement analysis
- For example, in app development, the product said to achieve a QQ group function, requiring folding function. At the same time, in the APP, picture album, imitate the car to achieve group pictures. See such a requirement, think can use a recyclerView to achieve, use type to distinguish between different types of layout.
- RecyclerView can use ViewType to distinguish different items, and can also meet the needs, but there are still some problems, such as:
- 1. In the interface of the list of too many items and complicated logic, there is a large amount of code in the Adapter, which is difficult to maintain in the later stage.
- 2. Each time a list is added, an Adapter needs to be added, which is inefficient to move bricks repeatedly.
- If you have multiple pages with multiple types, you need to write multiple adapters.
- 4, if there is local refresh, then it is more troublesome, for example, the advertising area is also a nine-grid RecyclerView, click local refresh the current data, more troublesome.
4.2 Adapter Implements multiple types
- The usual way to write a multi-item list
- Handling different items for different viewTypes can be a lot of code if logic is complex. If new requirements are added in version iterations, it is cumbersome to modify the code and difficult to maintain later.
- Main Operation Steps
- Determine the ViewHolder type to be created in onCreateViewHolder based on the return value of the viewType parameter, getItemViewType
- The onBindViewHolder method is used to determine the specific type of ViewHolder and bind data and logic for different types of ViewHolder
- The code is shown below
public class HomePageAdapter extends RecyclerView.Adapter { public static final int TYPE_HEADER = 1; public static final int TYPE_FOOTER = 2; public static final int TYPE_IMAGE = 3; private List<HomePageEntry> mData; public void setData(List<HomePageEntry> data) { mData = data; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType){ case TYPE_HEADER: return new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null)); case TYPE_FOOTER: return new FooterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_text_item_layout,null)); case TYPE_CHILD: return new ChildViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_image_item_layout,null)); } return null; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { int type = getItemViewType(position); switch (type) {caseTYPE_HEADER: // logical processing of TYPE_HEADERbreak; caseTYPE_FOOTER: // TYPE_FOOTER logic processingbreak; caseTYPE_CHILD: // TYPE_CHILD logical processingbreak; } } @Override public int getItemViewType(int position) { returnmData.get(position).type; //typeValues are TYPE_HEADER, TYPE_FOOTER, TYPE_AD, etc.} @override public intgetItemCount() { returnmData == null ? 0:mData.size(); } public static class HeaderViewHolder extends RecyclerView.ViewHolder{ public HeaderViewHolder(View itemView) { super(itemView); // bind control}} // omit some code}Copy the code
4.3 Disadvantages of this writing
- The downside of that
- Type checking and type transformation. Since different ViewHolder types are created in onCreateViewHolder, data binding and logic processing should be performed in onBindViewHolder for different ViewHolder types. This results in the need to type check and cast ViewHolder via Instanceof.
- As the layout type in the list increases and changes, the code in getItemViewType, onCreateViewHolder, and onBindViewHolder all need to be changed or increased. The code in the Adapter becomes bloated and messy. Increased code maintenance costs.
- For example, in the group control, like QQ group, click the header in the group, you can switch off and extend the children’s optional item in the group, so if it is not encapsulated, Adapter for data processing is more troublesome.
- Sometimes, in a grouped control, some groups do not want to display the header, and some groups do not want to display the footer, so this is not flexible. Can you use an on-off method to show and hide headers and footers?
4.4 Grouping entity beans
- The GroupStructure records whether each group has a head, whether it has a tail and the number of subitems. It is easy to calculate the length of the list and the position of each group’s head, tail and subitems in the list.
4.5 Building an Encapsulation Adapter
- There are three core purposes
- Avoid type checking and type transitions for classes
- Enhance Adapter scalability
- Enhance Adapter maintainability
- GetItemViewType, onCreateViewHolder, and onBindViewHolder are the three main methods that change in Adapter as the types in the list increase or decrease, so we’ll start with them.
- In the getItemViewType method.
- Logic judgments such as if simplify code and can be used as TYPE_HEADER, TYPE_FOOTER, TYPE_CHILD to add type identifiers.
- If the adapter is a grouping adapter, it first retrieves the index of the group, then determines the type of the type by the index of the group, and finally returns the specific itemType.
@Override public int getItemViewType(int position) { itemType = position; int groupPosition = getGroupPositionForPosition(position); int type = judgeType(position); if (type == TYPE_HEADER) { return getHeaderViewType(groupPosition); } else if (type == TYPE_FOOTER) { return getFooterViewType(groupPosition); } else if (type == TYPE_CHILD) { int childPosition = getChildPositionForPosition(groupPosition, position); return getChildViewType(groupPosition, childPosition); } returnsuper.getItemViewType(position); } /** * check itemtypeHeader tail and child * * @param position * @return*/ public int judgeType(int position) { int itemCount = 0; Int groupCount = mStructures. Size ();for(int i = 0; i < groupCount; i++) { GroupStructure structure = mStructures.get(i); // Check whether there is a header viewif (structure.hasHeader()) { itemCount += 1; if (position < itemCount) { returnTYPE_HEADER; }} / / get the number of children itemCount + = structure. The getChildrenCount ();if (position < itemCount) { returnTYPE_CHILD; } // Check if there are footersif (structure.hasFooter()) { itemCount += 1; if (position < itemCount) { returnTYPE_FOOTER; Do not throw an exception, even when position == getItemCount() istrue, you can use empty pages insteadreturn TYPE_NO; //throw new IndexOutOfBoundsException("can't determine the item type of the position." + // "position = " + position + ",item count = "+ getItemCount()); } // omit some code, see the source code in libCopy the code
- In the onCreateViewHolder method
- Create a viewHolder, which creates the Item view and returns the corresponding viewHolder. Can you expose the header, footer, children layout of a group to external developers?
- So, we’re going to have to differentiate the type here, and then return the layout, and we’re going to return the layout and we’re going to have a couple of methods that we can make abstract methods that subclasses have to implement. Let the subclass return the specific header, footer, and children layout.
@NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view; if(viewType ! = TYPE_NO){ int layoutId = getLayoutId(itemType, viewType);if (inflater==null){ inflater = LayoutInflater.from(mContext); } view = inflater.inflate(layoutId, parent, false); } else{// Use empty layout // Unknown type can use empty layout instead of view = new View(parent-getContext ()); }return new GroupViewHolder(view); } private int getLayoutId(int position, int viewType) { int type = judgeType(position); if (type == TYPE_HEADER) { return getHeaderLayout(viewType); } else if (type == TYPE_FOOTER) { return getFooterLayout(viewType); } else if (type == TYPE_CHILD) { return getChildLayout(viewType); } return 0; } Copy the code
- In the onBindViewHolder method
- This method does two things. The first one is to set the header, footer, and children click events in the group, and return the specific index, including the group index, and the index of the children in the group.
- The second is the bind viewHolder, which is used to bind data to the correct Item view. This method can be abstracted and implemented by subclasses.
@Override public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) { int type = judgeType(position); final int groupPosition = getGroupPositionForPosition(position); if (type == TYPE_HEADER) { if(mOnHeaderClickListener ! = null) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(mOnHeaderClickListener ! = null) { mOnHeaderClickListener.onHeaderClick(AbsGroupAdapter.this, (GroupViewHolder) holder, groupPosition); }}}); } onBindHeaderViewHolder((GroupViewHolder) holder, groupPosition); }else if (type == TYPE_FOOTER) { if(mOnFooterClickListener ! = null) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(mOnFooterClickListener ! = null) { mOnFooterClickListener.onFooterClick(AbsGroupAdapter.this, (GroupViewHolder) holder, groupPosition); }}}); } onBindFooterViewHolder((GroupViewHolder) holder, groupPosition); }else if (type == TYPE_CHILD) { final int childPosition = getChildPositionForPosition(groupPosition, position); if(mOnChildClickListener ! = null) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(mOnChildClickListener ! = null) { mOnChildClickListener.onChildClick(AbsGroupAdapter.this, (GroupViewHolder) holder, groupPosition, childPosition); }}}); } onBindChildViewHolder((GroupViewHolder) holder, groupPosition, childPosition); }}Copy the code
- Post-package benefits
- Extensibility – Each group supports adding headers, footers, and children, and each group supports setting multiple types of views. It also supports local insert refresh, local remove refresh, that is, data can be inserted or removed by group, or data can be inserted or removed by some unknown child in the group.
- Maintainability – Different list types are processed by Adapter with header, footer, and children types. They do not interfere with each other. The code is simple and the maintenance cost is low. You can also control the visibility of header and footer layouts.
Reference Case notes
- Github.com/msdx/group-…
- www.jianshu.com/p/7b5607a7f…
- www.jianshu.com/p/26b0911f3…
- Github.com/donkinglian…
- www.jianshu.com/p/1558cbd20…
Other Recommendations
- 1. Tech blog round-up
- 2. Open source project summary
- 3. Life Blog Summary
- 4. Himalayan audio summary
- 5. Other summaries
- 6. Key recommendation: blog notes summary, open source files are MD format
About the LICENSE
Copyright 2017 Yangchong211 (github.com/yangchong211) Licensed under the Apache License, Version 2.0 (the"License");
you may not use this file except inThe 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 toin 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.
Copy the code