I'd like to have my application do some on-screen changes (outside the ListView) based on what the first visible item in my list is.
What I've done is override the onScroll handler. When this handler is invoked, I set the position of my Cursor to the first visible position so I can look at the data for that row:
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
mCursor.moveToPosition(getListView().getFirstVisiblePosition());
...
I then reference the data in the Cursor to make my display changes.
Here's the thing: This working is spotty. Most of the time it works perfectly. I can't make it screw up.
However, sometimes getListView().getFirstVisiblePosition() does not return 1 until the 3rd item (index 2) is really the first visible item. It displays 0 when the 0 index is showing. It displays 0 when the 1 index is showing. It displays 1 when the 2 index is showing and so on. Additionally, it only does this when the user is scrolling down the list. If the user is scrolling backwards (up), getFirstVisiblePosition() returns the correct value.
I'm wondering if this isn't related to some efficiency problem. This handler is going to execute over and over and over as the user scrolls.
Does anyone have any ideas on what could be the problem? Does anyone have any better solutions?
Update:
Do I remember hearing that getFirstVisiblePosition() is really only a guess; and that Android essentially supposes what index might be available by rows' probable height? If this is so, then I have a problem. Some rows in my list are bigger than others. If this is the case, is there any way around it?
I would actually perform your call in onScrollStateChanged and only execute when scrolling is idle. You will drastically cut down on number of executed requests. Something like this:
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
// do yer stuff
}
}
Related
I'm trying to implement an autoplay feature for video items in a RecyclerView (linear vertical layout). I can't figure out how to know when a certain item is currently on/off the screen so I can autoplay/pause the video. If I put the code in onBindViewHolder method all videos start playing simultaniously. Couldn't find a solution by googling it either. Help, please!
For Recyclerview you should rely on your layout manager to give you this information.
https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager
or
https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager
Assign a layout to the RecyclerView when you assign the adapter. Then use it to see what is visible.
Let me help a bit more with some Psedo Kotlin code for you to help with the player aspect.
Let's pretend you have some object that is bound into each row that can trigger the playing and has a unique ID. Let's call that an ActiveRowPlayer for this example.
NOTE*
If you are using databinding, it is simple to bind your video player playing content to a property in your model that is populating the row, but that's different story for a different post.
You can make an interface like:
interface IActivePlayerUpdater{
fun onUpdateCurrentPlayer
}
You can make a helper method like:
in your activity, you can implement an interface like :IActivePlayerUpdater and override the methods for it.
override fun onUpdateCurrentPlayer(){
var activeRowPlayer = recyclerView.layoutManager.findFirstCompletelyVisibleItemPosition()
if(activeRowplayer.someID != currentRowPlayer.someID){
currentRowPlayer.stopPlaying
currentRowPlayer = activeRowPlayer
activeRowPlayer.startPlaying
}
}
Then pass it into your adapter and just monitor your onBind method and anytime a new onBind is called that means the content has moved enough to trigger a new row item.
MyAdapterConstructor(IActivePlayerUpdater myCallback)
fun recyclerView.onBindMethod(stuffThatComesHere){
//do normal stuff
myCallback.onUpdateCurrentPlayer()
}
Keep in mind, this is just pseudo to help you on your journey. not intended to be direct copy and paste.
----NOTE* REQUESTED FROM COMMENT TO SUPPLY HOW TO TOUCH VIEWMODEL FROM OUTSIDE OF ADAPTER---
#Goran, this example I had setup a long time ago to avoid
notifyDataSetChanged on selection changed to toggle a checkbox for
each item. I eventually moved to a better option, but you asked how do
you get the viewmodel, here is a sample. rvCameraRoll was my
recyclerView, I was using it to display camera media, but that is not
relevant, just focus on getting the viewModel piece.
The only part you should care about is getting the ViewHolder, I just left the rest there in case it helps you with anything else.
int count = rvCameraRoll.getChildCount();
for (int i = 0; i < count; i++) {
MediaModelGridAdapter.ViewHolder childRow = (MediaModelGridAdapter.ViewHolder)rvCameraRoll.getChildViewHolder(rvCameraRoll.getChildAt(i));
if(isVisible) {
childRow.imgCellSelected.setVisibility(View.VISIBLE);
if(getMediaModelList().get(i).getIsSelected()){
childRow.imgCellSelected.setBackgroundResource(R.drawable.ic_ap_selected_on);
}
}else{
//check that exists, because after fresh delete list may be short while updating cells
if(getMediaModelList().size() > i) {
getMediaModelList().get(i).setIsSelected(false);
}
childRow.imgCellSelected.setBackgroundResource(R.drawable.ic_ap_selected_off);
childRow.imgCellSelected.setVisibility(View.GONE);
}
}
OLD
leaving this for anyone using a ListView
There is a OnScrollListener for ListView
You can override the
onScrollStateChanged(AbsListView view, int scrollState)
and
the
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
this will give you the ListView item that is visible. So by using the onScroll you can detect which items are visible and determine which one to play and stop playing.
if you use with recyclerview
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener
The RecyclerView is only holding the items and the LayoutManager is responsible for displaying the items, so in order to get the ones that are visible to the user, assuming that you use LinearLayoutManager, you should call :
((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
or
((LinearLayoutManager)recyclerView.getLayoutManager()).findLastVisibleItemPosition();
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 would like to achieve a "snap" effect on an Android ListView. Specifically, when the ListView stops scrolling I would like it to stop at certain positions so that the first visible item is completely shown. To do that, I need to be able to estimate the final position that the ListView will stop at when the user stops dragging or flinging.
In the case of dragging, when we receive the ACTION_UP or ACTION_CANCEL event, I can simply read off the current Y offset as the final position.
In the case of flinging, however, when we receive the ACTION_UP or ACTION_CANCEL event, I will need some extra information to determine the final position that the ListView will stop scrolling at because of the deceleration period.
iOS provides such information through the targetContentOffset parameter in willEndDragging. Is there any equivalent information available in Android?
This is related to another question I asked but got no response: In Android, how to achieve snap effect on a listview while respecting acceleration caused by fling?
I would override the OnScroll event of your TestListView like:
#Override
public void onScroll(AbsListView lw, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
// Determine when the last row item is fully visible.
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem >= totalItemCount) {
Log.d("onScroll", "Reached last item");
}
}
}
Notice this listener gives you the first visible position/item, and the number of row items in the Listview.
I would use this listener because sometimes getFirstVisiblePosition() is not accurate, especially on a virtual ListView like what we're using.
Another advantage of onScroll override method over onTouchEvent is that onScroll covers less triggers than onTouchEvent. And it is fast in my experience with it.
Keep us posted and I am interested on this issue. Regards, Tommy Kwee
I have a requirement
I have a large data have to put in the list view and it's too stupid to load all data and populate in list view so i load 10 first item from server and populate in listView.
So everytime user scroll down at the bottom of listview ( They viewed all first 10 item ) my app will load the next 10 item automatically.
Problem is : Is there anyway that i can detect that whether user is at the bottom of the first 10 item or not ?
Sorry about my English . appreciate for any help !
You can detect the end of scrolling by the following code
if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
//It is scrolled all the way down here
}
Hope it helps.
You can register a listener to the OnScroll event and then detect where you have to start reloading data:
ListView listView = (ListView)findViewById(R.id.list_view_id);
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int amountVisible, int totalItems) {
//now get the point where you have to reload data
if (firstVisibleItem+1 + amountVisible > totalItems) {
//reload your data here
}
}
});
Where's the data coming from ? It's common to load large data sets from a DB via a cursor, and then use a simple cursor adapter to populate the list view. (And I'm talking 10,000s of rows). So long as you do it in the background (cursor loader) it shouldn't be a problem, and it probably a lot easier than trying to manage the scrolling yourself.
Try this, it implements a listview that pulls more items when the user reaches the bottom, also using a progressbar in the bottom when is loading more items.
I have used it before and it works well for what you need.
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) .