Recyclerview Dynamic Sections not using any 3rd lib - android

i want to add header to recycle view i am trying to achieve it using
#Override
public int getItemViewType(int position) {
// depends on your problem
if (position == 0 || position == 4) {
return 2;
} else {
return 1;
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
if (viewType == 1) {
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.cardview_row, null);
return new ViewHolder(itemLayoutView);
} else if (viewType == 2) {
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.cardview_row1, null);
return new ViewHolders(itemLayoutView);
}
return null;
}
but how i can do it in run time like when i don't know position when it should show section like i have json
{
"DATA": [
"hi",
"heloo",
"bye"
],
"MAHA": [
"ans",
"rs",
"re",
"jab",
"bad"
]
}
where data and maha is section i want to display other elements
currently i am making arraylist of all elements and adding hardcore
value for section but how i can do this viva using above json

What you need is an expandable recyclerview with parents(headers) and children(entries). Here you go:
https://github.com/bignerdranch/expandable-recycler-view
If you want to show the entries always and dont profit from the expandable feature, just do: expAdapter.expandAllParents().
I know you dont want to use 3rd library parties but in my opinion this is the best way to deal with it and saves you a lot of time. Moreover if someone else has the same question he maybe finds this solution useful.

Create a class like this for your JSON data, and create one Section class per node (in your example, one for DATA and another for MAHA):
class Section {
String header;
List<String> itemList;
}
Then create your custom adapter:
public class SectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Section> sectionList;
public SectionedRecyclerViewAdapter(List<Section> sectionList) {
this.sectionList = sectionList;
}
#Override
public int getItemViewType(int position) {
int currentPos = 0;
for (Section section : sectionList) {
// check if position is in this section
// +1 for the header, -1 because idx starts at 0
if (position >= currentPos && position <= (currentPost + section.itemList.size() + 1 - 1)) {
if (position == currentPos) {
return 2; // header
} else {
return 1; // item
}
}
// +1 for the header
currentPos += section.itemList.size() + 1;
}
throw new IndexOutOfBoundsException("Invalid position");
}
// use the onCreateViewHolder method from your question...
}
I know you don't want to use a 3rd party lib, but you can check how the getItemViewType method of SectionedRecyclerViewAdapter is done here.

Related

How to hide/display specific RecyclerView item?

