Section in RecylerView with Cursor - java

I've read a lot of StackOverflow posts about such problem but most of them were not using cursor as their data source for RecyclerView. But my app does.
In my app, I query the database which gives me results as shown in the following image. Results are ordered by _id column.
I want my RecyclerView to represents the data something like below:
White Blood Cell(WBC)
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
Red Blood Cell - Male
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
As you can see it forms groups with headings and relevant items go under each heading.
I have created two different layouts.
Heading with an item below it layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<TextView
android:id="#+id/heading"" />
<LinearLayout>
<TextView
android:id="#+id/value" />
<TextView
android:id="#+id/date" />
</LinearLayout>
</LinearLayout>
Item layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<TextView
android:id="#+id/value" />
<TextView
android:id="#+id/date"/>
</LinearLayout>
Ignore missing details in layout files
On the other hand, my adapter does the following.
public class BloodTestRecordAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// it holds the _id of the last group
private int lastGroupId = -1;
private final int VIEW_TYPE_HEADING = 0, VIEW_TYPE_ITEM = 1;
#Override
public int getItemViewType(int position) {
int returnItemViewType;
// get _id value for the row at given position
int currentGroupId = getCursorAtPosition(position).getInt(0);
if (currentGroupId != lastGroupId) {
// return VIEW_TYPE_HEADING
lastGroupId = currentGroupId;
returnItemViewType = VIEW_TYPE_HEADING;
} else {
returnItemViewType = VIEW_TYPE_ITEM;
}
return returnItemViewType;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
View view;
if (viewType == VIEW_TYPE_HEADING) {
// inflate HEADING LAYOUT
} else {
// inflate item layout
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, Cursor cursor, int position) {
if (viewHolder.getItemViewType() == VIEW_TYPE_HEADING) {
// bind values to heading layout
} else {
// bind values to item layout
}
}
public class HeadingViewHolder extends RecyclerView.ViewHolder {
}
public class ItemViewHolder extends RecyclerView.ViewHolder {
}
}
what I am doing in getItemViewType is I check to see whether the _id column matches the lastGroupId by using position argument that is passed to the method. If it matches I just return the VIEW_TYPE_ITEM, which is the normal item layout.
And if it doesn't match, then it gives us an indication that it is the beginning of another group, so for this case, I return VIEW_TYPE_HEADING.
It works fine but as I get more items the heading and placement of items under groups don't seem to be correct. Sometimes the heading layout goes vanish for some group of items as shown below in the image.
I know for sure that the way i implemented the getViewItemType is not
correct, I also know as my viewholders get recycled it messes with my getViewItem method because of that lastGroupId variable.
Can you give me any suggestions how should I do this or what can I do to achieve such things in RecyclerView with Cursor or what are my mistakes?

The problem appear when you scrolling up the recyclerView and so that lastGroupId doesn't full fill correctly.
Use this logic in place of your current code in getItemViewType:
if (position == 0) {
return VIEW_TYPE_HEADING;
} else {
int currentGroupId = getCursorAtPosition(position).getInt(0);
int previousGroupId= getCursorAtPosition(position - 1).getInt(0);
return currentGroupId != previousGroupId ? VIEW_TYPE_HEADING : VIEW_TYPE_ITEM;
}

Related

Only one of multiple views fails with findViewById()

