Add and remove horizontal list items onScroll
I need to show infinite scrolling of horizontal ListView items (meaning duplicate items are required) for example
a,b,c,d,e,f,a,b,c,d,e,f,a,b,….
Bold is the items visible on the screen
The procedure is to repeat the list item after reaching the last list item, I tried to add the list item while scrolling the list, so I customized the horizontal ListView class. But when scrolling to the left after adding a list item on the right, I get ArrayIndexOutOfBoundsException
. I tried to add the Integer.MAX VALUE option to the base adapter to get an unlimited number of items, but it didn’t help when positioning items (set selection ()).
If anyone knows how to add and remove horizontal list items, please share.
Solution
Instead of extending the BaseAdapter, extend the ArrayAdapter.
Solution 1:
Note: Set the boolean variable isHorizontalListView = true for the horizontal ListView in AgarwalActivity.java.
HorizontalListView.java
public class HorizontalListView extends AdapterView<ListAdapter> {
public boolean mAlwaysOverrideTouch = true;
protected ListAdapter mAdapter;
private int mLeftViewIndex = -1;
private int mRightViewIndex = 0;
protected int mCurrentX;
protected int mNextX;
private int mMaxX = Integer.MAX_VALUE;
private int mDisplayOffset = 0;
protected Scroller mScroller;
private GestureDetector mGesture;
private Queue<View> mRemovedViewQueue = new LinkedList<View>();
private OnItemSelectedListener mOnItemSelected;
private OnItemClickListener mOnItemClicked;
private OnItemLongClickListener mOnItemLongClicked;
private boolean mDataChanged = false;
public HorizontalListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private synchronized void initView() {
mLeftViewIndex = -1;
mRightViewIndex = 0;
mDisplayOffset = 0;
mCurrentX = 0;
mNextX = 0;
mMaxX = Integer.MAX_VALUE;
mScroller = new Scroller(getContext());
mGesture = new GestureDetector(getContext(), mOnGesture);
}
@Override
public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
mOnItemSelected = listener;
}
@Override
public void setOnItemClickListener(AdapterView.OnItemClickListener listener){
mOnItemClicked = listener;
}
@Override
public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener) {
mOnItemLongClicked = listener;
}
private DataSetObserver mDataObserver = new DataSetObserver() {
@Override
public void onChanged() {
synchronized(HorizontalListView.this){
mDataChanged = true;
}
invalidate();
requestLayout();
}
@Override
public void onInvalidated() {
reset();
invalidate();
requestLayout();
}
};
@Override
public ListAdapter getAdapter() {
return mAdapter;
}
@Override
public View getSelectedView() {
TODO: implement
return null;
}
@Override
public void setAdapter(ListAdapter adapter) {
if(mAdapter != null) {
mAdapter.unregisterDataSetObserver(mDataObserver);
}
mAdapter = adapter;
mAdapter.registerDataSetObserver(mDataObserver);
reset();
}
private synchronized void reset(){
initView();
removeAllViewsInLayout();
requestLayout();
}
@Override
public void setSelection(int position) {
TODO: implement
}
private void addAndMeasureChild(final View child, int viewPos) {
LayoutParams params = child.getLayoutParams();
if(params == null) {
params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
}
addViewInLayout(child, viewPos, params, true);
child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
}
@Override
protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if(mAdapter == null){
return;
}
if(mDataChanged){
int oldCurrentX = mCurrentX;
initView();
removeAllViewsInLayout();
mNextX = oldCurrentX;
mDataChanged = false;
}
if(mScroller.computeScrollOffset()){
int scrollx = mScroller.getCurrX();
mNextX = scrollx;
}
if(mNextX <= 0){
mNextX = 0;
mScroller.forceFinished(true);
}
if(mNextX >= mMaxX) {
mNextX = mMaxX;
mScroller.forceFinished(true);
}
int dx = mCurrentX - mNextX;
removeNonVisibleItems(dx);
fillList(dx);
positionItems(dx);
mCurrentX = mNextX;
if(!mScroller.isFinished()){
post(new Runnable() {
public void run() {
TODO Auto-generated method stub
requestLayout();
}
});
}
}
private void fillList(final int dx) {
int edge = 0;
View child = getChildAt(getChildCount()-1);
if(child != null) {
edge = child.getRight();
}
fillListRight(edge, dx);
edge = 0;
child = getChildAt(0);
if(child != null) {
edge = child.getLeft();
}
fillListLeft(edge, dx);
}
private void fillListRight(int rightEdge, final int dx) {
while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) {
View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);
addAndMeasureChild(child, -1);
rightEdge += child.getMeasuredWidth();
if(mRightViewIndex == mAdapter.getCount()-1) {
mMaxX = mCurrentX + rightEdge - getWidth();
}
if (mMaxX < 0) {
mMaxX = 0;
}
mRightViewIndex++;
}
}
private void fillListLeft(int leftEdge, final int dx) {
while(leftEdge + dx > 0 && mLeftViewIndex >= 0) {
View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this);
addAndMeasureChild(child, 0);
leftEdge -= child.getMeasuredWidth();
mLeftViewIndex--;
mDisplayOffset -= child.getMeasuredWidth();
}
}
private void removeNonVisibleItems(final int dx) {
View child = getChildAt(0);
while(child != null && child.getRight() + dx <= 0) {
mDisplayOffset += child.getMeasuredWidth();
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mLeftViewIndex++;
child = getChildAt(0);
}
child = getChildAt(getChildCount()-1);
while(child != null && child.getLeft() + dx >= getWidth()) {
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mRightViewIndex--;
child = getChildAt(getChildCount()-1);
}
}
private void positionItems(final int dx) {
if(getChildCount() > 0){
mDisplayOffset += dx;
int left = mDisplayOffset;
for(int i=0; i<getChildCount(); i++){
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
child.layout(left, 0, left + childWidth, child.getMeasuredHeight());
left += childWidth;
}
}
}
public synchronized void scrollTo(int x) {
mScroller.startScroll(mNextX, 0, x - mNextX, 0);
requestLayout();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = super.dispatchTouchEvent(ev);
handled |= mGesture.onTouchEvent(ev);
return handled;
}
protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
synchronized(HorizontalListView.this){
mScroller.fling(mNextX, 0, (int)-velocityX, 0, 0, mMaxX, 0, 0);
}
requestLayout();
return true;
}
protected boolean onDown(MotionEvent e) {
mScroller.forceFinished(true);
return true;
}
private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return HorizontalListView.this.onDown(e);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return HorizontalListView.this.onFling(e1, e2, velocityX, velocityY);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
synchronized(HorizontalListView.this){
mNextX += (int)distanceX;
}
requestLayout();
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
for(int i=0; i<getChildCount(); i++){
View child = getChildAt(i);
if (isEventWithinView(e, child)) {
if(mOnItemClicked != null){
mOnItemClicked.onItemClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i ));
}
if(mOnItemSelected != null){
mOnItemSelected.onItemSelected(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i ));
}
break;
}
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (isEventWithinView(e, child)) {
if (mOnItemLongClicked != null) {
mOnItemLongClicked.onItemLongClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i));
}
break;
}
}
}
private boolean isEventWithinView(MotionEvent e, View child) {
Rect viewRect = new Rect();
int[] childPosition = new int[2];
child.getLocationOnScreen(childPosition);
int left = childPosition[0];
int right = left + child.getWidth();
int top = childPosition[1];
int bottom = top + child.getHeight();
viewRect.set(left, top, right, bottom);
return viewRect.contains((int) e.getRawX(), (int) e.getRawY());
}
};
}
AgarwalActivity.java
public class AgarwalActivity extends Activity{
private LayoutInflater _inflater;
private ListView listView;
private HorizontalListView hListView;
private RankingsAdapter rankingsAdapter;
private boolean isHorizontalListView = false;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(isHorizontalListView){
setContentView(R.layout.listviewdemo);
hListView = (HorizontalListView) findViewById(R.id.listview);
}else{
setContentView(R.layout.listview);
listView=(ListView)findViewById(R.id.listView);
}
ArrayList<String> rankingArrayList = new ArrayList<String>();
for(int i=0; i<25; i++)
rankingArrayList.add("agarwal"+i);
_inflater= LayoutInflater.from(AgarwalActivity.this);
rankingsAdapter = new RankingsAdapter(AgarwalActivity.this,R.id.text,rankingArrayList);
if(isHorizontalListView)
hListView.setAdapter(rankingsAdapter);
else
listView.setAdapter(rankingsAdapter);
}
private class RankingsAdapter extends ArrayAdapter<String>{
private ArrayList<String> _row_list;
public static final int HALF_MAX_VALUE = Integer.MAX_VALUE/2;
public final int MIDDLE;
public RankingsAdapter(Context context,int textViewResourceId,ArrayList<String> arrL) {
TODO Auto-generated constructor stub
super(context,textViewResourceId, arrL);
_row_list = arrL;
MIDDLE = HALF_MAX_VALUE - HALF_MAX_VALUE % _row_list.size();
}
public int getCount() {
TODO Auto-generated method stub
return Integer.MAX_VALUE;
}
public String getItem(int position) {
TODO Auto-generated method stub
return _row_list.get(position % _row_list.size());
}
public long getItemId(int position) {
TODO Auto-generated method stub
return position % _row_list.size();
}
public View getView(final int position, View convertView, ViewGroup parent) {
TODO Auto-generated method stub
ViewHolder holder = null;
if(convertView == null){
holder = new ViewHolder();
convertView = (LinearLayout)_inflater.inflate(R.layout.text_layout, null);
getting the IDs
holder.driver = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder=(ViewHolder)convertView.getTag();
}
Settng bean values to row components
holder.driver.setText(_row_list.get(position % _row_list.size()));
return convertView;
}//getView()
class ViewHolder{
holding the components
TextView driver;
}//ViewHolder--Class inside CountriesAdapter
}//RankingsAdapter-inner class
}//AgarwalActivity-class
listview.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</RelativeLayout>
listviewdemo.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#fff"
>
<com.vl.agarwal.HorizontalListView
android:id="@+id/listview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#ddd"
/>
</LinearLayout>
text_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingLeft="6dip"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
——————————————– ————————————–
Solution 2: Use the gallery as a horizontal ListView
Row .xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/itemtext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="30sp"/>
</LinearLayout>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<Gallery
android:id="@+id/horizontallistview"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
AgarwalActivity.java
public class AgarwalActivity extends Activity {
Gallery myHorizontalListView;
private RankingsAdapter rankingsAdapter;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ArrayList<String> rankingArrayList = new ArrayList<String>();
for(int i=0; i<25; i++)
rankingArrayList.add("agarwal"+i);
DisplayMetrics metrics = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(metrics);
myHorizontalListView = (Gallery)findViewById(R.id.horizontallistview);
MarginLayoutParams mlp = (MarginLayoutParams) myHorizontalListView.getLayoutParams();
mlp.setMargins(-(metrics.widthPixels/2),
mlp.topMargin,
mlp.rightMargin,
mlp.bottomMargin
);
rankingsAdapter = new RankingsAdapter(AgarwalActivity.this,R.id.itemtext,rankingArrayList);
myHorizontalListView.setAdapter(rankingsAdapter);
myHorizontalListView.setSelection(10,true);
myHorizontalListView.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Toast.makeText(
AgarwalActivity.this,
parent.getItemAtPosition(position).toString() + " Clicked",
Toast.LENGTH_LONG)
.show();
}});
}
private class RankingsAdapter extends ArrayAdapter<String>{
private ArrayList<String> _row_list;
public static final int HALF_MAX_VALUE = Integer.MAX_VALUE/2;
public final int MIDDLE;
public RankingsAdapter(Context context,int textViewResourceId,ArrayList<String> arrL) {
TODO Auto-generated constructor stub
super(context,textViewResourceId, arrL);
_row_list = arrL;
MIDDLE = HALF_MAX_VALUE - HALF_MAX_VALUE % _row_list.size();
}
@Override
public int getCount() {
TODO Auto-generated method stub
return Integer.MAX_VALUE;
}
@Override
public String getItem(int position) {
TODO Auto-generated method stub
return _row_list.get(position % _row_list.size());
}
@Override
public long getItemId(int position) {
TODO Auto-generated method stub
return position % _row_list.size();
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
TODO Auto-generated method stub
View rowView = LayoutInflater
.from(parent.getContext())
.inflate(R.layout.row, null);
TextView listTextView = (TextView)rowView.findViewById(R.id.itemtext);
listTextView.setText(_row_list.get(position % _row_list.size()));
return rowView;
}//getView()
}//RankingsAdapter-inner class
}//AgarwalActivity-class