I have a contacts list RecyclerView, where the first item (position 0) holds the user's details. When the search icon in the toolbar is pressed, I want that entry to be hidden from the user.
I've tried using setVisibility(View.GONE) on it, and, although the entry is hidden, the space it occupied was still there, much like setting setVisibility(View.INVISIBLE). How can I toggle the visibility of this specific entry to VISIBLE/GONE in my RecyclerView, or how can I set its height only to 0dp?
EDIT:
My xml is quite simple, it comprises of simply a recyclerView and a FAB. My adapter code is as below.
ContactsAdapter(
Context context,
List<LinphoneContact> contactsList,
ContactViewHolder.ClickListener clickListener,
SelectableHelper helper) {
super(helper);
mContext = context;
updateDataSet(contactsList);
mClickListener = clickListener;
}
#NonNull
#Override
public ContactViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
// For the first position only, use the user's own contact card
// For all the rest, use the contact card
switch (viewType) {
case USER_CARD_VIEW:
View userCard =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.contacts_user_card, parent, false);
return new ContactViewHolder(userCard, mClickListener);
case CONTACT_CARD_VIEW:
View contactCard =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.contact_cell, parent, false);
return new ContactViewHolder(contactCard, mClickListener);
}
return null;
}
#Override
public void onBindViewHolder(#NonNull ContactViewHolder holder, int position) {
// Remove the user's card when searching contacts - DOES NOT WORK, MAKES ALL FIRST ITEMS
// INVISIBLE
// if (position == 0) {
// if (mIsSearchMode) holder.itemView.setVisibility(View.GONE);
// else holder.itemView.setVisibility(View.VISIBLE);
// }
if (position != 0) {
LinphoneContact contact = (LinphoneContact) getItem(position - 1);
holder.name.setText(contact.getFullName());
if (!mIsSearchMode) {
String fullName = contact.getFullName();
if (fullName != null && !fullName.isEmpty()) {
holder.separatorText.setText(String.valueOf(fullName.charAt(0)));
}
}
// Separator as in the big capital letter on the left to indicate sections
holder.separator.setVisibility(
mIsSearchMode
|| (getPositionForSection(getSectionForPosition(position))
!= position)
? View.GONE
: View.VISIBLE);
holder.linphoneFriend.setVisibility(
contact.isInFriendList() ? View.VISIBLE : View.GONE);
ContactAvatar.displayAvatar(contact, holder.avatarLayout);
boolean isOrgVisible = LinphonePreferences.instance().isDisplayContactOrganization();
String org = contact.getOrganization();
if (org != null && !org.isEmpty() && isOrgVisible) {
holder.organization.setText(org);
holder.organization.setVisibility(View.VISIBLE);
} else {
holder.organization.setVisibility(View.GONE);
}
holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.INVISIBLE);
holder.delete.setChecked(isSelected(position));
// } else {
// // TODO - user's card should have whole and voicemail onClick listeners
}
// Log.d("contactsAdapter", "Position: " + position);
// Log.d("contactsAdapter", "Section for position: " +
// getSectionForPosition(position));
// Log.d(
// "contactsAdapter",
// "The other thingy: " +
// getPositionForSection(getSectionForPosition(position)));
}
// +1 item count to account for user's contact card
#Override
public int getItemCount() {
return mContacts.size() + 1;
}
public Object getItem(int position) {
if (position >= getItemCount()) return null;
return mContacts.get(position);
}
public void setIsSearchMode(boolean set) {
mIsSearchMode = set;
}
[...]
#Override
public int getItemViewType(int position) {
return position == 0 ? USER_CARD_VIEW : CONTACT_CARD_VIEW;
}
Essentially, I don't add the first item to my list of items, but instead I add it to the recyclerView, and shift my list of items by one position up.
although the entry is hidden, the space it occupied was still there
may be because you are hiding the child but its parent is still there with its height or padding given
How can I toggle the visibility of this specific entry to VISIBLE/GONE in my RecyclerView
Toggle the visibility of parent
In your onBindViewHolder
#Override
public void onBindViewHolder(#NonNull MyViewHolder viewHolder, int i) {
if (isButtonPressed) {
viewHolder.parentViewId.setVisibility(View.GONE);
}else {
viewHolder.parentViewId.setVisibility(View.VISIBLE);
}
}
Create a public boolean isButtonPressed; and when you press the button make it true and call adapter.notifyDataSetChanged();

Android:getting number of views in recyclerview

