在读本文之前,请先阅读博文
之前的文章已经介绍了横向lListView的基础实现逻辑,在这里我将介绍快速滚动实现及item相关事件实现
列表的快速滚动的实现主要依赖于android官方提供的android.widget.Scroller类,具体实现需要以下步骤:
1.捕获快速滑动事件,并启动快速滑动计算(Scroller的功能)
2.使用Scroller计算一次发生滚动的位移值,刷新视图
3.如果整体滑动还未停止(即Scroller的滚动计算还未结束),则重复执行步骤2
4.捕获按下事件,实现当用户按下时停止自动快速滚动操作
如对Scroller的工作原理不了解的,可以参考以下文章:
对于ListView,item需要响应的事件比较重要的就两个,点击和长按,具体实现如下:
1.点击事件:在OnGestureListener中添加public boolean onSingleTapConfirmed(MotionEvent e)方法的实现,以响应点击事件
2.长按事件:在OnGestureListener中添加public void onLongPress(MotionEvent e) 方法的实现,以响应长按事件
先上完整代码:
package com.hss.os.horizontallistview.history_version;import android.content.Context;import android.database.DataSetObserver;import android.graphics.Rect;import android.os.Build;import android.support.annotation.RequiresApi;import android.util.AttributeSet;import android.util.Log;import android.view.GestureDetector;import android.view.MotionEvent;import android.view.View;import android.widget.AdapterView;import android.widget.ListAdapter;import android.widget.Scroller;import java.util.LinkedList;import java.util.Queue;/** * 为横向ListView添加快速滚动功能及item相关事件实现 * * Created by sxyx on 2017/8/8. */public class HorizontalListView2 extends AdapterView{ private ListAdapter adapter = null; private GestureDetector mGesture; private Queue cacheView = new LinkedList<>();//列表项缓存视图 private int firstItemIndex = 0;//显示的第一个子项的下标 private int lastItemIndex = -1;//显示的最后的一个子项的下标 private int scrollValue=0;//列表已经发生有效滚动的位移值 private int hasToScrollValue=0;//接下来列表发生滚动所要达到的位移值 private int maxScrollValue=Integer.MAX_VALUE;//列表发生滚动所能达到的最大位移值(这个由最后显示的列表项决定) private int displayOffset=0;//列表显示的偏移值(用于矫正列表显示的所有子项的显示位置) private Scroller mScroller; private int firstItemLeftEdge=0;//第一个子项的左边界 private int lastItemRightEdge=0;//最后一个子项的右边界 public HorizontalListView2(Context context) { super(context); init(context); } public HorizontalListView2(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public HorizontalListView2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public HorizontalListView2(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context); } private void init(Context context){ mGesture = new GestureDetector(getContext(), mOnGesture); mScroller=new Scroller(context); } private void initParams(){ mScroller.forceFinished(true);//避免在滑动过程中变换视图内容时,出现列表无法滚动的情况 removeAllViewsInLayout(); if(adapter!=null&&lastItemIndex >>>>left:"+left+" top:"+top+" right:"+right+" bottom:"+bottom); //需要先布局列表项再根据余下的空间布局列表头尾 //布局列表项 /* 1.计算这一次整体滚动偏移量 2.根据偏移量提取需要缓存视图 3.根据偏移量显示新的列表项 4.根据整体偏移值整顿所有列表项位置 5.计算最大滚动位移值,记录已经发生有效滚动的位移值 6.根据显示的最终效果,判断是否要居中显示 */ int dx=calculateScrollValue(); removeNonVisibleItems(dx); showListItem(dx); adjustItems(); calculateMaxScrollValue(); //继续滚动 if(!mScroller.isFinished()){ post(new Runnable(){ @Override public void run() { requestLayout(); } }); } } /** * 计算这一次整体滚动偏移量 * @return */ private int calculateScrollValue(){ int dx=0; if(mScroller.computeScrollOffset()){ hasToScrollValue = mScroller.getCurrX(); } if(hasToScrollValue<=0){ hasToScrollValue=0; mScroller.forceFinished(true); } if(hasToScrollValue >= maxScrollValue) { hasToScrollValue = maxScrollValue; mScroller.forceFinished(true); } dx=hasToScrollValue-scrollValue; scrollValue=hasToScrollValue; return -dx; } /** * 计算最大滚动值 */ private void calculateMaxScrollValue(){ if(getListItemCount()>0) { if(lastItemIndex==adapter.getCount()-1) { //已经显示了最后一项 if(getChildAt(getChildCount() - 1).getRight()>=getShowEndEdge()) { maxScrollValue = scrollValue + getChildAt(getChildCount() - 1).getRight() - getShowEndEdge(); }else{ maxScrollValue=0; } } }else{ if(adapter!=null&&adapter.getCount()>0){ }else { if (getChildCount() > 0 && getChildAt(getChildCount() - 1).getRight() >= getShowEndEdge()) { maxScrollValue = scrollValue + getChildAt(getChildCount() - 1).getRight() - getShowEndEdge(); } else { maxScrollValue = 0; } } } } /** * 根据偏移量提取需要缓存视图 * @param dx */ private void removeNonVisibleItems(int dx) { if(getListItemCount()>0) { //移除列表头 View child = getChildAt(getStartItemIndex()); while (getListItemCount()>0&&child != null && child.getRight() + dx <= getShowStartEdge()) { displayOffset += child.getMeasuredWidth(); cacheView.offer(child); removeViewInLayout(child); firstItemIndex++; child = getChildAt(getStartItemIndex()); } //移除列表尾 child = getChildAt(getEndItemIndex()); while (getListItemCount()>0&&child != null && child.getLeft() + dx >= getShowEndEdge()) { cacheView.offer(child); removeViewInLayout(child); lastItemIndex--; child = getChildAt(getEndItemIndex()); } } } /** * 根据偏移量显示新的列表项 * @param dx */ private void showListItem(int dx) { if(adapter==null)return; int firstItemEdge = getFirstItemLeftEdge()+dx; int lastItemEdge = getLastItemRightEdge()+dx; displayOffset+=dx;//计算偏移量 //显示列表头视图 while(firstItemEdge > getShowStartEdge() && firstItemIndex-1 >= 0) { firstItemIndex--;//往前显示一个列表项 View child = adapter.getView(firstItemIndex, cacheView.poll(), this); addAndMeasureChild(child, getStartItemIndex()); firstItemEdge -= child.getMeasuredWidth(); displayOffset -= child.getMeasuredWidth(); } //显示列表未视图 while(lastItemEdge < getShowEndEdge() && lastItemIndex+1 < adapter.getCount()) { lastItemIndex++;//往后显示一个列表项 View child = adapter.getView(lastItemIndex, cacheView.poll(), this); addAndMeasureChild(child, getEndItemIndex()+1); lastItemEdge += child.getMeasuredWidth(); } } /** * 调整各个item的位置 */ private void adjustItems() { if(getListItemCount() > 0){ int left = displayOffset+getShowStartEdge(); int top = getPaddingTop(); int endIndex = getEndItemIndex(); int childWidth,childHeight; for(int i=0;i<=endIndex;i++){ View child = getChildAt(i); childWidth = child.getMeasuredWidth(); childHeight = child.getMeasuredHeight(); child.layout(left, top, left + childWidth, top + childHeight); left += childWidth; } firstItemLeftEdge=getChildAt(getStartItemIndex()).getLeft(); lastItemRightEdge=getChildAt(getEndItemIndex()).getRight(); } } //以下八个方法为概念性封装方法,有助于往后的扩展和维护 /** * 获得列表视图中item View的总数 * @return */ private int getListItemCount(){ int itemCount=getChildCount(); return itemCount; } /** * 获得列表视图中第一个item View下标 * @return */ private int getStartItemIndex(){ return 0; } /** * 获得列表视图中最后一个item View下标 * @return */ private int getEndItemIndex(){ return getChildCount()-1; } /** * 获得列表视图中第一个item View左边界值 * @return */ private int getFirstItemLeftEdge(){ if(getListItemCount()>0) { return firstItemLeftEdge; }else{ return 0; } } /** * 获得列表视图中最后一个item View右边界值 * @return */ private int getLastItemRightEdge(){ if(getListItemCount()>0) { return lastItemRightEdge; }else{ return 0; } } /** * 取得视图可见区域的左边界 * @return */ private int getShowStartEdge(){ return getPaddingLeft(); } /** * 取得视图可见区域的右边界 * @return */ private int getShowEndEdge(){ return getWidth()-getPaddingRight(); } /** * 取得视图可见区域的宽度 * @return */ private int getShowWidth(){ return getWidth()-getPaddingLeft()-getPaddingRight(); } /** * 在onTouchEvent处理事件,让子视图优先消费事件 * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { return mGesture.onTouchEvent(event); } private GestureDetector.OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { mScroller.forceFinished(true);//点击时停止滚动 return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mScroller.fling(hasToScrollValue, 0, (int)-velocityX, 0, 0, maxScrollValue, 0, 0); requestLayout(); return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { synchronized(HorizontalListView2.this){ hasToScrollValue += (int)distanceX; } requestLayout(); return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { for(int i=0;i
列表的快速滚动的实现主要依赖于android官方提供的android.widget.Scroller类,具体实现需要以下步骤:
1.捕获快速滑动事件,并启动快速滑动计算(Scroller的功能)
@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mScroller.fling(hasToScrollValue, 0, (int)-velocityX, 0, 0, maxScrollValue, 0, 0); requestLayout(); return true;}
在OnGestureListener中添加public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)方法,以捕获快速滑动事件,调用Scroller.fling()启动快速滑动计算,然后调用requestLayout()要求重新布局界面,已实现快速滑动效果。
2.使用Scroller计算一次发生滚动的位移值,刷新视图
在未实现快速滑动之前calculateScrollValue()方法实现如下:
/** * 计算这一次整体滚动偏移量 * @return */private int calculateScrollValue(){ int dx=0; hasToScrollValue=hasToScrollValue<0? 0:hasToScrollValue; hasToScrollValue=hasToScrollValue>maxScrollValue? maxScrollValue:hasToScrollValue; dx=hasToScrollValue-scrollValue; scrollValue=hasToScrollValue; return -dx;}
以下代码是为了实现快速滑动而做出的修改:
/** * 计算这一次整体滚动偏移量 * @return */private int calculateScrollValue(){ int dx=0; if(mScroller.computeScrollOffset()){ hasToScrollValue = mScroller.getCurrX(); } if(hasToScrollValue<=0){ hasToScrollValue=0; mScroller.forceFinished(true); } if(hasToScrollValue >= maxScrollValue) { hasToScrollValue = maxScrollValue; mScroller.forceFinished(true); } dx=hasToScrollValue-scrollValue; scrollValue=hasToScrollValue; return -dx;}
主要是调用Scroller计算需要发生滚动的位移值,以及在滚动到边界上的时候,让Scroller停止计算
3.如果整体滑动还未停止(即Scroller的滚动计算还未结束),则重复执行步骤2
这一步只需要在protected void onLayout(boolean changed, int left, int top, int right, int bottom)方法中添加以下代码即可
//继续滚动if(!mScroller.isFinished()){ post(new Runnable(){ @Override public void run() { requestLayout(); } });}
Scroller的整体滚动计算还未完成则调用 requestLayout()不断重复刷新界面,直到整体滚动完成
4.捕获按下事件,实现当用户按下时停止自动快速滚动操作
@Overridepublic boolean onDown(MotionEvent e) { mScroller.forceFinished(true);//点击时停止滚动 return true;}
在OnGestureListener的public boolean onDown(MotionEvent e) 方法中添加mScroller.forceFinished(true); 用于告诉Scroller整体滚动计算需要停止,借此停止整个界面的滑动
对于ListView,item需要响应的事件比较重要的就两个,点击和长按,具体实现如下:
1.点击事件:在OnGestureListener中添加public boolean onSingleTapConfirmed(MotionEvent e)方法的实现,以响应点击事件
@Overridepublic boolean onSingleTapConfirmed(MotionEvent e) { for(int i=0;i
2.长按事件:在OnGestureListener中添加public void onLongPress(MotionEvent e) 方法的实现,以响应长按事件
@Overridepublic void onLongPress(MotionEvent e) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (isEventWithinView(e, child)) { int position=firstItemIndex + i; if (getOnItemLongClickListener() != null) { getOnItemLongClickListener().onItemLongClick(HorizontalListView2.this, child, position, adapter.getItemId(position)); } break; } }}