I'm trying to reference a TextView from a layout. It works for all other views but for this specific one it returns null. Doing it the same way for all the views, so I can't see why it wouldn't work on this one.
Important to mention is that the layout file showed here is included into b_user_list. All views referenced from within the include works, except for one.
b_user_list.xml:
<include
android:id="#+id/showUserLay"
layout="#layout/c_show_front_layout"
... />
c_show_front_layout.xml:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/volunDbListLay"
... >
<TextView
android:id="#+id/textView932"
... />
<View
android:id="#+id/divider522"
... />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/volunDbRecycler"
... />
<TextView
android:id="#+id/noVolunOrgsFound"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="No organizations found."
app:layout_constraintBottom_toBottomOf="#+id/volunDbRecycler"
app:layout_constraintEnd_toEndOf="#+id/volunDbRecycler"
app:layout_constraintStart_toStartOf="#+id/volunDbRecycler"
app:layout_constraintTop_toTopOf="#+id/volunDbRecycler" />
</androidx.constraintlayout.widget.ConstraintLayout>
ActivityUser.java:
private ConstraintLayout settErrorLay;
private TextView errorTxt;
private TextView errorClose;
private TextView noVolunOrgsFound;
private RecyclerView volunDbRecycler;
private ConstraintLayout volunDbListLay, notVsAccWarning;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.b_user_list);
...
settErrorLay = findViewById(R.id.userIntegrErrorLay);
errorClose = findViewById(R.id.closeUserIntegrErrorTxt);
...
if (errorClose != null && settErrorLay != null) {
// Checks if includeView show_front_layout.xml worked
volunDbRecycler = findViewById(R.id.volunDbRecycler);
volunDbListLay = findViewById(R.id.volunDbListLay);
notVsAccWarning = findViewById(R.id.notVsAccWarning);
noVolunOrgsFound = findViewById(R.id.noVolunOrgsFound);
...
}
...
}
...
I found a way to reference the view, but obviously not the proper way. I ran through all the children of the parent view, and when the id match I add it as the view. Then it didn't return null:
if (noVolunOrgsFound == null) {
for (int i = 0; i < volunDbListLay.getChildCount(); i++) {
if (volunDbListLay.getChildAt(i).getId() == R.id.noVolunOrgsFound) {
noVolunOrgsFound = (TextView) volunDbListLay.getChildAt(i);
}
}
}
So I have ran out of ideas for why this doesn't work. It can't be the <include> since the other sibling views works, the id is the same both in the layout and java, and it works with by finding it within the children of the parent, just not with findViewById().
EDIT: I posted an answer that seemed to be solving the problem, but now I just have the same problem with a RecyclerView. This time, it didn't work to manually loop through the views of the parent and checking when the child view id match the RecyclerView's id..
Is there a limit to how many views can be loaded or referenced in Java or something?
Found a solution, I left out something in the question that I didn't think would have a say in the matter (And I'm still not sure why it does).
Before the findViewById(R.id.noVolunOrgsFound); was a function, and in that function was a button assigned to use the TextView (the one that was null). Not sure how the button makes the TextView null, but apparently it did.
initializeDbSettingsLay();
volunDbRecycler = findViewById(R.id.volunOrgRecycler);
volunDbListLay = findViewById(R.id.volunOrgListLay);
notVsAccWarning = findViewById(R.id.notVsAccWarning);
noVolunOrgsFound = findViewById(R.id.noVolunOrgsFound);
Moving the function after the findViewById(R.id.noVolunOrgsFound);, it now wasn't null anymore. Not sure why the button either made it null or not properly find the view, since it worked on the other layouts that also is used in that button function. Only the TextView became null. I'd like to say that the TextView can't be assigned after the ClickListener has been created, but generally values can be changed later, and so did the other views. So I'm not sure why it failed with the TextView.
Here is the function:
private void initializeDbSettingsLay() {
...
vsSettingsBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
userSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(Color.WHITE));
dbSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(Color.WHITE));
vsSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(ActivityUser.this.getColor(R.color.slight_gray)));
userSettingsLay.setVisibility(View.GONE);
dbSettingsLay.setVisibility(View.GONE);
vsSettingsLay.setVisibility(View.VISIBLE);
learnMoreVsLay.setVisibility(View.GONE);
learnMoreVsBtn.setText("Learn more");
if (currentUserModel == null || currentUserModel.getVolunAccount() == null) {
volunDbListLay.setVisibility(View.GONE);
notVsAccWarning.setVisibility(View.VISIBLE);
} else {
volunDbListLay.setVisibility(View.VISIBLE);
notVsAccWarning.setVisibility(View.GONE);
loadVolunOrgList();
}
}
});
...
}
private void loadVolunOrgList() {
if (currentUserModel == null || currentUserModel.getVolunAccount() == null || currentUserModel.getOrgsList() == null || currentUserModel.getOrgsList().isEmpty()) {
return;
}
List<OrganisationModel> orgList = currentUserModel.getOrgDataList();
if (orgList.size() == 0) {
noVolunOrgsFound.setVisibility(View.VISIBLE);
return;
}
noVolunOrgsFound.setVisibility(View.GONE);
//Removing both noVolunOrgsFound.setVisibility() above from this function, the rest of the code then worked \/
UserIntegrAdapter orgAdapter = new UserIntegrAdapter(orgList, this, getSupportFragmentManager());
volunDbRecycler.setAdapter(orgAdapter);
volunDbRecycler.setLayoutManager(new LinearLayoutManager(ActivityUser.this));
}

