Android: what ListView.setScrollIndicators(up, down) does? - android

I tried to consult documentation, but found setScrollIndicators empty there. What it does?

I tried to search but couldn't find documentation either.
But while looking at source of AbsListView, I found following
View mScrollUp;
View mScrollDown;
public void setScrollIndicators(View up, View down) {
mScrollUp = up;
mScrollDown = down;
}
void updateScrollIndicators() {
if (mScrollUp != null) {
boolean canScrollUp;
// 0th element is not visible
canScrollUp = mFirstPosition > 0;
// ... Or top of 0th element is not visible
if (!canScrollUp) {
if (getChildCount() > 0) {
View child = getChildAt(0);
canScrollUp = child.getTop() < mListPadding.top;
}
}
mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
}
if (mScrollDown != null) {
boolean canScrollDown;
int count = getChildCount();
// Last item is not visible
canScrollDown = (mFirstPosition + count) < mItemCount;
// ... Or bottom of the last element is not visible
if (!canScrollDown && count > 0) {
View child = getChildAt(count - 1);
canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
}
mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
}
}
Here mListPadding is
Rect mListPadding = new Rect();
This might help in understanding concept better. I haven't tried this yet but from my understanding, if top of first element of the listview or bottom of the last element or last element is not visible and if list can be scrollable (to up or down) then respective view gets visible by calling updateScrollIndicators() method
Hope this will be useful for you

Related

How to determine the most visible view position after the RecyclerView adapter data change?

I want to determine the most visible item in my RecyclerView and so I use the following method:
public int getMostVisibleIndex() {
// try to figure which child is the most visible on screen
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
mFirstVisibleIndex = layoutManager.findFirstVisibleItemPosition();
mLastVisibleIndex = layoutManager.findLastVisibleItemPosition();
// How do I use convertPreLayoutPositionToPostLayout() ?
VisibleIndex mostVisible = null;
if (mFirstVisibleIndex != -1|| mLastVisibleIndex != -1) {
// if it's not the same
if (mFirstVisibleIndex != mLastVisibleIndex) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) this.getLayoutManager();
// get the visible rect of the first item
Rect firstPercentageRect = new Rect();
linearLayoutManager.findViewByPosition(mFirstVisibleIndex).getGlobalVisibleRect(firstPercentageRect);
// get the visible rect of the last item
Rect lastPercentageRect = new Rect();
linearLayoutManager.findViewByPosition(mLastVisibleIndex).getGlobalVisibleRect(lastPercentageRect);
// since we're on a horizontal list
if (firstPercentageRect.width() > lastPercentageRect.width()) {
return mFirstVisibleIndex;
} else {
return mLastVisibleIndex;
}
} else {
return mFirstVisibleIndex;
}
}
return -1;
}
It works great, but after I change the data set, and call any notify*Changed methods in my adapter, if I try to use the above function, the item positions that findFirstVisibleItemPosition and findLastVisibleItemPosition return are wrong.
I noticed that they both use getlayoutposition behind the scenes, and I also noticed that on the documentation it says:
If LayoutManager needs to call an external method that requires the adapter position of the item, it can use getAdapterPosition() or convertPreLayoutPositionToPostLayout(int).
It sounds as if convertPreLayoutPositionToPostLayout is EXACTLY what I'm looking for, but I have no idea how to access it from within a RecyclerView.
Any ideas?
Thank you.
In my case I can only have maximum 2 visible views at a time in recyclerview and
I am using this method in onScrolled for always having the mostVisibleItemPosition
for playing video content when user scrolls.
So getTop() returns top position of this view relative to its parent and when user starts scrolling firstView is getting out of the screen and the firstView.getTop() will return minus value while secondView.getTop() is positive and when secondView top reaches nearly the center of the screen getting absolute value of firstView.getTop() we will determine how many pixels firstView top is above from parent view top and getting secondView.getTop() we will determine how many pixels secondView top is above parent bottom and this is where mostVisibleItemPosition will changed.
private void detectMostVisibleItem() {
int firstItemPosition = layoutManager.findFirstVisibleItemPosition();
int secondItemPosition = layoutManager.findLastVisibleItemPosition();
if (firstItemPosition == secondItemPosition) {
mostVisibleItemPosition = firstItemPosition;
} else {
View firstView = layoutManager.findViewByPosition(firstItemPosition);
View secondView = layoutManager.findViewByPosition(secondItemPosition);
if (Math.abs(firstView.getTop()) <= Math.abs(secondView.getTop())) {
if (mostVisibleItemPosition != firstItemPosition) {
mostVisibleItemPosition = firstItemPosition;
...
}
} else {
if (mostVisibleItemPosition != secondItemPosition) {
mostVisibleItemPosition = secondItemPosition;
...
}
}
}
}

