why recycler view items are not rearranging? - android

I have a recycler view and there are items in it. Now I am using bubble sorting to rearrange them. But adapter.notifyItemMoved doesn't work properly in the loop (it is slow). Please look at the code below and help, I have added comments to make u understand. One more thing, I can't sort the list in beginning before adapting. So please don't put that as a solution. I know that this is a solution but that will not work in my case.
//rv_all is a recycler view
for (i in 0 until rv_all.childCount)
{
for (j in 0 until rv_all.childCount - 1)
{
/* i have added tag while binding in ViewHolder
itemView.stdate.tag = "date" */
val date1 =
sdf.parse(rv_all.getChildAt(j).findViewWithTag < TextView("date").text.toString())
val date2 =
sdf.parse(rv_all.getChildAt(j + 1).findViewWithTag < TextView("date").text.toString())
if (date1 != null && date2 != null)
{
if (date1.before(date2))
{
//alllist is a list used to adapt to recycler view
Collections.swap(alllist, j, j + 1)
alladapter.notifyItemMoved(j, j + 1)
// problem is here ,it is working fine when not in a loop but in loop it is doing nothing
// i have debugged and used breakpoints and what i saw that alllist is swapping elements but adapter child are not rearranging
}
}
}
}

you are iterating through childrens of RecyclerView (why twice? you arent using i anywhere, this is unefficient)
for (i in 0 until rv_all.childCount) {
for (j in 0 until rv_all.childCount - 1) {
but these are only Views - first child/View is at position 0, second 1 etc. When you scroll a bit down and your RecyclerView first visible item is e.g. 10th in alllist, then still first visible View is at position 0, like always
thus these lines makes no sense:
Collections.swap(alllist,j,j+1)
alladapter.notifyItemMoved(j,j+1)
they always swapping and notifying items at the beggining of array, starting 0, but your RecyclerView can be scrolled a bit down to e.g. 10th item - then above lines are swapping items in alllist, but notifyItemMoved does nothing as RecyclerView doesn't have to redraw first items, they are "scrolled out"
so in short: position of View drawn in RecyclerView != position in data array
you can add "real_position" tag in adapter to every child, then you can still iterate through visible childs/Views, obtain Views with findViewByTag, but swap and notifyItemMoved for positions in data array ("real_position" obtained from tag), not visible childs positions in parent RecyclerView
var realPosition : Integer = rv_all.getChildAt(j).tag as Integer // set in adapter
Collections.swap(alllist, realPosition, realPosition+1)
alladapter.notifyItemMoved(realPosition, realPosition+1)

You can try using notifyDataSetChanged():
class MovieAdapter(...) : RecyclerView.Adapter<MovieViewHolder>() {
var data: List<Movies> = list
set(value) {
field = value
notifyDataSetChanged()
}
...
For a more effient way of refreshing use DiffUtil.ItemCallback<...>(). See the Docs

Related

Android ListView Adapter adding views

I'm attempting to change the color of certain items in a ListView. It crashes with a NullPointerException, I'm not exactly sure why, I think it's because the Adapter hasn't created/added the view to the ListView, so it's trying to retrieve an item that isn't on the array. Whenever there is at least 1 item on the list, I am able to add the colored items perfectly fine. How can I resolve this?
int index = 0;
for(ItemEntry i: tentry) {
adapter.add(i.Name); // Adding to Adapter
adapter.notifyDataSetChanged(); // Telling it I've done so
long time = TimeUnit.MILLISECONDS.toDays(i.Date.getTime() - System.currentTimeMillis());
ListView stuff = (ListView) this.findViewById(R.id.contentsList);
if( time < 0 ) {
stuff.getChildAt(index).setBackgroundColor(Color.RED); // Null exception
} else if( time < 1 ) {
stuff.getChildAt(index).setBackgroundColor(Color.RED); // Null exception
} else if( time < 2 ) {
stuff.getChildAt(index).setBackgroundColor(Color.YELLOW); // Null exception
}
index++;
}
Don't reach into children by index to make UI changes like this. It's begging for trouble:
stuff.getChildAt(index).setBackgroundColor(Color.RED); // Null exception
Instead, make changes to the item views in the getView() of your adapter based on some logic that determines which individual items should have what background color.
You should put this kind of logic in the getView method of your Adapter.
There you can modify the backgroundColor of the item depending on its value.
There are many threads on this topic.
For example take a look here: How can I change the background color of list items based on the data being displayed in each item?

Get RecyclerView child Views

I am trying to get all the Views of RecyclerView items but it seems I can get only the first one. I am fetching the Views like this:
LinearLayoutManager manager = (LinearLayoutManager) rv.getLayoutManager();
List<View> list = new ArrayList<View>();
for(int i = 0; i < manager.getItemCount(); i ++ ) {
manager.scrollToPosition(i);
View view = manager.findViewByPosition(i);
if(view == null) {
Log.i("Null Check", "Null: " + i);
}
list.add(view);
}
The problem is that I fetch the first View in RecyclerView all the time. If I have 10 items, first item will be fetched and other 9 items will be logged as null. It's like scrollToPosition(i) is not working correctly.
This is scrollToPosition(i) javadoc:
Scroll the RecyclerView to make the position visible. RecyclerView
will scroll the minimum amount that is necessary to make the target
position visible. If you are looking for a similar behavior to
android.widget.ListView.setSelection(int) or
android.widget.ListView.setSelectionFromTop(int, int), use
scrollToPositionWithOffset(int, int). Note that scroll position change
will not be reflected until the next layout call.
I am worried because of this last sentence. What do you think how can I solve this?
Retrieve RecyclerView children through the assigned RecyclerView.Adapter not the layout manager. The layout manager assists in laying out children views (ie. measuring and layout) but you should be consulting the Adapter assigned to manage the RecyclerView's data and not its layout manager.

Change background color of ListView item after 3 seconds

I have a list of Items that are "seen" or "not seen" in ArrayList<Item>. If they're not seen I change the background color of the ListView item in my CustomArrayAdapter like this :
if(item.is_seen == null || item.is_seen == 0) {
row.setBackgroundResource(R.color.yellow);
} else {
row.setBackgroundResource(R.color.transparent);
}
Now what I want to do is set all items background to transparent after 3 seconds spent on the page.
I already tried to do something like this:
mScheduledExecutor.schedule(new Runnable() {
#Override
public void run() {
for(int i=0; i<mItems.size(); i++) {
final Item n = mItems.get(i);
if(n.is_seen == null || n.is_seen == 0) {
// update value in db
int isSeen = 1;
updateItem(n._id, isSeen);
// change the color of backgrounds
View view = listViewItem.getChildAt(i);
if(view != null) {
view.setBackgroundResource(R.color.red);
}
}
}
Updating the value in the DB works, but the rest doesn't. But I'd like to avoid to reload the data. I just need the color to change.
I don't have errors, it just does nothing.
I look everywhere for an answer and didn't find one.
Am I wrong since the beginning? Is what I want to achieve even possible?
I thank you in advance for all the help you could give me.
Instead of changing the color of the view like youre doing,
// change the color of backgrounds
View view = listViewItem.getChildAt(i);
if(view != null) {
view.setBackgroundResource(R.color.red);
}
(code above will not work, because the views are recycled in the ListAdapter) update the DATA off which you build your list - add a property to the class you are passing into your ListAdapter, then grab that instance from the list and update that property, you have the position at which it needs to be updated already, so that's easy. Then, call notifyDataSetChanged() on the list. It will not redraw the list if you didn't ADD/REMOVE items from list, but it will update correct view for you. This is the only way to do it - absolutely NO WAY to get to a view corresponding to a specific element in a list after list has been drawn already. Only way is to refresh/redraw the list with notifyDataSetChanged(), followed by refreshDrawableState().

Synchronize two ListView positions when you scroll from both list views Android

I have two ListViews. Is there any way to synchronize the position of ListViews when I scroll both the Lists
Use the following method to get the scroll position of your first listView-
private void saveListScrollPosition()
{
// save index and top position
index = _listview1.getFirstVisiblePosition();
View view = _listview1.getChildAt(0);
top = (view == null) ? 0 : view.getTop();
}
And scroll the second listView to that position with-
// restore
_listview2.setSelectionFromTop(index, top);
You could use this in your second list view: smoothScrollToPosition(position)
And in your first ListView you could use a OnScrollListener and check the first visible item with getFirstVisiblePosition.
Best wishes,
Tim

ListView getChildAt returning null for visible children

I'm getting some strange behavior from a listview/the getChildAt method.
I have a HashSet, iconsToUpdate, of icons that have been changed in the database. I want to iterate over the visible rows to see if any of their icons need to be updated to reflect the new icons. I don't need to test the icons not currently in view as they will be drawn properly when rendered.
My problem is that getChildAt is returning null when it seems like it shouldn't. I know that getChildAt can only return views that are currently visible, but it is returning null for some of the visible rows.
Here is my code that iterates over the visible rows:
Logger.debug("First visible index: " + f_listView.getFirstVisiblePosition());
Logger.debug("Last visible index: " + f_listView.getLastVisiblePosition());
for (int i = f_listView.getFirstVisiblePosition(); i <= f_listView.getLastVisiblePosition(); i++) {
String tag = "asdf"; // Remove when bug is fixed.
if (f_listView == null) {
Logger.debug("f_listView is null");
} else if (f_listView.getChildAt(i) == null) {
Logger.debug("Child at index " + i + " is null");
} else {
tag = (String) f_listView.getChildAt(i).getTag();
Logger.debug("Successful at index " + i + ", tag is: " + tag);
}
if (iconsToUpdate.contains(tag)) {
setIcon(i, f_aim.getInHouseIcon(tag));
}
}
Here is the log corresponding to a run of this loop:
D/...: First visible index: 3
D/...: Last visible index: 8
D/...: Successful at index 3, tag is: ...
D/...: Successful at index 4, tag is: ...
D/...: Successful at index 5, tag is: ...
D/...: Child at index 6 is null
D/...: Child at index 7 is null
D/...: Child at index 8 is null
It should be noted that the first and last visible indexes are being correctly reported, as I am viewing rows 3-8 when I run this. Rows 6, 7, 8 are being rendered properly. How are they being displayed if they are null?
Also, I do not know if this is important, but row 5 is the last visible row when I am at the top of the listview.
Any info as to why these rows are being returned as null would be greatly appreciated.
Thanks!
listView.getChildAt(i) works where 0 is the very first visible row and (n-1) is the last visible row (where n is the number of visible views you see).
The get last/first visible return the position in the dataAdapter you have. So you since you start at position 3, with what looks like 6 visible views, that's when get for positions 6-8 you get null.
In your example getChildAt(0) would return position 3. What I usually do is store the position on my views so I can lookup on my dataAdapter later if I need values.
I think your for loop should look like this:
for (int i = 0; i <= f_listView.getLastVisiblePosition() - f_listView.getFirstVisiblePosition(); i++)
Try
f_listView.getChildAt(positionOfChildYouWantGet - f_listView.getFirstVisiblePosition());
When you call listView1.getLastVisiblePosition() and listView1.getFirstVisiblePosition(), the listview returns the positions of the items that are partially visible in the listview. For example, the first item may be half visible and the last item may be half visible. In this case, even though you can see part of the item in the listview, the adapter has not yet called the getView() function for the item and therefore the item is still considered null.
In my case i have a listView and the first item is full visible but when I do getFirstVisiblePosition() the result is 1 !
It gets more weird when I scroll and make the first item half visible then my getView show that getFirstVisiblePosition() is 0.
this my code :
public View getView(final int position, View convertView, final ViewGroup parent) {
row = convertView;
final AtomPaymentHolder holder = new AtomPaymentHolder();
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
row.setTag(items.get(position));
holder.songitem = items.get(position);
final int firstPosition = MainActivity.listviewSong.getFirstVisiblePosition() - MainActivity.listviewSong.getHeaderViewsCount();
final int lastPosition = MainActivity.listviewSong.getLastVisiblePosition();
if(MusicService.indexService>= firstPosition && MusicService.indexService<= lastPosition){
MainActivity.listviewSong.getChildAt(MusicService.indexService-firstPosition).setBackgroundColor(getContext().getResources().getColor(R.color.pressed_color));
}
(when I selected the next item and scroll, it works good)
They are partially visible maybe. But when the getView() tries to recycle the views it gets FULLY visible ones other takes them as null. for example if you scroll the list and the top item is partially visible and the bottom is partially visible so these are nulls but you still see them.
I tried by ever mean in OnListItemClickListener(),but fails. At last I made some modification in my customized adapter for listview. Here in getView(),I apply clickListener on the item which i added into the list frequently. n do all required functionality there. Here is my Code, where i add Image View in the list n so apply listener on imageview.
I Think it will help those who want to change the color when specific List Item is >selected. Go For it..
In getView() of customized Adapter
//---------------------------------code------------------------------------------
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.icon_image_layout, parent, false);
ImageView imageView = (ImageView) rowView.findViewById(R.id.Icon_ImageView);
imageView.setClickable(true);
final int pos=position;
imageView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
// TODO Auto-generated method stub
try{
if(previous_view!=null)
previous_view.setBackgroundColor(Color.WHITE);
}catch (Exception e) {
System.out.println("Exception Occurs Previous View");
}
v.setBackgroundColor(Color.RED);
MainActivity.imageView.setImageResource(MainActivity.Image_Name[pos]);
previous_view=v;
return false;
}
});
I know this is very old post. But I'm answering because people are still looking for a work around on ListView getChildAt() null pointer exception.
This is because the ArrayApdater is REMOVING and RECYCLING the views that are not visible yet on the ListView because of height. So that if you have 10 item views, and ListView can display 4 - 5 at a the time :
The Adapter REMOVE the item views at position 5 to 9, so that any attempt to adapter.getChildAt(5... to 9) will cause null pointer exception
The Adapter also RECYCLE the item view, so that any reference you made on position 3 for example will be lost when you scroll down to 5 to 9, and also any Input that you make on position 3 (EditText, Checkbox, etc.) will be recycled when you scroll down to 5 to 9 and will be reused at another position later (ex position 1, 2 or 3, etc.) with the same value
The only way I found to control this is to forget about getting the View and to have :
Attribute HashMap<Integer, ImageView> iconViews or any type you want for handling the values you want to use for each item on the list. The first type must be unique for item like item->getId() or position. Initialize it with new HashMap<>() in the Constructor;
in getViews make iconViews.put(position, iconView);
Prevent Adapter from using recycled convertView, remove condition if(convertView == null) so that adapter always inflate a brand new view instance. Because the view instance is new each time, you must set the value from HashMap each time also like if it already contains the key if(iconViews.containsKey(position)){iconView = iconViews.get(position))};. Probably in this case there is not tons of Items, so that smooth scrolling won't be a must.
And finally create public methods to get the Values outside of Adapter passing item->getId() Integer as parameter. Ex : public ImageView getIconViewAt(int position) { return iconViews.get(position); } . It will be easy then to select Item from Adapter
See more from my answer.
Try
if (f_listView.getChildCount() > 0){
f_listView.getChildAt(i)
}
Isn't it because you are saying
f_listView.getChildAt(i)
And you should be retrieving the item at that position?
f_listView.getItemAtPosition(i)

Categories

Resources