How to set spancount for recyclerview

<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv46"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:spanCount="3"
tools:listitem="#layout/wallet_items"
/>
I am implementing recyclerview with N number of items but i want to show only 3 item in list.Also i have set spancount for recyclerview but its not working
instead of app:spanCount="3" use tools:itemCount="3"
In your adapter class, assign a return value 3 of `getItemCount(), it shows only 3 list items
override fun getItemCount(): Int {
return 3
}
Add this condition in Your adapter getItemCount() method:
return Math.min(yourlist.size(), 3);
Like this:
#Override
public int getItemCount() {
return Math.min(memeberList.size(), 3);
}

Position in recylerview changes while scrolling

Seems like position in recylerview changes while scrolling.
What I want to do is like this.
Adapter.java
#Override
public void onBindViewHolder(aViewHolder holder, int position) {
if (position == 0) {
holder.zeroIcon.setVisibility(View.VISIBLE);
} else if (position == 1) {
holder.oneIcon.setVisiblity(View.VISIBLE);
} else {
holder.otherIcon.setVisiblity(View.VISIBLE);
}
// Set text on each item
...
}
#Override
public int getItemCount() { return models.size(); }
public class aViewHolder extends RecyclerView.ViewHolder {
private ImageView zeroIcon;
private ImageView oneIcon;
private ImageView otherIcon;
public aViewHolder(View itemView) {
super(itemView);
zeroIcon = itemview.findViewById(...);
...
}
}
I set these icon's visibility GONE as default in xml file.
When I see the recylerview at first, the icons show up as I expected depending on its position.
However, when I scroll down and scroll up, incorrect icons also show up on incorrect position.
Like otherIcon shows up on the first and second item while scrolling down and up. While scrolling down, zeroIcon and oneIcon show up on some other items.
How can I fix this?
list_item.xml is like this.
<RelativeLayout ...>
<ImageView
android:id="#+id/zero"
android:visiblity="gone"
android:background="#drawable/zero" />
<ImageView
android:id="#id/one"
android:visiblity="gone"
android:background="#drawable/one" />
<ImageView
android:id="#id/other"
android:visiblity="gone"
android:background="#drawable/other" />
Modify it in this way,
if (position == 0) {
holder.zeroIcon.setVisibility(View.VISIBLE);
holder.otherIcon.setVisiblity(View.GONE);
holder.oneIcon.setVisiblity(View.GONE);
} else if (position == 1) {
holder.oneIcon.setVisiblity(View.VISIBLE);
holder.zeroIcon.setVisibility(View.GONE);
holder.otherIcon.setVisiblity(View.GONE);
} else {
holder.otherIcon.setVisiblity(View.VISIBLE);
holder.oneIcon.setVisiblity(View.GONE);
holder.zeroIcon.setVisibility(View.GONE);
}
In RecyclerView you should manage other views also while changing an item.

make footer in new row in recycleview

I have list with only three items , and i'm using GridLayout with two columns , so when calling it i found that rwo two has only one item , and the last is my footer , so i want to something add empty item or move my footer when my list has odd numbers to move new row
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (holder instanceof FooterViewHolder) {
loadChatMessages();
}
private boolean isPositionFooter(int position) {
return position == dish.size();
}
#Override
public int getItemViewType(int position) {
if (isPositionFooter(position)) {
return TYPE_FOOTER;
}
return TYPE_ITEM;
}
#Override
public int getItemCount() {
return dish.size() + 1;
}
You should implement your own version of GridLayoutManager.SpanSizeLookup and add it to your layout manager by calling gridlayoutManager.setSpanSizeLookup(myLookup).
The description of SpanSizeLookup reads
A helper class to provide the number of spans each item occupies.
So for every normal item you'd return 1 (column used) and in case of your footer you need to return 2 (or the amount of columns total) from getSpanSize(int position) and you're set.
This will signal the layout manager that it needs the whole row.

ExpandableListView open collapse problem

I am using a custom expandable list adapter. I highlight a child when the user clicks on it. This works fine until the user opens/collapses a group. Say the user touches Group 2 Item 1. This highlights Group 2 Item 1. Then the user opens Group 1. Now Group 3 Item 2 is highlighted. I have done some testing choosing different items and I can not find a pattern where the highlighted row will jump to. Sometimes its up in the list, sometimes its down. I'm having trouble figuring out the logic to put in my activity's onGroupExpandListener and onGroupCollapseListener to rehighlight the correct view. Any ideas?
EDIT: Current code inside my onChildClickListener
if (groupPosition == 0){
switch(childPosition) {
case 0:
previouslySelectedView.setBackgroundResource(R.color.transparent);
currentlySelectedView.setBackgroundResource(R.color.blue);
break;
Same code for all the groups/children
Item slection in ExpandableListView is made through flat list (absolute positiond). Thus if the newlly opened group is before the currenet selection and has fewer children then the selection will move up and vice versa. I suggest to set choice mode to none and implement onclick/expand logic to handle focus on your own - implement tags for views, for example, and set the currently highlighted item via the tag.
Here are few suggestions:
In the ExpandableListView.OnChildClickListener first you perform ExpandableListView.findViewWithTag(theTag) to check for views with such tag and unmark them (also setTag(null)) and restore the background. Then for the item clicked setTag(theTag) and change the background to selected. Of course you can have some other logic and have multiple items marked. Note that once the view is destroyed you will lose the selection (for example during expands).
Have some custom map or something that will hold a unique ID of the view and the (un)marked state. That's the best solution that will allow to maintain persistent selection across scrolls and expands.
In the backing adapter introduce a "marked" state. Thus the marking will be persistent even between application start/stop. Not a good approach, though, because selection is more of a UI behaviour.
I am currently doing an ExpandableListView selection with Choice Mode Multiple of the list. But since, as I said, the selection is per position I had to sacrifice in terms of functionality - namely, I clear the selection whenever a manipulation is made. The previous implementation was with custom Map holding the selected IDs and - to honest - it was the better approach.
This is how I get selected IDs (remember I use choice mode multiple):
final SparseBooleanArray checkedPositions = expList.getCheckedItemPositions();
final ExpandableListAdapter adapter = expList.getExpandableListAdapter();
List<Long> checkedIds = new ArrayList<Long>();
if (packedPositionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
for (int i = checkedPositions.size() - 1; i >= 0; i--) {
if (checkedPositions.valueAt(i)) {
checkedIds.add(adapter.getGroupId(checkedPositions.keyAt(i)));
}
}
}
In your case, though, you will want to check for CHILD packed positions. Also note that my adapter has stable (unique) ids. If you do not have stable IDs then you can rely on ExpandableListView's getPackedPositionForChild() method and store the packed position as marked.
Right way to do it (Will solve jumping highlight upon group collapse)
Do the following on ExpandableListView:
Step 1. Set choice mode to single (Can be done in xml as android:choiceMode = "singleChoice")
Step 2. Use a selector xml as background (android:listSelector = "#drawable/selector_list_item")
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_mediumAnimTime">
<item android:drawable="#android:color/holo_blue_bright" android:state_pressed="true"/>
<item android:drawable="#android:color/holo_blue_light" android:state_selected="true"/>
<item android:drawable="#android:color/holo_blue_dark" android:state_activated="true"/>
</selector>
Step 3. Call expandableListView.setItemChecked(index,true) in onChildClick() callback.
index is a 0 based index of the child item calculated as follows
Group 1 [index = 0]
Child item 1
Child item 2
Child item 3 [index = 3]
Group 2 [index = 4]
Child item 1
Child item 2
Child item 3 [index = 7]
Group 3 [index = 8]
Child item 1
Child item 2
Child item 3 [index = 11]
If we have list headers as well, they would also account for index values.
Here is a working example:
#Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
...
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
parent.setItemChecked(index, true);
return true;
}
My solution for the collapse issue :
mDrawerExpandableList.setOnGroupCollapseListener(new OnGroupCollapseListener() {
#Override
public void onGroupCollapse(int groupPosition) {
if(ExpandableListView.getPackedPositionGroup(selectedFragmentId) == groupPosition) {
mDrawerExpandableList.setItemChecked(mDrawerExpandableList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition)), true);
}
}
});
mDrawerExpandableList.setOnGroupExpandListener(new OnGroupExpandListener() {
#Override
public void onGroupExpand(int groupPosition) {
if(ExpandableListView.getPackedPositionGroup(selectedFragmentId) == groupPosition) {
mDrawerExpandableList.setItemChecked(((ExpandableListView)drawerListView).getFlatListPosition(selectedFragmentId), true);
}
}
});
//MY LIST A FOLLOWS
(0)SALES
(0)SALES
(1)SALES VIEW
(2)SALES RETURN
(3)SALES RETURN VIEW
(1)STOCK
(0)ADD STOCK
(1)VIEW STOCK
(2)SETTINGS
(3)NOTIFICATION
//declare tow integer variable globali as follows
private int lastCheckedGroupPosition=-1;
private int lastCheckedChildPosition=-1;
mDrawerList.setOnGroupClickListener(new OnGroupClickListener() {
#Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
if(groupPosition==2){
lastCheckedGroupPosition=groupPosition;
lastCheckedChildPosition=-1;
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
parent.setItemChecked(index, true);
//TODO Write here the code for opening settings page
...............
...............
...............
return true;
}
else if(groupPosition==3){
lastCheckedGroupPosition=groupPosition;
lastCheckedChildPosition=-1;
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
parent.setItemChecked(index, true);
//TODO Write here the code for opening settings page
...............
...............
...............
return true;
}
return false;
}
});
mDrawerList.setOnChildClickListener(new OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
if(groupPosition==0){
if(childPosition==0){
//TODO Write here the code for opening SALES page
...............
...............
...............
}
else if(childPosition==1){
//TODO Write here the code for opening SALES VIEW page
...............
...............
...............
}
else if(childPosition==2){
//TODO Write here the code for opening SALES RETURN page
...............
...............
...............
}
else if(childPosition==3){
//TODO Write here the code for opening SALES RETURN VIEW page
...............
...............
...............
}
}
else if(groupPosition==1){
if(childPosition==0){
//TODO Write here the code for opening ADD STOCK page
...............
...............
...............
}
else if(childPosition==1){
//TODO Write here the code for opening VIEW STOCK page
...............
...............
...............
}
}
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
parent.setItemChecked(index, true);
lastCheckedGroupPosition=groupPosition;
lastCheckedChildPosition=childPosition;
return false;
}
});
mDrawerList.setOnGroupExpandListener(new OnGroupExpandListener() {
#Override
public void onGroupExpand(int groupPosition) {
if(groupPosition==lastCheckedGroupPosition){
if(lastCheckedChildPosition!=-1){
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForChild(lastCheckedGroupPosition, lastCheckedChildPosition));
mDrawerList.setItemChecked(index, true);
}
else{
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(lastCheckedGroupPosition));
mDrawerList.setItemChecked(index, true);
}
}
else{
if(mDrawerList.isGroupExpanded(lastCheckedGroupPosition)){
if(lastCheckedChildPosition!=-1){
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForChild(lastCheckedGroupPosition, lastCheckedChildPosition));
mDrawerList.setItemChecked(index, true);
}
else{
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(lastCheckedGroupPosition));
mDrawerList.setItemChecked(index, true);
}
}
else{
mDrawerList.setItemChecked(-1, true);
}
}
}
});
mDrawerList.setOnGroupCollapseListener(new OnGroupCollapseListener() {
#Override
public void onGroupCollapse(int groupPosition) {
if(groupPosition==lastCheckedGroupPosition){
if(lastCheckedGroupPosition!=-1){
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(lastCheckedGroupPosition));
mDrawerList.setItemChecked(index, true);
}
else{
mDrawerList.setItemChecked(-1, true);
}
}
if(mDrawerList.isGroupExpanded(lastCheckedGroupPosition)){
if(lastCheckedChildPosition!=-1){
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForChild(lastCheckedGroupPosition, lastCheckedChildPosition));
mDrawerList.setItemChecked(index, true);
}
else{
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(lastCheckedGroupPosition));
mDrawerList.setItemChecked(index, true);
}
}
else{
mDrawerList.setItemChecked(-1, true);
}
}
});

Categories