I am using RecyclerView to list Items and In each single list displaying an image which will be Visible/Gone dynamically. I am using View.GONE to hide the view.
In a condition where the image should hide is not working always. It is still showing in screen,and also in debug mode i have checked that and when getting the
image.getVisiblity() it is giving me int value "8" which means the view is Gone,But still i can see that image in that list.
It happens only sometimes.
And i tried to use View.INVISIBLE and it is working all the time but it is taking the space in layout which is as expected
I am using sparseArray to store all the holders classes.I have written a method in Adapter and calling this from activity.I am trying to hide the replayIcon view
public void handleReplayButton(int pos,Boolean isDisplay) {
Holder holder = holderSparseArray.get(pos);
if(holder != null) {
if (isDisplay != null && isDisplay == true) {
holder.playIcon.setVisibility(View.GONE);
holder.pauseIcon.setVisibility(View.GONE);
holder.replayIcon.setVisibility(View.VISIBLE);
} else if(isDisplay != null && isDisplay == false) {
holder.playIcon.setVisibility(View.VISIBLE);
holder.pauseIcon.setVisibility(View.GONE);
holder.replayIcon.setVisibility(View.GONE);
} else {
holder.playIcon.setVisibility(View.GONE);
holder.pauseIcon.setVisibility(View.VISIBLE);
holder.replayIcon.setVisibility(View.GONE);
}
}
}
Here it is going to the last else statement what i want and it is setting the view to GONE.and when i call holder.replayIcon.getVisibility() it is giving me int 8 but,still i can see the icon
Try calling invisible at the end of one statement which makes it visible and vice versa.
Or
You can also try to put notifydatasetchanged().
You will have to call notifyDataSetChanged() to refresh the list in the recycler view.
But since you have to remove an item, you can also use notifyItemRemoved
Also, if you are using setVisibility() method to HIDE the view, then make sure you also set the view as VISIBLE for valid items, because the items are reused in a recycler view.
For more : https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter
If you will call notifyDataSetChanged() - it will update all the items in the list.
Don't do that if you need to update special items by index because it will take a lot of memory to redraw the all views.
Instead like the guys wrote before you should use notifyItemChanged(), notifyItemInserted() or notifyItemRemoved().
If you want to update couple views use can use notifyItemRangeChanged(), notifyItemRangeRemoved() or notifyItemRangeInserted().
You can read more about it here
Also there is one way to it. You can use DiffUtils callbacks.
Pretty good approach that work with animation already.
DiffUtils Calbacks
I'm creating a slideshow with ViewPager2. For example, the slideshow has 3 items and I want to show the second item when the activity opens. I use setCurrentItem(int item, boolean smoothScroll) method but it doesn't work and nothing happens. How can I achieve it?
viewPager.adapter = adapter
viewPager.setCurrentItem(1, true)
I think an easier more reliable fix is to defer to next run cycle instead of unsecure delay e.g
viewPager.post {
viewPager.setCurrentItem(1, true)
}
setCurrentItem(int item, boolean smoothScroll) works correctly in ViewPager but in ViewPager2 it does not work as expected. Finally, I faced this problem by adding setCurrentItem(int item, boolean smoothScroll) method into a delay like this:
Handler().postDelayed({
view.viewPager.setCurrentItem(startPosition, false)
}, 100)
Do not use timers, you will run into a lot of probable states in which the user has a slow phone and it actually takes a lot longer than 100 ms to run, also, you wouldn't want too slow of a timer making it ridiculously un-reliable.
Below we do the following, we set a listener to our ViewTreeObserver and wait until a set number of children have been laid out in our ViewPager2's RecyclerView (it's inner working). Once we are sure x number of items have been laid out, we start our no-animation scroll to start at the position.
val recyclerView = (Your ViewPager2).getChildAt(0)
recyclerView.apply {
val itemCount = adapter?.itemCount ?: 0
if(itemCount >= #(Position you want to scroll to)) {
viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
// False for without animation scroll
(Your ViewPager2).scrollToPosition(#PositionToStartAt, false)
}
}
}
First off, I think that the accepted answer shouldn't be #hosseinAmini 's, since it's suggesting to use a delay to work around the problem. You should first be looking for what the assumed bug is caused by, rather than trusting unreasonable solutions like that.
#Rune's proposal is correct, instead; so I'm quoting their code in my answer:
viewPager.post {
viewPager.setCurrentItem(1, true)
}
The only thing I'd argue about is the aforementioned one's belief that their solution is just deferring the execution of that lambda in the next run cycle. This wouldn't make anything buggy work properly. Rather, what it is actually being done is deferring the execution of that lambda to once the view has been attached to a window, which implies it's also been added to a parent view. Indeed, there looks to be an issue as to changing the current ViewPager2 item before being attached to a window. Some evidence to support this claim follows:
Using whichever Handler won't work nearly as effectively.
Handler(Looper.getMainLooper()).post {
viewPager.setCurrentItem(1, true) // Not working properly
}
From a theoretical standpoint, it might incidentally work due to the ViewPager2 being attached to a window acquiring priority in the message queue of the main looper, but this shouldn't ever be relied upon as there's just no guarantee that it'll work (it's even more likely it won't) and if it even turned out to be working, further investigation running multiple tests should make my point clear.
View.handler gets null, which means the view hasn't been attached to any window yet.
View.handler // = null
Despite Android UI being tied to the main looper, which will always uniquely correspond to the main thread –hence also called the UI thread,– a weird design choice stands in the handler not being associated to each view until they get attached to a window. A reason why this may lay on the consequent inability of views to schedule any work on the main thread while they're not part of the hierarchy, which may turn useful when implementing a view controller that schedules view updates while unaware of their lifecycle (in which case it would employ the View's handler, if any — or just skip scheduling whatever it was going to if none).
EDIT:
Also, #josias has pointed out in a comment that it'd be clearer to use:
viewPager.doOnAttach {
viewPager.setCurrentItem(1, true)
}
Thanks for that suggestion! It expresses better the actual intent, rather than relying on the behavior of the View.post method.
Do not use timers and all that stuff with 'post', it's not the reliable solution and just a piece of code that smells.
Instead, try use viewPager.setCurrentItem(1, false). That 'false' is about smoothScroll, you can't smooth scroll your viewPager2 when your activity is just opened. Tested it on a fragment in onViewCreated() method, it also didn't work with "true", but works with "false"
As it was mentioned above you have to use setCurrentItem(position, smoothScroll) method on ViewPager2 in order to show selected item. To make it work you have to define a callback, here is an example:
ViewPager2.OnPageChangeCallback callback = new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
}
};
And then you have to register it as follow:
viewPager.registerOnPageChangeCallback(callback);
Also do not forget to unregister it:
viewPager.unregisterOnPageChangeCallback(callback);
When you call setCurrentItem(position) method it will call onPageSelected(int position) method from your callback passing your argument, and then method createFragment(int position) from FragmentStateAdapter class will be called to show your fragment.
I tried changing viewpager2 page in Handler().dely() and viewPager2.post{} and even 'viewPager2.get(0).post all didn't work for me, I'm using ViewPager with FragmentStateAdapter with Tablayout.
What worked for me is changing the position of the RecylerView in ViewPager2 after binding FragmentStateAdapter to yourViewPager2View.adapter manually:
(yourViewPager2View[0] as RecyclerView).scrollToPosition(moveToTabNumber)
Why
My problem is onCreateFragment(position:Int):Fragmeet function in FragmentStateAdapter always starting fragment at 0 position no matter what pageNumber I set the page
viewPager.setCurrentItem = pageNumber
I checked where it's called in FragmentStateAdapter it's called in FragmentStateAdapter:
onBindViewHolder(final #NonNull FragmentViewHolder holder, int position)`
so all I needed is to force onBindViewHolder to call onCreateFragment(position:Int) with the page number I wanted.
mViewPager.setCurrentItem(1, true); ---> this is sufficient as you written above
That should work,
in doubt, just check your position:
#Override
public void onPageSelected(int i) {
if (LOG_DEBUG) Log.v(TAG, " ++++++++ onPageSelected: " + i);
mViewPager.setCurrentItem(i);
//TODO You can use this position: to write other dependent logic
}
and also check
getItem(int position) in PagerAdapter
or else paste your code.
I noticed that it works fine when the view is initially created if you opt to not animate it.
viewPager2.setCurrentItem(index, false)
This is usually fine depending on your use case - this initial/default item probably doesn't need to be animated in.
I met the same problem. In my case, I make the viewPager2 Gone by default until network requests succeed, I fix it by setting the CurrentItem after I make the viewPager2 visible.
My answer may not be helpful now but i see no harm to post my expreince, i just came to this problem using ViewPager and ViewPager2 and unexpectedly solved it by just changing some line codes order.
Here is (java) solution for ViewPager:
reviewWordViewPager.addOnPageChangeListener(changeListener);
reviewWordViewPager.setCurrentItem(viewPosition, true/false);
reviewWordTabIndicator.setupWithViewPager(reviewWordViewPager, true);
(Java) solution for ViewPager2:
wordViewPager.registerOnPageChangeCallback(viewPager2OnPageChangeCallback);
wordViewPager.setCurrentItem(vpPosition, true/false);
new TabLayoutMediator(tabIndicator, wordViewPager,
((tab, position) -> tab.setText(viewPagerTitle[position]))).attach();
I did not look up for ViewPager2 whether it needs the following old code used in ViewPager
#Override
public int getItemPosition(#NonNull Object object) {
// refresh all fragments when data set changed
return POSITION_NONE;
}
But surprisingly no need for it in ViewPager2 to solve the problem i've been having, hope it helps others
In case you use context.startActivity to start new activities no need to use wordViewPager.setCurrentItem(item, smoothScroll) in your onResume function to get back to the last selected tab before you started new activity you just save ViewPager/ViewPager2 position like vpPisition = wordViewPager.getCurrentItem(); in onStop function.
vpPisition is a global variable.
as #Daniel Kim but a java version
RecyclerView rvOfViewPager2 = (RecyclerView) viewPager2.getChildAt(0);
rvOfViewPager2.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
rvOfViewPager2.getViewTreeObserver().removeOnGlobalLayoutListener(this);
viewPager2.setCurrentItem(currentTabId, false);
}
});
First You need to Initilaze the Main activity under any listener or button You want then After that You need to put this Line..
here MainActvity is the Viewpager Main Class You are using and and 2 is the position where you want to move
MainActivity main = (MainActivity ) mContext;
main.selectTab(2, true);
I have a list that displays a fairly complex layout for each list item and there is noticable lag when I scroll. Yes, I have already put it in AsyncTask and used a View Holder, as suggested here http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/ and http://developer.android.com/training/improving-layouts/smooth-scrolling.html
I think the next thing to try is to load all items in the list when the page is loading (even those that will be off screen), but I cannot seem to find a way to do this. And, yes, I understand that this is a very bad idea and goes against the point of a ListView in a way, if you are expecting many list items. But I should never have more than 20 items in my case and I cannot think of what else to try to make it faster when the user scrolls. So, I would like to be able to control what gets loaded into my ListView adapter, not just take the default of "whatever is visible on the screen at the time".
I think I understand from http://android.amberfog.com/?p=296 and How ListView's recycling mechanism works how the ListView recycling works. But I cannot see a way to load the items that are not currently appearing on the screen, so that it does not have to create a new view when the user scrolls. So my question is: is it possible to load off screen items in the list view so they are not created when the user scrolls? If so, how?
Thanks in advance.
Use this listener object to detect if visibleThreshold number of items are still to be rendered in the list. And based on that condition fire your Asynk task to load the data in advance for the rest of elements in list. This kind of technique is used in adding Footer view also.
listenerObject = new AbsListView.OnScrollListener() {
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int visibleThreshold = 3;
if (loading == false && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
loading = true;
//DO YOUR JOB HERE i.e. your async task
// once your async Task is complete make loading as false
}
}
};
lv.setOnScrollListener(listenerObject);
Here lv is the listview.
And in case your data is already loaded then you don't have to worry about view creation because it is already taken care of by Android's mechanism to optimise performance.
I'm having a bit of trouble preserving the scroll position of a list view when changing it's adapter's data.
What I'm currently doing is to create a custom ArrayAdapter (with an overridden getView method) in the onCreate of a ListFragment, and then assign it to its list:
mListAdapter = new CustomListAdapter(getActivity());
mListAdapter.setNotifyOnChange(false);
setListAdapter(mListAdapter);
Then, when I receive new data from a loader that fetches everything periodically, I do this in its onLoadFinished callback:
mListAdapter.clear();
mListAdapter.addAll(data.items);
mListAdapter.notifyDataSetChanged();
The problem is, calling clear() resets the listview's scroll position. Removing that call preserves the position, but it obviously leaves the old items in the list.
What is the proper way to do this?
As you pointed out yourself, the call to 'clear()' causes the position to be reset to the top.
Fiddling with scroll-position, etc. is a bit of a hack to get this working.
If your CustomListAdapter subclasses from ArrayAdapter, this could be the issue:
The call to clear(), calls 'notifyDataSetChanged()'. You can prevent this:
mListAdapter.setNotifyOnChange(false); // Prevents 'clear()' from clearing/resetting the listview
mListAdapter.clear();
mListAdapter.addAll(data.items);
// note that a call to notifyDataSetChanged() implicitly sets the setNotifyOnChange back to 'true'!
// That's why the call 'setNotifyOnChange(false) should be called first every time (see call before 'clear()').
mListAdapter.notifyDataSetChanged();
I haven't tried this myself, but try it :)
Check out: Maintain/Save/Restore scroll position when returning to a ListView
Use this to save the position in the ListView before you call .clear(), .addAll(), and . notifyDataSetChanged().
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
After updating the ListView adapter, the Listview's items will be changed and then set the new position:
mList.setSelectionFromTop(index, top);
Basically you can save you position and scroll back to it, save the ListView state or the entire application state.
Other helpful links:
Save Position:
How to save and restore ListView position in Android
Save State:
Android ListView y position
Regards,
Please let me know if this helps!
There is one more use-case I came across recently (Android 8.1) - caused by bug in Android code. If I use mouse-wheel to scroll list view - consecutive adapter.notifyDataSetChanged() resets scroll position to zero. Use this workaround until bug gets fixed in Android
listView.onTouchModeChanged(true); // workaround
adapter.notifyDataSetChanged();
More details is here: https://issuetracker.google.com/u/1/issues/130103876
In your Expandable/List Adapter, put this method
public void refresh(List<MyDataClass> dataList) {
mDataList.clear();
mDataList.addAll(events);
notifyDataSetChanged();
}
And from your activity, where you want to update the list, put this code
if (mDataListView.getAdapter() == null) {
MyDataAdapter myDataAdapter = new MyDataAdapter(mContext, dataList);
mDataListView.setAdapter(myDataAdapter);
} else {
((MyDataAdapter)mDataListView.getAdapter()).refresh(dataList);
}
In case of Expandable List View, you will use
mDataListView.getExpandableListAdapter() instead of
mDataListView.getAdapter()
I have a custom listview(with two image and 5 textviews) in which I have to show more than 200 data when I load it at first time with all data then it returns out of memory exception, to resolve the same problem I want that when we scrolled down the listview till the last item of the list then it app again adds more data to the same list. It running as same till we have got all the data on the list view. Please don't tell me to use EndlessAdapter because Endlessadapter always downloading the items after each 10 seconds. Which returns also the outof memory after some time.
Thanks in advance.
assign a List http://developer.android.com/reference/java/util/List.html to your addapter then you can call item.add and item.remove
please try this
endless listView
Thanks
I'd recommend that every time a user scrolls you check the position of the first element of the ListView.
ListView.getFirstVisiblePosition()
If it's close to the amount of the elements in your list you can add items to the bottom of the list and remove the ones from the top of the list.
I did something like this to load in more elements when the user scrolled down on an ExpandableListView once.
myListView.setOnScrollListener( new OnScrollListener()
{
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
int lastVisibleElement = firstVisibleItem + visibleItemCount;
if(lastVisibleElement == totalItemCount)
{
//Load elements
myListAdapter.notifyDataSetChanged();
}
}
about out of memory , there is no way that 5 imageViews and 5 textViews will cause out of memory . you are probably saving all of the 200 images together instead of using some sort of caching (like softreference and LruCache) . the whole point of the adapter is to show tons of items (not at the same time , of course) using minimal memory . you can see that even the play store (which i still like to call android market) app uses memory cache if you play enough with scrolling ...
for more information about listview, watch this
you might also want to read more about handling images here .
anyway , you can take the getView as a trigger (using the position parameter compared to the getCount() value) to when to load more items and then update the adapter using notifyDatasetChanged (and update the getCount() value) .