Synchronize ListView scroll position - android

I am trying to synchronize ListViews scroll positions in a ViewPager.
I have a ViewPager with Fragments as children. Each fragment contains a ListView. I created a ListViewScrollListener interface, with which I listen to each ListView scroll activity. When one ListView is scrolled by the user, the active fragments in the ViewPager listen to the scroll event and call the following method on their ListView:
listView.setSelectionFromTop(firstVisibleItem, pixelOffset);
Sadly, the scroll positions of the ListView elements in the Fragments do not update. When I put the same setSelectionFromTop method in the onResume() method, it works like a charm, hence the position is set correctly when I swype the ViewPager and the Fragments are being created.
I also checked out StackOverflow for similar problems and tried the following solution, without positive results:
mListView.post(new Runnable() {
#Override
public void run() {
listView.setSelectionFromTop(firstVisibleItem, pixelOffset);
}
});
I checked if the setSelectionFromTop is called correctly, and apparently it is. Any ideas why it's not working?
Update 1
As hinted by Ne0, I also checked to run the setSelectionFromTop method inside the Fragment on the main ui thread, without any positive results.
getActivity().runOnUiThread( new Runnable() {
#Override
public void run() {
listView.setSelectionFromTop(firstVisibleItem, pixelOffset);
}
});
Any other ideas what this could be?

Related

RecyclerView doesn't stay modified after changes

I'm having an issue when deleting or adding items to a RecyclerView element, the problem is not when adding or deleting itself, so I explain the situation.
I have a recycler and a button to add elements to it, if the user press a view, it shows a dialog that contains a delete button and some more stuff. I can add and/or delete items on recycler view, and the view updates ok, using notifyDataSetChanged(), but when I press back button(only by doing this, not any other way) the view reappears again (in case of deletion) or disappear( if I did an adding) setting the recycler the way it was before changes, and I don't want this behavior, because of the delete and add method I created don't just add/delete the view, but associated data in a database too, so If I press the view after deletion the app crash because there are not such data anymore, but the view exists still.
All this content is inside a fragment accessible by a Navigation Drawer, if I navigate other fragment and return to this one again the back press do not affect the recycler anymore, and all those "ghosts" views desapear becouse the data is reloaded from database.
I need a som help for avoiding this, I know undo deletion can be done, but I don want to, I just want to delete data.
UPDATE: This is exactly what happens, the vanishing/reappearing of the items in the recycler View is caused by a back button pressed
This one shows the vanishing after adding
This one shows the reappearing after deleting
UPDATE 2: I've solved the problem by overriding the onBackPressed method using this documentation from google, when back pressed what I do is to navigate to first fragment in the navigation, this is not what I wanted but solved the problem temporarily (as a patch), I'm still looking for answer.
UPDATE 3: Posted my own answer with the fix of all this problem, not using onBackPressed, it worked fine, and I realize some curious behavior
your lists are out of sync. you have 2 lists, one that holds a reference for you collection of objects that are passed to the recycler, the other one is within the recycler to iterate over and draw your view holders.
I suggest once you delete|add sth, beside DB, do it to the outer list (one outside recycler) and pass the list as a whole to the recycler. this way you will ensure that the recycler's list is always up-to-date.
this is not the best solution out there, other possible solutions might include an RX Supported Db with no intermediate calls to invoke the recycler.
As you may notice my app has a navigation drawer, when trying to fix the problem by overridig the onBackPressed() in fragment, I tried to get back to the first fragment in my navigation drawer, when doing so I realize an error that I had not realized before:
NavigationUI.onNavDestinationSelected(menuItem,navController);
This line was duplicated in the NavigationItemSelectedListener:
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull final MenuItem menuItem) {
int id=menuItem.getItemId();
//this line indicates the action of changing fragment using navController reference
NavigationUI.onNavDestinationSelected(menuItem,navController);
if (id==R.id.nav_cuanto){
Handler mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {drawer.closeDrawer(GravityCompat.START);}
},300);
}
if(id==R.id.nav_reportes){
// NavigationUI.onNavDestinationSelected(menuItem,navController); -> this line caused the hole problem
Handler mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {drawer.closeDrawer(GravityCompat.START);}
},300);
}
if(id==R.id.nav_about){
Handler mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
drawer.closeDrawer(GravityCompat.START);
}
},300);
}
return true;
}
});
I deleted the line and everything worked just fine, as expected, this particular method creates a new instance of the fragment you are trying to navigate to, but the problem is that when you try to navigate to the fragment you already are in, it creates a new instance a navigate to it, and this new instance is on the over the old one, so when back pressed the app do not return to previous view, but to the same fragment with the old view (out of date, of couse), I couldn't imagine this behavior, this is highly not imaginable, and it's not in the docs so I almost die trying to understand. So WARNING: if someone is trying to use
NavigationUI.onNavDestinationSelected(menuItem,navController);
do not use it more than once in the same call, if so you will need to press back twice in best case scenario :-(

Why Does RecyclerView Update Without notifyDataSetChanged()?

It is common knowledge to call notifyDataSetChanged() or notifyItemChanged() when you mutate an array that is being handled by a RecyclerView adapter in order to update the view and reflect the changes made. But I spun up a sample application in which neither are called when I add items to an array immediately after calling .setAdapter() in the same block, and the view updates! This behavior doesn't happen when I try to add items via a button's onClickListener or from a background thread (both require notifyDataSetChanged for the view to update).
What's going on here? Shouldn't adding items to the list immediately after calling .setAdapter() in the same block require something like notifyDataSetChanged()?
i.e.
#Override
protected void onCreate(Bundle savedInstanceState) {
...
myList.add("a");
myList.add("b");
recyclerView.setAdapter(adapter);
myList.add("c"); // the recyclerView is updating and shows "c" here! why???
findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
myList.add("e"); // the recyclerView will NOT update here unless you call `notifyDataSetChanged`
}
});
...
If you're passing an ArrayList to your Adapter, and you're adding elements in onCreate, that would be before the app has drawn your first frame.
There is a moment where you're able to add elements, because the adapter hasn't laid out the items.
And within your onClick, that would be after onCreate has finished, which means the adapter is already laid out.
You could try moving add("c"); to onPostCreate or onResume and you may see similar result as your onClick.