I am implementing recyclerview with multiple layouts.Usually we have multiple viewholders for different layouts and override other methods as per the required layout.I have successfully implemented this.But now i have a different scenario like: A recyclerview that shows some videos (say 3) then another layout(say layout x), again 3 videos and then again layout x and so on.Suppose i have 10 videos then in this case the itemcount would be 10 + 3 as 3 layout x would be displayed.But the videos are loaded while scrolling.So how can i determine the number of views to return in getItemCount();
I mean
#Override
public int getItemCount() {
return ListofVideos.size() + "WHAT??"
}
layout is like this
If all the videos are loaded at at once then it is easy to calculate the number of views like if i have 21 videos i would have total 27 views(i.e 21 videos and 6 layout X views). But when the list is loaded on scroll how can i determine the number of views?
Your Adapter is responsible to populate view so it has all views of your RecyclerView while your ListofVideos (may) have only video links.
Whenever you scroll your RecyclerView, Adapter is responsible to inflate views.
What you should do?
Create an interface
public interface BaseItem {
int ITEM_TYPE_HEADER = 0;
int ITEM_TYPE_SUB_HEADER = 1;
int ITEM_TYPE_ROW_NORMAL = 2;
int getItemType();
}
And implement this interface with your adapter's video item like
public class YourAdapterVideoItem implements BaseItem {
// rest of your code
#Override
public int getItemType() {
return ITEM_TYPE_ROW_NORMAL;
}
}
Create your adapter's header item
public class YourAdapterHeaderItem implements BaseItem {
// rest of your code
#Override
public int getItemType() {
return ITEM_TYPE_HEADER;
}
}
Update your adapter with
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<BaseItem> items = new ArrayList<BaseItem>();
#Override
public BaseRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
BaseRecyclerViewHolder holder;
switch (viewType) {
case BaseItem.ITEM_TYPE_ROW_NORMAL:
default:
// inflate your default items
break;
case BaseItem.ITEM_TYPE_HEADER:
// inflate your default items
break;
}
return holder;
}
#Override
public void onBindViewHolder(BaseRecyclerViewHolder viewHolder, int position) {
BaseItem base = getItemAt(position);
switch (base.getItemType()) {
case BaseItem.ITEM_TYPE_HEADER:
// populate your header view
break;
case BaseItem.ITEM_TYPE_ROW_NORMAL:
// populate your actual view
break;
}
}
#Override
public int getItemCount() {
return items == null ? 0 : items.size();
}
#Override
public int getItemViewType(int position) {
return getItemAt(position).getItemType();
}
public BaseItem getItemAt(int position) {
return items == null ? null : items.get(position);
}
}
When you want to add header use YourAdapterHeaderItem for your videos use YourAdapterVideoItem.
Hope this helps
Edit
For adding headers in GridLayoutManager have a look at RecyclerView GridLayoutManager with full width header

Display Namelist In Recyclerview under each letter in alphabetic Order Android

