Java – Add and remove horizontal list items onScroll

Add and remove horizontal list items onScroll… here is a solution to the problem.

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.

Horizo​​ntalListView.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>

ma​​in.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

Related Problems and Solutions