RecyclerView: How to determine when scrollToPosition() has finished binding the ViewHolders

tl;dr How to wait until the true scrolling is done after calling scrollToPosition()? What I mean by true scrolling is that (1) the RecyclerView has finished the scrolling and (2) it has bound all the ViewHolders for the set position of scroll.
Description:
I am trying to fix some transition animation for recycler view and I'm having a hard time achieving it.
The app is consistent of a RecyclerView in one of the activities and a ViewPager that acts as a "detail view" when users click on an item in the RecyclerView.
The forward navigation from RecyclerView to ViewPager works perfectly fine but the problem is, the backward navigation doesn't work. Please note that the user may navigate using the ViewPager in the detail view and when they re-enter the activity with RecyclerView, the position of the item may be different from the original case, meaning the RecyclerView needs to scoll first to the item and then resume the transition animation.
The reason I can't make it to work is because I resume the animation before the RecyclerView has properly scolled and bound the underlying ViewHolders but I don't know how to wait for the scrolling to truly finish and then resume the shared element transitions.
What I do in my code (on the Reenter code) is this:
if (isReentering) {
// postpone the animation here
ActivityCompat.postponeEnterTransition(getActivity());
scollIndex = ... // determine the correct item position
layoutManager.scrollToPosition(scrollIndex);
final RecyclerView recyclerView = getRecyclerView();
new Handler().post(new Runnable() {
#Override
public void run() {
recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
recyclerView.requestLayout();
ActivityCompat.startPostponedEnterTransition(getActivity());
return true;
}
});
}
});
}
I also set the transition name in my Adapter as such:
#Override
public void onBindViewHolder(TViewHolder holder, int position) {
....
holder.setTransitionName(_data.get(position).getContentId().toString());
}
The transition name is the UUID of the item, set in the underlying View. Both the ViewPager and the RecyclerView set this correctly.
The scrollIndex is correct and the RecyclerView does scroll to it but here's the catch: The scrolling and binding only takes effect after I've already started the postponed enter transition in my code (by calling ActivityCompat.startPostponedEnterTransition(getActivity());) which causes weird transition animations.
What I would like to happen is to somehow wait until the scrolling and the ViewHolder binding is finished before calling startPostponedEnterTransition so my question is, how can I find out when the scrolling has finished and all the ViewHolders are bound so that I can call this?
Many thanks in advance,
You can check, if the ViewHolders have been bound, by using the code in this answer.

