In one of my past projects I implemented a "Time Picker Carousel". It is based on a HorizontalScrollView
. The user can pick a time while scrolling this view. The time value is calculated from the X-Offset of the HorizontalScrollView
.
I wanted to share this project at github, but while cleaning up the code I realized some bad performance issue.
The HorizontalScrollView
is populated with a custom ArrayAdapter
. The getView()
uses a Holder
for the convertView
. I thought it might work as an adapter within a ListView
, so only the visible items are rendered and reused if they will be destroyed. Instead, all items are rendered!!! (in my case 1008!) I add them myself (#1 in Code example), but even if I try to add less, the logic from remove (and recycle) old ones doesn't work, or did I miss something?
So my basic question is: What do I have to change to make my adapter behave like ListView
?
remove
function, but this is never called (somehow logic, because i am only adding)ArrayAdapter<String>
Any thoughts, links are welcome!
And please don't suggest using ListView
instead. We decided to use HorizontalScrollView
because of callbacks and the fact we already have a ListView
in the layout.
HorizontalScrollView
InfiniteTimeScrubberHorizontalView extends HorizontalScrollView{
...
public void setAdapter(Context context, TimeScrubberListAdapter mAdapter) {
this.mAdapter = mAdapter;
try {
fillViewWithAdapter(mAdapter);
} catch (ZeroChildException e) {
e.printStackTrace();
}
}
private void fillViewWithAdapter(TimeScrubberListAdapter mAdapter) {
//...
ViewGroup parent = (ViewGroup) getChildAt(0);
parent.removeAllViews();
for (int i = 0; i < mAdapter.getCount(); i++) {
//#1: Here: ALL views are added
parent.addView(mAdapter.getView(i, null, parent));
}
}
Adpater
public class TimeScrubberListAdapter extends ArrayAdapter<String> {
//...
private ArrayList<String> list; //list from 0:00,0:30...23:00,23:30
final static int MAXIMUM_DAYS = 21
@Override
public int getCount() {
return list.size() * MAXIMUM_DAYS;
}
@Override
public String getItem(int position) {
return list.get(position % list.size());
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
RelativeLayout layout;
if (convertView == null) {
layout = (RelativeLayout) View.inflate(context, layoutId, null);
holder = new Holder();
holder.title = (TextView) layout.findViewById(R.id.epg_item_text);
layout.setTag(holder);
} else {
layout = (RelativeLayout) convertView;
view = layout;
holder = (Holder) layout.getTag();
}
layout.setLayoutParams(mLP);
String timeValue = getItem(position);
holder.title.setText(timeValue);
return layout;
}
//...
@Override
public void remove(String object) {
//not called...some how logic, because i do not remove an item
super.remove(object);
}
I think maybe you are confusing where the logic lies for rendering between View and Adapter. Using an Adapter does not cause view recycling behavior. It is the ListView
itself, along with its parent, AbsListView
, that implement the view recycling behavior. The Adapter is required by the ListView
in order to properly populate the Views that are shown on screen as needed, but the logic for actually choosing which Views to display and when and how to recycle those views is not in the Adapter at all.
If you look at the source code for HorizontalScrollView
and for ListView
, you will see that they are dramatically different. They are not just the same thing in different orientations.
So, the long and short of it is that you will not be able to get the view recycling that you are looking for by using a HorizontalScrollView
or even a simple descendent. If you want view recycling, you should check out the RecyclerView
.