Find first completely visible element in recyclerview

I have a layout in which it has parallax effect. So this are the elements in it -
AppBarLayout
CollapsingToolbarLayout inside AppBarLayout
Toolbar inside CollapsingToolbarLayout
RecyclerView
All this views are within CoordinatorLayout. Now I require to find out what is the first completely visible item of RecyclerView. Normally I used following logic to get it -
int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
But here I am getting lots of 1 when even 0th position is not completely visible.
getChildAt begins at the first visible position, not at the position of the adapter.
Here is the resulting code.
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
View v = layoutManager.getChildAt(0);
if (firstVisiblePosition > 0 && v != null) {
int offsetTop = v.getTop();
chatAdapter.notifyDataSetChanged();
if (firstVisiblePosition - 1 >= 0 && chatAdapter.getItemCount() > 0) {
layoutManager.scrollToPositionWithOffset(firstVisiblePosition - 1, offsetTop);
}
}
I found it myself. As I am using AppBarLayout, I need to check it out whether particular view is available on screen at that particular scroll or not.
I did is:
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
View v = recyclerView.getLayoutManager().getChildAt(1);
int offset = 0;
if (v != null) {
offset = v.getTop();
}
if ((verticalOffset * -1) >= offset) {
layoutBuy.setVisibility(View.GONE);
} else {
layoutBuy.setVisibility(View.VISIBLE);
}
}
I used recyclerView.getLayoutManager().getChildAt(1); because I wanted to work around with that particular position which is 1.
Since, vertical offset becomes minus values while scrolling I multiplied it by -1. Then just checked whether offset and top of the view which I am looking for is same or not.
Thus while using parallax effect in a screen and at that same time one needs to check which view is visible in RecyclerView, needs the logic as mentioned above.

Smoothy scroll to Child in NestedScrollView

