Why does LinearLayoutManager.childAt() returns null? - android

I have a recylcerview where the method shown below gets called on each selection of an item. The method only runs sucessfully when I do not scroll the recyclerview, so the firstvisible is 0. The moment I scroll down a bit and select something the first and last visible are set correctly in the method, but as can be seen in the screenshot, for reasons I do not understand the childAt returns null instead of the view I can see on my app screen. In the screenshot for position 7, the first 4-6 returned a child.
Can someone explain to me how this can happen? From my pov, getChildAt() should always return a view in this scenario.

first and last are going to be the adapter positions of the data and not the position as laid out in the layout manager. See LinearLayoutManager#findFirstVisibleItemPosition. The children will always start with zero and increase from there.
That is why it works before your scroll since the child at the zeroth index in the layout manager is also the zeroth item in the adapter.
Here is a discussion about the various positions in RecyclerView.
It looks like you want to make changes to all visible items. Your first and last variables will have the correct start/end adapter positions that correspond to what is visible on the screen. You need the adapter positions to call the various "notify" methods.
So, given the adapter positions, we need a map to the views that are represented on the screen. As an example, the following code loops through every visible view and changes the background color of each view.
LinearLayoutManager lm = (LinearLayoutManager) Recycler.getLayoutManager();
// Get adapter positions for first and last visible items on screen.
int firstVisible = lm.findFirstVisibleItemPosition();
int lastVisible = lm.findLastVisibleItemPosition();
for (int i = firstVisible; i <= lastVisible; i++) {
// Find the view that corresponds to this position in the adapter.
View visibleView = lm.findViewByPosition(i);
visibleView.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
}
If you use the child methods of the layout manager, you will need to loop from zero to LayoutManager.getChildCount() - 1 to make the changes. You will see each attached view which, I believe, can exceed the number of visible views.

Related

Find if the first visible item in the recycler view is the first item of the list or not

I have a recycler view with 13 data items. I want to find out whether the first item of my list is visible or not?
I am aware about the methods like findFirstVisibleItemPosition and findLastVisibleItemPosition but they did not tell whether the first visible item is actually the first item of the list or not.
The problem that I am trying to solve is this, I have a view pager as the first item of my recycler view and I want to stop auto scroll when user scrolls down and it becomes completely invisible.
Please help if anyone has any idea about how to do this.
You can do it by checking if findFirstVisibleItemPosition is 0 or not, like this :
// layoutManager is your recycler view's layout manager
int position = layoutManager.findFirstVisibleItemPosition();
if(position != 0){
stopAutoScroll();
}else{
startAutoScroll();
}
i.e, if findFirstVisibleItemPosition returns 0 we should start auto scroll and if it is not 0, stop swiping.

How to programmatically snap to position on Recycler view with LinearSnapHelper

I have implemented a horizontal recyclerView with LinearSnapHelper, to implement a UI input that selects a particular configuration. Kinda like the old school number picker/selector or spinner. The item in the center is the selected position.
it works fine and all, but here's the problem. On initial start up, I need to programmatically set the position of the recycler view such that the selected item (the index of which was loaded from disk) is position in the center.
.scrollToPosition() wont work becuase it places the selected item in the begining.
now I know I can do all the math and calculate the x coordinate and manually set it, but thats a lot of redundant work because LinearSnapHelper is already doing this, and I feel like there should be a way to just reuse that logic, but with actually initiating a fling.
I need something like LinearSnapHelper.snapToPosition()
More general solution:
First scroll RecyclerView to make target item visible.
Than, take the object of target View and use SnapHelper to determine
distance for the final snap.
Finally scroll to target position.
NOTE: This works only because programmatically you are scrolling at the exact position & covering the missing distance by exact value using scrollBy instead of doing smooth scrolling
Code snippet:
mRecyclerView.scrollToPosition(selectedPosition);
mRecyclerView.post(() -> {
View view = mLayoutManager.findViewByPosition(selectedPosition);
if (view == null) {
Log.e(WingPickerView.class.getSimpleName(), "Cant find target View for initial Snap");
return;
}
int[] snapDistance = mSnapHelper.calculateDistanceToFinalSnap(mLayoutManager, view);
if (snapDistance[0] != 0 || snapDistance[1] != 0) {
mRecyclerView.scrollBy(snapDistance[0], snapDistance[1]);
}
}
});
Try calling smoothScrollToPosition on the RecyclerView object, and passing the position index (int)
mRecyclerView.smoothScrollToPosition(position);
Worked for me with a LinearLayoutManager and LinearSnapHelper. It animates the initial scroll, but at least snaps the item in position.
This is my first post on the stack, hope it helps :)
I have a recyclerView which I have added padding at the left and right with dummy views in the adapter. So that the first "actual" item can be snapped to.
I couldn't get smoothScrollToPosition(0) to work though for the initial snap. I used the following
recycler.scrollBy(snapHelper.calculateDistanceToFinalSnap(binding.recycler.getLayoutManager(), recycler.getChildAt(1))[0], 0);
Isn't the nicest looking way, but seems to work!