Notifying fragments in a viewpager

I am using ViewPager in my app. In each fragment there is a toolbar. On a single tap on the image the the toolbar is animated to the top out of the screen. But I have to notify all the remaining fragments to do the same thing. So that when the user scrolls to the next fragment he doesn't see the toolbar.
I tried adding setUserVisibleHint(), but it did not work as it was called only when the fragment was completely visible, thus showing the toolbar exiting to the user.
Then I tried it in onResume and setting pager.offscreenpagelimit=1, it worked fine for the fragment next to next but did not work for the next fragment.
Thanks!!
First Notify your activity from fragment using:
In Fragment on Animation End:
((YourActivity)getActivity()).hideToolbar();
In Activity:
public void hideToolbar() {
// Redraw view pager without toolbar (notify your adapter create pager without toolbar)
}
Well, why don't you get your toolbar out of your fragments and just create one in activity and change its state on page change (definitely it will not slide but may
You have 2 problems to face.
How to call existing fragments to hide/show their toolbars.
How to create another fragments with hidden toolbar.
First problem can be easly done by using Otto event library found here. Just paste this code in your viewpager fragments:
Bus bus = new Bus();
#Override
protected void onResume() {
super.onResume();
bus.register(this);
}
#Override
protected void onPause() {
super.onPause();
bus.unregister(this);
}
#Subscribe
public void onToolbarEvent(ToolbarEvent event) {
//do your toolbar logic
}
Then in your onClick event on image just put (of course creating bus object before)
bus.post(new ToolbarEvent());
ToolbarEvent can be just empty class. If you read about Otto events you will understand how it works.
Another problem is how to know that the toolbar should be hidden/shown, when viewpager instantiates new fragments? Simply add a boolean flag in your shared prefferences, so every time fragment is created, it can check if it can show toolbar or not e.g. in onViewCreated() method. The example how to use shared prefferences can be found here
Hope I helped a little bit.

ViewPager OnLongClick listener not firing

I have a fragment which contains a ViewPager. When I inflate the layout, I assign an OnLongClick listener to it as follows:
mPager.setOnLongClickListener(mOnPagerLongClickListener);
However, when I perform a long click on the ViewPager, nothing happens. What can I do to make this work? Or do I need to assign the listener to every view in the ViewPager instead? Assigning the listener to the GridViews which the ViewPager contains doesn't seem to work either.
Excuse for putting on a query, but I am able to solve the issue. It won't be helpful to you but may come in handy for any other viewer.
The simple solution is to assign listener directly to ImageView's object rather assigning it to ViewPager's object, i.e, assigning viewPager.setOnLongClickListener will not fire anything.
So, we have to initialize ImageView with onLongClickListeners in the class extending PageAdapter in instantiateItem() by:
imageView.setOnLongClickListener(new OnLongClickListener()){
#Override
public boolean onLongClick(View v) {
// Do your stuff
return false;
}
});

Categories

Resources