I have expandable views inside CardView thats parent is NestedScrollView. I'm trying to create smooth scroll to child when expand animation ended. But I found only one solution:
scrollView.requestChildFocus(someView, someView);
This code works fine, but, when call requestChildFocus it scrolls immediately, and that annoying me a little bit. Is it possible to scroll to child smoothly?
The childView, to which I wanted to scroll, has CardView parrent, so childView.getTop() returns the value relative to the CardView not to the ScrollView. So, to get top relative to ScrollView I should get childView.getParent().getParent() then cast it to View and call getTop().
Scroll position calculates like
int scrollTo = ((View) childView.getParent().getParent()).getTop() + childView.getTop();
nestedScrollView.smoothScrollTo(0, scrollTo);
more to the answer from #jerry-sha
fun NestedScrollView.smoothScrollTo(view: View) {
var distance = view.top
var viewParent = view.parent
//traverses 10 times
for (i in 0..9) {
if ((viewParent as View) === this) break
distance += (viewParent as View).top
viewParent = viewParent.getParent()
}
smoothScrollTo(0, distance)
}
I had child views at different levels to the scrollview so made this function based off the accepted answer to calculate the distance and scroll
private int findDistanceToScroll(View view){
int distance = view.getTop();
ViewParent viewParent = view.getParent();
//traverses 10 times
for(int i = 0; i < 10; i++) {
if (((View) viewParent).getId() == R.id.journal_scrollview) {
return distance;
}
distance += ((View) viewParent).getTop();
viewParent = viewParent.getParent();
}
Timber.w("view not found");
return 0;
}
Then scroll using
journal_scrollview.smoothScrollTo(0, distance);
You can use my library ViewPropertyObjectAnimator for that.
Assuming mNestedScrollView is your NestedScrollView and mChildView is the child View you want to scroll to, you can do the following:
ViewPropertyObjectAnimator.animate(mNestedScrollView).scrollY(mChildView.getTop()).start();
Just make sure mChildView.getTop() is not 0 at the moment of calling .animate(...).
Edit:
As I said: make sure your View's top is non-zero when CALL .animate(...). In other words: call .animate(...) only when your child View already has dimensions. How can you determine that? For example like this:
mChildView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int width = mChildView.getWidth();
int height = mChildView.getHeight();
if (width > 0 && height > 0) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
mChildView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mChildView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
ViewPropertyObjectAnimator.animate(mNestedScrollView)
.scrollY(mChildView.getTop())
.start();
}
}
});
Try to read the source code.
svMain.setSmoothScrollingEnabled(true);
Rect rect = new Rect();
rect.top = 0;
rect.left = 0;
rect.right = tv4.getWidth();
rect.bottom =tv4.getHeight();
svMain.requestChildRectangleOnScreen(tv4,rect,false);
rect is the place u want the view to be shown on screen.
The main issue is to calculate the correct x/y position relative to the nestedscrollview. However, most of the answers here try to propose a solution by navigating in the hierarchy and hardcoding the hierarchy-level of the desired view. Imho, this is very error prone, if you change your viewgroup hierarchy.
Therefore, I would suggest to use a much more cleaner approach, which computes the relative position based on a Rect. Then, you can use the nestedscrollview's smoothScrollTo(..) methods to scroll to the desired position.
This should get the work done, where childView is the view you want to scroll to
public static void scrollToView(final NestedScrollView nestedScrollView, final View viewToScrollTo) {
final int[] xYPos = new int[2];
viewToScrollTo.getLocationOnScreen(xYPos);
final int[] scrollxYPos = new int[2];
nestedScrollView.getLocationOnScreen(scrollxYPos);
int yPosition = xYPos[1];
if (yPosition < 0) {
yPosition = 0;
}
nestedScrollView.scrollTo(0, scrollxYPos[1] - yPosition);
}
I find the accepted answer to work, but it is specific to their view structure as of now and it could not be used as a static method to use for all similar scrolls with different view structures. I made a variety of it in a utility class that measures until it find it's ScrollView or NestedScrollView parent. I made it so that the scrollToView(View,View) method should work with both ScrollView and NestedScrollView in case I would update which I use later on or whatever. You could of course call the right "child method" directly.
public static void scrollToView(View scrollView, View scrollToView) {
if (scrollToView == null) {
Log.d(TAG, "scrollToView() failed due to scrollToView == NULL!");
return;
}
if (scrollView instanceof NestedScrollView) {
scrollToInNestedView((NestedScrollView) scrollView, scrollToView);
} else if (scrollView instanceof ScrollView) {
scrollToInScrollView((ScrollView) scrollView, scrollToView);
} else {
Log.d(TAG, "scrollToView() failed due to scrollView not appearing to be any kind of scroll view!");
}
}
public static void scrollToInNestedView(NestedScrollView scrollView, View scrollToView) {
if (scrollView == null || scrollToView == null) {
return;
}
scrollView.post(() -> {
int startY = scrollView.getScrollY();
int requiredY = scrollToView.getTop();
View parent = (View) scrollToView.getParent();
while (parent != null && !(parent instanceof NestedScrollView)) {
requiredY += parent.getTop();
parent = (View) parent.getParent();
}
if (requiredY != startY) {
scrollView.smoothScrollTo(0, requiredY);
}
});
}
public static void scrollToInScrollView(ScrollView scrollView, View scrollToView) {
if (scrollView == null || scrollToView == null) {
return;
}
scrollView.post(() -> {
int startY = scrollView.getScrollY();
int requiredY = scrollToView.getTop();
View parent = (View) scrollToView.getParent();
while (parent != null && !(parent instanceof ScrollView)) {
requiredY += parent.getTop();
parent = (View) parent.getParent();
}
if (requiredY != startY) {
scrollView.smoothScrollTo(0, requiredY);
}
});
}

