Android ListView y position - android

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.

Related

Why does LinearLayoutManager.childAt() returns null?

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.

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!

Android - dynamically update GridView views

I've got a GridView with a custom Adapter, displaying thumbnails of pictures to the user. I can select and deselect pictures (I use setAlpha(0.25) on the views to notify the user of the change), and that's all well and fine.
Now what I want to do next, is have a button on top of the gridview that clears the whole selection, i.e. call setAlpha(1.0) on all views that were changed. So far I can reset my elements in the adapter, and I can setAlpha to 1, but the view doesn't update unless I scroll it out of the display, and then get back to it, or notify the adapter of changes, which redraws all my views, which does not look too pretty if only one element was selected.
I already dynamically set and reset individual elements through the GridView's onClickListener, but can't do this for more. I even tried calling performClick on all selected items through my button, but again it only displays the changes after the views have been out of the screen and are shown again.
Here's how I simulate the clicks:
for (int i = 0; i < mAdapter.getCount(); i++) {
PictureForSelection tempPic = (PictureForSelection) mAdapter.getItem(i);
if (tempPic.isPicSelected()) {
//tempPic.setIfSelected(false);
gridview.performItemClick(mAdapter.getView(i, null, null), i, i);
}
}
EDIT:
Conclusion - don't simulate clicks like this :)
So now I skip click simulation and use this:
gridview.getChildAt(i).setAlpha((float) 1.0);
In general it does exactly what I wanted it to do, except for one case:
If I have for example 3 pictures selected, one is off screen, one is partly shown, and one is fully shown, the ones shown (even partially) don't update their views.
If all the selected pictures are displayed it works flawlessly, but if some are out of the display, the rest do not update until the adapter's getView() gets called by Android. Still, thanks #mmlooloo for getting me so far.
If anyone knows a way around this, please do share :)
After setting your alpha you can call imageview.invalidate(); this causes your imageview to redraw itself.
if notifyDataSetChanged() is not working you can try like this.
i'm not sure whether this works,
1.onclick of the button you set the gridView.setAdapter(null);
2. Then set a new CustomAdapter object to gridview with default values.

Maintain Scroll Position of GridView through Screen Rotation

How can I maintain the scroll position of a GridView (it's filled with search results) throughout a screen reorientation?
I have the GridView inside a Fragment, and when I rotate the screen it jumps back up to the top of the list.
To make it even more tricky, in landscape mode my GridView has 3 columns.. in portrait mode it has 2 columns.
I can't seem to figure out how to keep the fragment's GridView scrolled to anywhere near where it should be when the screen orientation changes.
You can use the following method, it will work for all Android versions.
To save the current scrolling position:
int index = gridView.getFirstVisiblePosition();
Then after orientation change, use the following code to set the gridView position to the saved index:
gridView.setSelection(index);
You can try this:
Initially you have to get the current scrolling position of the GridView by calling:
int index = gridview.getFirstVisiblePosition();
Then you have to save this value while orientation changes and when the GridView is created again you have to move your gridview to this index.
I suppose this could work:
gridview.smoothScrollToPosition(int index)
Hope this helps!
Using of getFirstVisiblePosition() is good, but I've found a nice solution, just to save a state of view.
//state of view (includes scroll position) as a Parceble
Parcelable mobileGalleryState = gridView.onSaveInstanceState();
//just restore previous state including all changes are made before
gridView.onRestoreInstanceState(mobileGalleryState);
By the way, you can use it for ListView also.

Listview + BaseAdapter - how to notify about change in single item?

I have a list view fed by BaseAdapter.
When something changes in data, I call BaseAdapter.notifyDataSetChanged. All works fine.
Now I wonder, if I change some tiny detail in some list item, is there some optimized way to update view of single item if it's displayed on screen?
I suppose that notifyDataSetChanged blindly rebuilds views of all visible items in list, which is not optimal if trivial change in item happens.
No, I guess it's not possible unless you implement your own ListView class extension.
Here there is the source code for setAdapter().
You will see, the ListView only registers itself as observer using mAdapter.registerDataSetObserver(mDataSetObserver);
And a DataSetObserver provides no means to notify about a change at a certain position.
However it might not be necessary to notify about updates of certain items, because as far as I know, a ListView only renders and updates the items currently seen on the screen so, no optimization should be necessary here.
Yes, there is way. Assuming I can identify list item, for example by assigning View.setTag to it to some value, I can iterate list items and rebind only one list item if desired, or even update only some sub-view of the item.
It's simple and relatively cheap (linear search):
for(int i = list.getChildCount(); --i>=0; ){
View v = list.getChildAt(i);
Object id = v.getTag();
if(id==myId){
updateListItem(id, v);
break;
}
}
where myId is some ID of item I want to update, and updateListItem makes desired changes on the list item. Proved, and working fine and very efficiently.

Categories

Resources