I have list of contacts which has to be displayed in alphabetic under each alphabet as shown in the image shown
How can I do this in RecyclerView, please suggest a solution.thanks
Sort list with data by name
Iterate via list with data, and in place when current's item first letter != first letter of next item, insert special kind of object.
Inside your Adapter place special view when item is "special".
This is what I did following #divers's post:
as he mentioned I pass a team list to the the adapter which is sorted and alphabets are added before the next name.
this is he code used to set adapter
void updateUI(ArrayList<TeamMember> teamMembers) {
adapter = new TeamMemberActivityAdapter(this, addAlphabets(sortList(teamMembers)));
recList.setAdapter(adapter);
recList.setVisibility(View.VISIBLE);
spinningProgressView.setVisibility(View.GONE);
}
code to sort the team list obtained from server is given below:
ArrayList<TeamMember> sortList(ArrayList<TeamMember> list) {
Collections.sort(list, new Comparator<TeamMember>() {
#Override
public int compare(TeamMember teamMember1, TeamMember teamMember2) {
return teamMember1.getFullname().compareTo(teamMember2.getFullname());
}
});
return list;
}
while adding alphabets to the list I am setting a type value to know whether its alphabet or team name to check this inside the adapter for showing corresponding layout .the code for that is as shown below:
ArrayList<TeamMember> addAlphabets(ArrayList<TeamMember> list) {
int i = 0;
ArrayList<TeamMember> customList = new ArrayList<TeamMember>(); TeamMember firstMember = new TeamMember();
firstMember.setFullname(String.valueOf(list.get(0).getFullname().charAt(0)));
firstMember.setType(1);
customList.add(firstMember);
for (i = 0; i < list.size() - 1; i++) {
TeamMember teamMember = new TeamMember();
char name1 = list.get(i).getFullname().charAt(0);
char name2 = list.get(i + 1).getFullname().charAt(0);
if (name1 == name2) {
list.get(i).setType(2);
customList.add(list.get(i));
} else {
list.get(i).setType(2);
customList.add(list.get(i));
teamMember.setFullname(String.valueOf(name2));
teamMember.setType(1);
customList.add(teamMember);
}
}
list.get(i).setType(2);
customList.add(list.get(i));
return customList;
}
And finally inside your adapter check if the item is teamMember name or alphabet and display corresponding layout as shown below:
#Override
public int getItemViewType(int position) {
int viewType = 0;
if (mMembers.get(position).getType() == TYPE_LETTER) {
viewType = TYPE_LETTER;
} else if (mMembers.get(position).getType() == TYPE_MEMBER) {
viewType = TYPE_MEMBER;
}
return viewType;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater mInflater = LayoutInflater.from(viewGroup.getContext());
switch (viewType) {
case TYPE_LETTER:
ViewGroup vGroupImage = (ViewGroup) mInflater.inflate(R.layout.board_team_letter_item, viewGroup, false);
ViewHolderLetter image = new ViewHolderLetter(vGroupImage);
return image;
case TYPE_MEMBER:
ViewGroup vGroupText = (ViewGroup) mInflater.inflate(R.layout.board_team_member_item, viewGroup, false);
ViewHolderMember text = new ViewHolderMember(vGroupText);
return text;
default:
ViewGroup vGroupText2 = (ViewGroup) mInflater.inflate(R.layout.board_team_member_item, viewGroup, false);
ViewHolderMember text1 = new ViewHolderMember(vGroupText2);
return text1;
}
}
hope this could help you. all the best
compare your model and get first character from title ....
private void getHeaderListLatter(ArrayList<CountriesModel> usersList) {
Collections.sort(usersList, new Comparator<CountriesModel>() {
#Override
public int compare(CountriesModel user1, CountriesModel user2) {
return String.valueOf(user1.name.charAt(0)).toUpperCase().compareTo(String.valueOf(user2.name.charAt(0)).toUpperCase());
}
});
String lastHeader = "";
int size = usersList.size();
for (int i = 0; i < size; i++) {
CountriesModel user = usersList.get(i);
String header = String.valueOf(user.name.charAt(0)).toUpperCase();
if (!TextUtils.equals(lastHeader, header)) {
lastHeader = header;
mSectionList.add(new CountriesModel(header,true));
}
mSectionList.add(user);
}
}
and in your adapter getItemViewType Layout like this ....
#Override
public int getItemViewType(int position) {
if (mCountriesModelList.get(position).isSection) {
return SECTION_VIEW;
} else {
return CONTENT_VIEW;
}
}
for complete reference .
https://github.com/sayanmanna/LetterSectionedRecyclerView
I'm currently using this. It's very easy to implement, compatible with RecyclerView adapter, and so lightweight you'd barely call it a library!
You can achieve it with this library.
There is a full example here of how to add headers.
And if you want to implement the search functionality, there is also a full example here, this is the result:
https://github.com/emilsjolander/StickyListHeaders
I hope this is what You want.

Force RecyclerView to call onCreateViewHolder

I have a RecyclerView that can show items as list, small grids or large grid and this can be change at runtime. Depending on what style user chooses i inflate different layout in onCreateViewHolder.
I also use layoutManger.setSpanSizeLookUp() to switch between styles. My code looks like this
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
return 1;
else
return columnCount; //show one item per row
}
});
#Override
public void onClick(View v) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
showType = ProductAdapter.SHOW_TYPE_LARGE_GRID;
else
showType = ProductAdapter.SHOW_TYPE_SMALL_GRID;
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
adapter = new ProductAdapter(getActivity(), productList, showType);
recyclerView.setAdapter(adapter);
layoutManager.scrollToPosition(firstVisibleItem);
}
The problem is to force onCreateViewHolder to be called I'm creating a new object every time user changes the style. Is there any other way?! to force onBindViewHolder() to be recalled. I simply use adapter.notifyDataSetChanged() How can i get something similar for onCreateViewHolder?
Any solution that doesn't uses multiple adapters is good enough!
What you need to do is:
Modify your Adapter:
Specify two types of Views that your Adapter can inflate:
private static final int LARGE_GRID_ITEM = -1;
private static final int SMALL_GRID_ITEM = -2;
Create a field that can store current type mCurrentType
Use your Adapter's getItemViewType. For example like this:
#Override
public int getItemViewType (int position) {
return mCurrentType;
}
In your createViewHolder use the viewType to decide what type of ViewHolder you need to create.
public final RecyclerView.ViewHolder createViewHolder (ViewGroup parent, int viewType){
if (viewType == LARGE_GRID_ITEM) {
//return large grid view holder
} else {
//return small grid view holder
}
}
Additionally you can create methods:
public void toggleItemViewType () {
if (mCurrentType == LARGE_GRID_ITEM){
mCurrentType = SMALL_GRID_ITEM;
} else {
mCurrentType = LARGE_GRID_ITEM;
}
}
public boolean displaysLargeGrid(){
return mCurrentType == LARGE_GRID_ITEM;
}
Modify the code you posted:
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (adapter.displaysLargeGrid()) {
return 1;
} else {
return columnCount; //show one item per row
}
}
});
#Override
public void onClick(View v) {
adapter.toggleItemViewType();
adapter.notifyDataSetChanged();
}
Its not the optimal choice but it's better to create a new Adapter, which will call onCreateViewHolder(). This way you can avoid your troubles, by the cost of very tiny performance issues.

