Why does the scrollTo method inside RecyclerView not have any effect? - android

I have a RecyclerView with a LinearLayoutManager set to horizontal. As you can see in the code below, I am trying to animate from the first item of the list to the last item of the list using ValueAnimator. In my usage, I don't need to use the scrollBy or scrollToPosition methods. I need to use the scrollTo method, but it has no effect. Why is this the case?
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,activityMainBinding.rv.computeHorizontalScrollOffset());
valueAnimator.setDuration((100000)); valueAnimator.setStartDelay(1500);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(valueAnimator1 -> {
activityMainBinding.rv.scrollTo((int) valueAnimator1.getAnimatedValue(), 0); });
valueAnimator.start();

If you look inside of RecyclerView, you will see the following for scrollTo():
#Override
public void scrollTo(int x, int y) {
Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
+ "Use scrollToPosition instead");
}
In fact, you should see that text in your logcat.
You say that you must use scrollTo(), but that doesn't seem possible. I suggest that you review this requirement to understand why scrollTo() is required.
I will also suggest that RecyclerView#computeHorizontalScrollOffset() is not doing what you think it is doing.

Related

Android: PageTransformer applying correct scale but not alpha before first touch

I have a PageTransformer that correctly displays my ViewPager with the second element correctly scaled, but there is no alpha applied to the second element. However, upon dragging the ViewPager a miniscule amount, the next view is properly displayed. The code for both my alpha and scale is identical, I'm not sure what the issue here is.
I have tried calling code from a handler once the ViewPagerAdapter has things added to it (such as viewPager.scrollBy() and pager.beginFakeDrag()) but these didn't help.
viewPager.setOffscreenPageLimit(2) is set. I have tried setting alpha to the views programmatically when instantiated in the Adapter but it has no effect. If the scale is correctly being applied then the alpha should be as well, surely.
#Override
public void transformPage(View page, float position)
{
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float alphaFactor = Math.max(MIN_ALPHA, 1 - Math.abs(position));
page.setScaleX(scaleFactor);
page.setScaleY(scaleFactor);
page.setAlpha(alphaFactor);
}
(EDIT) CORRECT SOLUTION:
Replace ViewPager with ViewPager2.
Useful resources:
https://developer.android.com/training/animation/vp2-migration
https://stackoverflow.com/a/57694887/10504386
LAZY (AND NOT SO COOL, ERROR PRONE) SOLUTION:
Unfortunately for me, I did not have the property android:animateLayoutChanges=true set in my layout.
I faced the same problem when I moved the ViewPager from an Activity to a Fragment.
I think the problem is due to the fact I reused the fragment. Upon a FragmentTransaction, the ViewPager technically still has its old value, so its view doesn't need to change, that's why the PageTransformer isn't fired.
Basically, all you need to do is to set the ViewPager's currentItem to 0, and then set it to the value you want to.
This is my snippet (MyViewPager.kt, but you don't need to override ViewPager).
init {
post {
currentItem = 0
}
}
fun setCurrentPage(position: Int) {
post {
setCurrentItem(position, true) // I want smooth scroll
}
}
I had android:animateLayoutChanges=true set on the ViewPager. Once I removed this, it loaded correctly. Don't ask me why it was there in the first place.

Override animation for notifyItemChanged in RecyclerView.Adapter

Well, I have a RecyclerView with an adapter and everything works great. The items in the ArrayList dataset are being updated periodically. So the items and their elements as well as their position in the list change. This is achieved by simple sorting and manually calling these methods, whenever things happen:
// swapping two items
Collections.swap(items, i, j);
itemsAdapter.notifyItemMoved(i, j);
// adding a new one
itemAdapter.notifyItemInserted(items.size());
// when updating valus
itemAdapter.notifyItemChanged(i);
The latter of which, is the cause of my misery. Every time an item is updated, a little "blink" animation is triggered.
I found a couple of solutions for this:
// disabling all animations
recyclerView.getItemAnimator().setSupportsChangeAnimations(false);
// or
// setting the animation duration to zero,
recyclerView.getItemAnimator().setChangeDuration(0);
But both of these kill the animations when items move (being swapped). I just want to override the one animation and keep all of this magic. Is there a way of doing this? And if it's overriding ItemAnimator, does anyone have a simple example?
Thanks in advance!
There is a dedicated method to disable just item changed animations:
((SimpleItemAnimator) myRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
Official docs.
Yes, I did.
First, get the source code of DefaultItemAnimator. Take the code and create a class named MyItemAnimator in your project. Then, set the ItemAnimator to a new instance of your modified MyItemAnimator, like so:
recyclerView.setItemAnimator(new MyItemAnimator());
Now, go in the new classes source code and locate the method
animateChangeImpl(final ChangeInfo changeInfo) { ... }
We simply have to locate the method calls changing alpha values. Find the following two lines and remove the .alpha(0) and .alpha(1)
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { ... }
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).alpha(1).setListener(new VpaListenerAdapter() { ... }
like so
oldViewAnim.setListener(new VpaListenerAdapter() { ... }
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).setListener(new VpaListenerAdapter() { ... }
Try setting:
mRecyclerview.setItemAnimator(null);
Kotlin version of #Pablo A. Martínez answer:
(recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
If you are only modifying the data of Recyclerview means no addition or deletion of item then you should add this line.
mRecyclerview.setHasFixedSize(true);
So that recyclerview will get to know there is no change in size after that you can apply
((SimpleItemAnimator) mRecyclerview.getItemAnimator()).setSupportsChangeAnimations(false);
So that animation will be disappear and code will work like charm :)
Slight variation on Alex's answer.
Instead of removing the alpha change altogether, I just reduced it. the only changes needed were:
//changed alpha from 0
ViewCompat.setAlpha(newHolder.itemView, 0.5f);
and
//changed alpha from 0
oldViewAnim.alpha(0.5f).setListener(new VpaListenerAdapter() {
In both cases, change the 0 to 0.5
This has the effect of removing the flicker associated with the alpha going completely to 0, but keeps the morphing qualities of the item change animation.
In my case the recyclerview took all the space below toolbar. All I did was changing the layout_height of recyclerview from wrap_content to match_parent and blinking disappeared.

How to scroll ListView to the bottom?

I though this would be a simple task, but apparently there is no way to scroll listview to the bottom. All solutions that I found are using variations of setSelection(lastItem) method which only sets selection to last item, but does not scrolls to the bottom of it.
In my case I have a empty listview (with a long empty view header set) and I want to scroll to bottom of it.
So, is there a way to do it?
Edit:
So for those who are interested the working solution is:
getListView().setSelectionFromTop(0, -mHeader.getHeight());
and
getListView().scrollTo(mOffset)
This also works, with right offset (calculated based on current scroll position), but might give you some unexpected results.
If you want the list view to be scrolled always at the bottom even when you are updating the list view dynamically then you can add these attributes in list view itself.
android:stackFromBottom="true"
android:transcriptMode="alwaysScroll"
use the following
lv.setSelection(adapter.getCount() - 1);
If you would like to have a scroll animation programmatically you could easily scroll to the bottom of the list using:
listView.smoothScrollToPosition(adapter.getCount());
scrolling to the top most of the list can be achieved using:
listView.smoothScrollToPosition(0);
Try this one.. it will solve your problem, i tried it and it works great.
listView.post(new Runnable(){
public void run() {
listView.setSelection(listView.getCount() - 1);
}});
private void scrollMyListViewToBottom() {
myListView.post(new Runnable() {
#Override
public void run() {
// Select the last row so it will scroll into view...
myListView.setSelection(myListAdapter.getCount() - 1);
}
});
}
Its working for me
Use following and try.
listview.setSelection(adapter.getCount()-1);
You might want to try myListView.smoothScrollToPosition(myListView.getCount() - 1). This way, you're not forced into selecting the last row. Plus, you get some smooth, beautiful scrolling! (:
I Had the same problem, This works best for me
listView.setSelection(listView.getAdapter().getCount()-1);
if there is no adapter set to your ListView then it has no children at all, empty view is not a child of your ListView
Try using list.scrollTo(0, list.getHeight());

HorizontalScrollView, auto-scroll to end with animation

I have a horizontalScrollView and I need to make an auto-scroll to end with animation when I load the view. I have implemented this method to do it:
final HorizontalScrollView strip = (HorizontalScrollView) contentView.
findViewById(R.id.horizontalScrollView1);
strip.postDelayed(new Runnable() {
public void run() {
strip.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
}, 1000L);
It works fine, but, the main problem is the animation of the scrolling is too fast and I need to implement a slower scrolling. Have you any idea?
This is a demo project I created for one of my projects. Its a scroller the scroll automatically and continously. It was made to show a credits screen by continously scrolling through a list of images.
This might help you or give you some idea.
https://github.com/blessenm/SlideshowDemo
Try this:
ObjectAnimator animator=ObjectAnimator.ofInt(buttonHolderScrollView, "scrollX",targetXScroll );
animator.setStartDelay(100);
animator.setDuration(100);
animator.start();

Scrolling issues with a ListView inside a ScrollView

Here's the scenario conceptually (excluding linearlayouts)
ScrollView
Button
Checkboxes
Spinner
ListView (full-size, non-scrolling)
AdMob advert
i.e. a scrolling pane, which has a filtering UI at the top, followed by results, but the advert must always remain visible, and when scrolling down the filter UI must scroll away, leaving maximum space for results.
I'm aware there are issues with a ListView inside a ScrollView, though for me it is working well in many ways (I'm fixing the length of the ListView to stop it collapsing). So the screen scrolls nicely, the ad stays put at the bottom, and it looks good.
But the problem I'm seeing is, inexplicably, when the activity opens, the ScrollView is scrolled down a bit, so the ListView is at the top of the screen. I assume this is a default behaviour, so I set about trying to force the scroll position of the ScrollView to the top, but I've tried various methods, and see no effect:
scrollview.scrollTo(0, 1000/-1000);
scrollview.smoothScrollBy(0, 1000/-1000);
scrollview.fullScroll(View.FOCUS_UP);
Is there any way to force the ScrollView to start with the scroll position at the top?
If not, how can I have an ad that doesn't scroll off the bottom, but a filter UI that always scrolls off the top? Using ListView seems overkill as I don't need scrolling but it does provide many benefits so would be nice to avoid starting from scratch and rendering everything myself.
Use the following method and enjoy!
private void setListViewScrollable(final ListView list) {
list.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
listViewTouchAction = event.getAction();
if (listViewTouchAction == MotionEvent.ACTION_MOVE)
{
list.scrollBy(0, 1);
}
return false;
}
});
list.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE)
{
list.scrollBy(0, -1);
}
}
});
}
listViewTouchAction is a global integer value.
If you can replace the line
list.scrollBy(0, 1);
with something else please share it with us.
Why are you using a listview if you're not scrolling? Why can't you just use a linearlayout or something more fit to this situation? You mention a filter, you could very easily roll your own filter especially since apparently you just have a few items in your listview.
Use something other that a ListView, you can dinamically generate linearLayouts to show the data you want. You should never use a listview inside a scrollView, it doesnt work for a simple reason, when you scroll, what should scroll, the listview or the scroll view. A couple of people from google have stated not to do this.
I've experienced the issue of a ScrollView starting off scrolled down slightly, the solution was to post a runnable which called the animateTo(0,0) method to get the list to scroll to the top. I found this only worked using anitmatTo(0,0) scrollTo(0,0) didn't seem to work.
Something along the lines of:
mListView.post(new Runnable(){ public void run() { mListView.animateScrollTo(0,0) });
Now as everyone has already stated you shouldn't do the whole ListView inside a ScrollView, but this may be a fix for the problem you had.
I have:
ScrollView
TextView
Button
TextView
ListView
and this work good for me:
scrollView.smoothScrollBy(0, 0);
without this view start from position of listview, after that it start from top
The solution for this issue is to make a request focus to an object in the top of the ScrollView. For example you can use a table layout wrapping the button and request focus to the table layout (If you focus the button it will change its color).
// onCreate method
TableLayout tablelayout = (TableLayout) findViewById(R.id.tablelayout);
tablelayout.setFocusableInTouchMode(true);
tablelayout.requestFocus();

Categories

Resources