RecycleView: how to go to a row without scrolling

If the recyclerview has a list of items and I want to go to a specific row immediately when user enters the UI, so I don't want the user to see it scroll to reach that row. Is it achievable?
Thanks.
You can use method scrollToPositionWithOffset (int position, int offset)
Scroll to the specified adapter position with the given offset from
resolved layout start.
See documentation
Example:
//Scroll to item position 2 with offset 0
RECYCLERVIEW_LAYOUT_MANAGER.scrollToPositionWithOffset(2, 0);
Hope this will help~
I would say it is impossible.
RecyclerView#scrollToPosition causes sort of animation because it does not know the height of all rows initially. Not all items are loaded immediatly but one by one as you scroll down the list.
I use ListView and I solved the issue by calling setSelection(position)

listview.getChildAt(i) returning NULL

I have a listview with very large number of elements and i have set the middle element in the middle of the screen (mRootLayoutHeight is height of the screen)
listview.setSelectionFromTop(adapter.getCount()/2,mRootLayoutHeight/2);
But when i try to access all the visible elements of listview on screen using
for(int i=listview.getFirstVisiblePosition();i<=listview.getLastVisiblePosition();i++)
View v=listview.getChildAt(i);
View v is always NULL. Why is that?
getFirstVisiblePosition, as per the documentation returns:
Returns the position within the adapter's data set for the first item displayed on screen.
So, if you have 100 items in your data set, and you scroll down the list, you might be looking at items 15-23. So you'd be iterating over i=15 to i=23.
The getChildAt method is from the base ViewGroup class however, and returns items at that index on screen - so if you have 8 items on screen (as per the example above), you'll only be able to get items from index 0 - 8, even though those items exist at a different position within the dataset.
So in the example I've supplied here, if you attempted to get the view at index 15, you'd get null, since there are only 8 views in the ListView view group.

Android ListView y position

It seems like ListView doesn't expose its y-position in the same way a ScrollView does. In other words: I need to remember the exact position the ListView was scrolled to and set it when I return to the activity.
Just to clarify: I don't need the selected item... that's pretty straight forward. I need to restore the exact pixel-wise y position on the ListView.
Thanks!
I've used this successfully when saving (in onSaveInstanceState) and restoring (onCreate), when switching orientations:
Saving:
int savedPosition = list.getFirstVisiblePosition();
View firstVisibleView = list.getChildAt(0);
int savedListTop = (firstVisibleView == null) ? 0 : firstVisibleView.getTop();
Restoring:
if (savedPosition >= 0) { //initialized to -1
list.setSelectionFromTop(savedPosition, savedListTop);
}
This will precisely save the Y position. Well, it misses by a few pixels every once in a while.
When you are returning from another Activity, the ListView will remain scrolled to its original position that it was at when you left that ListView Activity. If you are updating the contents of the list make sure you just use notifyDataSetChanged() on the adapter, do not re-assign the adapter - this will reset the list to the top.
Check your onResume method in your ListView Activity, it might be re-assigning a list adapter.
If you need to remember an arbitrary scroll position that doesn't rely on the Activity stack, I am willing to bet that isn't possible besides just saving the current selected or first visible item. A ListView does not have a defined height. It is relative to both the number of items and content of those items.
Parcelable state = list.onSaveInstanceState();
// do stuff
list.onRestoreInstanceState(state);
Is the only correct way I know of to maintain exact position of a list. The above solution that's marked as correct bumps up/down a few pixels so not really the most professional solution.

Categories

Resources