Refresh listView with multiple view types after delete/remove item

I have a ListView with a custom adapter that extends BaseAdapter and has 2 view types. When I run my adapter.removeRow(position) method, the data for the adapter is correctly updated, and the list reflects this, but the view types are not correctly updated. The Adapter is backed by
ArrayList<Map<String, String>> rows = new ArrayList<Map<String, String>>();
and I have a subset
List<Integer> flashSet = new ArrayList<Integer>();
which is a list of all the positions that are of ViewType 1 (as opposed to the standard view type 0).
Here is my adapter removeRow(position) method:
public void removeRow(int position) {
if (getItemViewType(position) == TYPE_FLASH) {
flashSet.remove(position);
}
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashPosition -= 1;
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
rows.remove(position);
notifyDataSetChanged();
}
Here is my getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
FlashHolder flashHolder;
ClipHolder clipHolder;
int type = getItemViewType(position);
if (convertView == null) {
if (type == TYPE_CLIP) {
convertView = rowInflater.inflate(R.layout.clip_note_row_layout, null);
clipHolder = new ClipHolder();
flashHolder = null;
clipHolder.textView = (TextView)(convertView.findViewById(R.id.clip_text));
convertView.setTag(clipHolder);
} else {
convertView = rowInflater.inflate(R.layout.flash_row_layout, null);
clipHolder = null;
flashHolder = new FlashHolder();
flashHolder.front = (TextView)(convertView.findViewById(R.id.flash_text));
flashHolder.back = (TextView)(convertView.findViewById(R.id.clip_text));
convertView.setTag(flashHolder);
}
} else {
if (type == TYPE_CLIP) {
clipHolder = (ClipHolder)convertView.getTag();
flashHolder = null;
} else {
clipHolder = null;
flashHolder = (FlashHolder)convertView.getTag();
}
}
if (type == TYPE_CLIP) {
clipHolder.textView.setText(rows.get(position).get("clip"));
} else {
flashHolder.front.setText(rows.get(position).get("flash_text"));
flashHolder.back.setText(rows.get(position).get("clip"));
}
return convertView;
}
I know that I could create a new adapter, give it the updated ArrayList and call listView.setAdapter(adapter) but this seems total overkill when I'm simply trying to remove one item from a potentially long list. See pics for a before and after deleting:
Then I delete the first item. The word "which" was hidden behind the "Let's watch it" item and now the "inspired by…" item is hidden behind a blank item 3.
So, data is updating, view types aren't. Thanks for the help!
I figured it out. This will be useful to no one as I don't expect others to make the same mistake.
I naively thought that by doing this
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashPosition -= 1;
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
I was changing the actual value stored in the List<Integer> flashSet = new ArrayList<Integer>();
In fact, I need to do the following instead:
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashSet.remove((Object)flashPosition);
flashPosition -= 1;
flashSet.add(flashPosition);
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
Try this, After delete or add item you need to call adapter refresh.
Youradapter.notifyDataSetChanged();
use yourlistview.invalidateViews()
instead of
notifyDataSetChanged();
it works for me.

Categories

Resources