Moving to a certain position in a ListView upon creation. (Android) [duplicate]

Is there a way that I can makle sure a given item in an android listview is entirely visible?
I'd like to be able to programmatically scroll to a specific item, like when I press a button for example.
ListView.setSelection() will scroll the list so that the desired item is within the viewport.
Try it:
public static void ensureVisible(ListView listView, int pos)
{
if (listView == null)
{
return;
}
if(pos < 0 || pos >= listView.getCount())
{
return;
}
int first = listView.getFirstVisiblePosition();
int last = listView.getLastVisiblePosition();
if (pos < first)
{
listView.setSelection(pos);
return;
}
if (pos >= last)
{
listView.setSelection(1 + pos - (last - first));
return;
}
}
I believe what you are looking for is ListView.setSelectionFromTop() (although I'm a bit late to the party).
Recently I met the same problem, paste my solution here in case someone need it (I was trying to make the entire last visible item visible):
if (mListView != null) {
int firstVisible = mListView.getFirstVisiblePosition()
- mListView.getHeaderViewsCount();
int lastVisible = mListView.getLastVisiblePosition()
- mListView.getHeaderViewsCount();
View child = mListView.getChildAt(lastVisible
- firstVisible);
int offset = child.getTop() + child.getMeasuredHeight()
- mListView.getMeasuredHeight();
if (offset > 0) {
mListView.smoothScrollBy(offset, 200);
}
}
I have a shorter and, in my opinion, better solution to do this : ListView requestChildRectangleOnScreen method is designed for it.
The answer above ensures that the item will be displayed, but sometimes it will be displayed partly (ie. when it is at the bottom of the screen). The code below ensures that the whole item will be displayed and that the view will scroll only the necessary zone :
private void ensureVisible(ListView parent, View view) {
Rect rect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
parent.requestChildRectangleOnScreen(view, rect, false);
}

ensure visible on android listview?

Is there a way that I can makle sure a given item in an android listview is entirely visible?
I'd like to be able to programmatically scroll to a specific item, like when I press a button for example.
ListView.setSelection() will scroll the list so that the desired item is within the viewport.
Try it:
public static void ensureVisible(ListView listView, int pos)
{
if (listView == null)
{
return;
}
if(pos < 0 || pos >= listView.getCount())
{
return;
}
int first = listView.getFirstVisiblePosition();
int last = listView.getLastVisiblePosition();
if (pos < first)
{
listView.setSelection(pos);
return;
}
if (pos >= last)
{
listView.setSelection(1 + pos - (last - first));
return;
}
}
I believe what you are looking for is ListView.setSelectionFromTop() (although I'm a bit late to the party).
Recently I met the same problem, paste my solution here in case someone need it (I was trying to make the entire last visible item visible):
if (mListView != null) {
int firstVisible = mListView.getFirstVisiblePosition()
- mListView.getHeaderViewsCount();
int lastVisible = mListView.getLastVisiblePosition()
- mListView.getHeaderViewsCount();
View child = mListView.getChildAt(lastVisible
- firstVisible);
int offset = child.getTop() + child.getMeasuredHeight()
- mListView.getMeasuredHeight();
if (offset > 0) {
mListView.smoothScrollBy(offset, 200);
}
}
I have a shorter and, in my opinion, better solution to do this : ListView requestChildRectangleOnScreen method is designed for it.
The answer above ensures that the item will be displayed, but sometimes it will be displayed partly (ie. when it is at the bottom of the screen). The code below ensures that the whole item will be displayed and that the view will scroll only the necessary zone :
private void ensureVisible(ListView parent, View view) {
Rect rect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
parent.requestChildRectangleOnScreen(view, rect, false);
}

Categories

Resources