检测向上滚动并在ListView中向下滚动
我有以下要求:
- 首先,页面号:2的数据从服务器获取,并且项目被填充在ListView中。
考虑到prev页面和下一个页面在场景中都可用,添加了以下代码:
if(prevPageNo > 0){ mListViewActual.setOnScrollListener(this); } if(nextPageNo > 0){ mListViewActual.setOnScrollListener(this); }
我应该检测什么条件向上滚动并向下滚动以下方法:
- void onScroll(AbsListView视图,int firstVisibleItem,int visibleItemCount,int totalItemCount)
- void onScrollStateChanged(AbsListView视图,int scrollState)
在操作之后:检测到向上滚动并向下滚动,相应地,服务将被调用,前导页面号或下一页否,以获取要在Listview中填充的项目。
任何投入都将有所帮助。
通过以下链接,但它没有返回正确的向上滚动/向下滚动操作:
链接1 链接2
尝试使用setOnScrollListener并使用scrollState实现onScrollStateChanged
setOnScrollListener(new OnScrollListener(){ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO Auto-generated method stub } public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO Auto-generated method stub final ListView lw = getListView(); if(scrollState == 0) Log.i("a", "scrolling stopped..."); if (view.getId() == lw.getId()) { final int currentFirstVisibleItem = lw.getFirstVisiblePosition(); if (currentFirstVisibleItem > mLastFirstVisibleItem) { mIsScrollingUp = false; Log.i("a", "scrolling down..."); } else if (currentFirstVisibleItem < mLastFirstVisibleItem) { mIsScrollingUp = true; Log.i("a", "scrolling up..."); } mLastFirstVisibleItem = currentFirstVisibleItem; } } });
以上是一些上述解决scheme的工作修改版本。
添加另一个类ListView:
package com.example.view; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.AbsListView; public class ListView extends android.widget.ListView { private OnScrollListener onScrollListener; private OnDetectScrollListener onDetectScrollListener; public ListView(Context context) { super(context); onCreate(context, null, null); } public ListView(Context context, AttributeSet attrs) { super(context, attrs); onCreate(context, attrs, null); } public ListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); onCreate(context, attrs, defStyle); } @SuppressWarnings("UnusedParameters") private void onCreate(Context context, AttributeSet attrs, Integer defStyle) { setListeners(); } private void setListeners() { super.setOnScrollListener(new OnScrollListener() { private int oldTop; private int oldFirstVisibleItem; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (onScrollListener != null) { onScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (onScrollListener != null) { onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } if (onDetectScrollListener != null) { onDetectedListScroll(view, firstVisibleItem); } } private void onDetectedListScroll(AbsListView absListView, int firstVisibleItem) { View view = absListView.getChildAt(0); int top = (view == null) ? 0 : view.getTop(); if (firstVisibleItem == oldFirstVisibleItem) { if (top > oldTop) { onDetectScrollListener.onUpScrolling(); } else if (top < oldTop) { onDetectScrollListener.onDownScrolling(); } } else { if (firstVisibleItem < oldFirstVisibleItem) { onDetectScrollListener.onUpScrolling(); } else { onDetectScrollListener.onDownScrolling(); } } oldTop = top; oldFirstVisibleItem = firstVisibleItem; } }); } @Override public void setOnScrollListener(OnScrollListener onScrollListener) { this.onScrollListener = onScrollListener; } public void setOnDetectScrollListener(OnDetectScrollListener onDetectScrollListener) { this.onDetectScrollListener = onDetectScrollListener; } }
和一个界面:
public interface OnDetectScrollListener { void onUpScrolling(); void onDownScrolling(); }
最后如何使用:
com.example.view.ListView listView = (com.example.view.ListView) findViewById(R.id.list); listView.setOnDetectScrollListener(new OnDetectScrollListener() { @Override public void onUpScrolling() { /* do something */ } @Override public void onDownScrolling() { /* do something */ } });
在你的XML布局中:
<com.example.view.ListView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent"/>
这是我的第一个话题,不要苛刻地评判我。 =)
这是一个简单的实现:
lv.setOnScrollListener(new OnScrollListener() { private int mLastFirstVisibleItem; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if(mLastFirstVisibleItem<firstVisibleItem) { Log.i("SCROLLING DOWN","TRUE"); } if(mLastFirstVisibleItem>firstVisibleItem) { Log.i("SCROLLING UP","TRUE"); } mLastFirstVisibleItem=firstVisibleItem; } });
如果你需要更多的精度,你可以使用这个自定义ListView类:
import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.AbsListView; import android.widget.ListView; /** * Created by root on 26/05/15. */ public class ScrollInterfacedListView extends ListView { private OnScrollListener onScrollListener; private OnDetectScrollListener onDetectScrollListener; public ScrollInterfacedListView(Context context) { super(context); onCreate(context, null, null); } public ScrollInterfacedListView(Context context, AttributeSet attrs) { super(context, attrs); onCreate(context, attrs, null); } public ScrollInterfacedListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); onCreate(context, attrs, defStyle); } @SuppressWarnings("UnusedParameters") private void onCreate(Context context, AttributeSet attrs, Integer defStyle) { setListeners(); } private void setListeners() { super.setOnScrollListener(new OnScrollListener() { private int oldTop; private int oldFirstVisibleItem; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (onScrollListener != null) { onScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (onScrollListener != null) { onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } if (onDetectScrollListener != null) { onDetectedListScroll(view, firstVisibleItem); } } private void onDetectedListScroll(AbsListView absListView, int firstVisibleItem) { View view = absListView.getChildAt(0); int top = (view == null) ? 0 : view.getTop(); if (firstVisibleItem == oldFirstVisibleItem) { if (top > oldTop) { onDetectScrollListener.onUpScrolling(); } else if (top < oldTop) { onDetectScrollListener.onDownScrolling(); } } else { if (firstVisibleItem < oldFirstVisibleItem) { onDetectScrollListener.onUpScrolling(); } else { onDetectScrollListener.onDownScrolling(); } } oldTop = top; oldFirstVisibleItem = firstVisibleItem; } }); } @Override public void setOnScrollListener(OnScrollListener onScrollListener) { this.onScrollListener = onScrollListener; } public void setOnDetectScrollListener(OnDetectScrollListener onDetectScrollListener) { this.onDetectScrollListener = onDetectScrollListener; } public interface OnDetectScrollListener { void onUpScrolling(); void onDownScrolling(); } }
一个使用的例子:(不要忘记在你的layout.xml中添加它作为一个Xml标签)
scrollInterfacedListView.setOnDetectScrollListener(new ScrollInterfacedListView.OnDetectScrollListener() { @Override public void onUpScrolling() { //Do your thing } @Override public void onDownScrolling() { //Do your thing } });
在发布所有方法的时候,在用户从第一个元素向上滚动或者从最后一个向下滚动的时候会出现问题。 这是检测向上/向下滚动的另一种方法:
listView.setOnTouchListener(new View.OnTouchListener() { float height; @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); float height = event.getY(); if(action == MotionEvent.ACTION_DOWN){ this.height = height; }else if(action == MotionEvent.ACTION_UP){ if(this.height < height){ Log.v(TAG, "Scrolled up"); }else if(this.height > height){ Log.v(TAG, "Scrolled down"); } } return false; } });
ListView listView = getListView(); listView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { view.setOnTouchListener(new OnTouchListener() { private float mInitialX; private float mInitialY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mInitialX = event.getX(); mInitialY = event.getY(); return true; case MotionEvent.ACTION_MOVE: final float x = event.getX(); final float y = event.getY(); final float yDiff = y - mInitialY; if (yDiff > 0.0) { Log.d(tag, "SCROLL DOWN"); scrollDown = true; break; } else if (yDiff < 0.0) { Log.d(tag, "SCROLL up"); scrollDown = true; break; } break; } return false; } });
我的解决scheme完美地为每个滚动方向提供确切的值。 distanceFromFirstCellToTop
包含从第一个单元格到父视图顶部的确切距离。 我保存在previousDistanceFromFirstCellToTop
这个值,当我滚动我比较新的价值。 如果它比较低,那么我滚动,否则,我向下滚动。
private int previousDistanceFromFirstCellToTop; listview.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { View firstCell = listview.getChildAt(0); int distanceFromFirstCellToTop = listview.getFirstVisiblePosition() * firstCell.getHeight() - firstCell.getTop(); if(distanceFromFirstCellToTop < previousDistanceFromFirstCellToTop) { //Scroll Up } else if(distanceFromFirstCellToTop > previousDistanceFromFirstCellToTop) { //Scroll Down } previousDistanceFromFirstCellToTop = distanceFromFirstCellToTop; } });
对于Xamarin开发人员来说,解决scheme如下:
注意:不要忘记在UI线程上运行
listView.Scroll += (o, e) => { View firstCell = listView.GetChildAt(0); int distanceFromFirstCellToTop = listView.FirstVisiblePosition * firstCell.Height - firstCell.Top; if (distanceFromFirstCellToTop < previousDistanceFromFirstCellToTop) { //Scroll Up } else if (distanceFromFirstCellToTop > previousDistanceFromFirstCellToTop) { //Scroll Down } previousDistanceFromFirstCellToTop = distanceFromFirstCellToTop; };
我已经使用了这个更简单的解决scheme:
setOnScrollListener( new OnScrollListener() { private int mInitialScroll = 0; @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int scrolledOffset = computeVerticalScrollOffset(); boolean scrollUp = scrolledOffset > mInitialScroll; mInitialScroll = scrolledOffset; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } }
只需将滚动监听器设置为您的列表视图。
如果您有页眉或页脚,则应该检查可见的计数。 如果增加,则表示您正在向下滚动。 (如果有页脚而不是页眉,则将其颠倒)
如果您的列表视图中没有任何页眉或页脚,则可以删除遮挡可见项目数的行。
listView.setOnScrollListener(new AbsListView.OnScrollListener() { public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mLastFirstVisibleItem > firstVisibleItem) { Log.e(getClass().toString(), "scrolling up"); } else if (mLastFirstVisibleItem < firstVisibleItem) { Log.e(getClass().toString(), "scrolling down"); } else if (mLastVisibleItemCount < visibleItemCount) { Log.e(getClass().toString(), "scrolling down"); } else if (mLastVisibleItemCount > visibleItemCount) { Log.e(getClass().toString(), "scrolling up"); } mLastFirstVisibleItem = firstVisibleItem; mLastVisibleItemCount = visibleItemCount; } public void onScrollStateChanged(AbsListView listView, int scrollState) { } });
并有这个variables
int mLastFirstVisibleItem; int mLastVisibleItemCount;
为了检测更大元素的滚动,我还提供了一个onTouch监听器:
listview.setOnTouchListener(new View.OnTouchListener() { int scrollEventListSize = 5; float lastY; // Used to correct for occasions when user scrolls down(/up) but the onTouchListener detects it incorrectly. We will store detected up-/down-scrolls with -1/1 in this list and evaluate later which occured more often List<Integer> downScrolledEventsHappened; @Override public boolean onTouch(View v, MotionEvent event) { float diff = 0; if(event.getAction() == event.ACTION_DOWN){ lastY = event.getY(); downScrolledEventsHappened = new LinkedList<Integer>(); } else if(event.getAction() == event.ACTION_MOVE){ diff = event.getY() - lastY; lastY = event.getY(); if(diff>0) downScrolledEventsHappened.add(1); else downScrolledEventsHappened.add(-1); //List needs to be filled with some events, will happen very quickly if(downScrolledEventsHappened.size() == scrollEventListSize+1){ downScrolledEventsHappened.remove(0); int res=0; for(int i=0; i<downScrolledEventsHappened.size(); i++){ res+=downScrolledEventsHappened.get(i); } if (res > 0) Log.i("INFO", "Scrolled up"); else Log.i("INFO", "Scrolled down"); } } return false; // don't interrupt the event-chain } });
存储firstVisibleItem,并在下一个onScroll检查新的firstVisibleItem是否小于或大于前一个。
示例伪代码(未testing):
int lastVisibleItem = 0; boolean isScrollingDown = false; void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (firstVisibleItem > lastVisibleItem) { isScrollingDown = true; } else { isScrollingDown = false; } lastVisibleItem = firstVisibleItem; }
这些方法不能直接用于检测滚动方向。 获取方向有很多方法。 下面解释一个简单的代码(未经testing):
公共类ScrollTrackingListViewinheritanceListView { private boolean readyForMeasurement = false; private Boolean isScrollable = null; 私人浮动prevDistanceToEnd = -1.0; private ScrollDirectionListener listener = null; 公共ScrollTrackingListView(上下文上下文){ 超级(上下文); 在里面(); } 公共ScrollTrackingListView(上下文上下文,AttributeSet attrs){ super(context,attrs); 在里面(); } 公共ScrollTrackingListView(上下文上下文,AttributeSet attrs,int defStyle){ super(context,attrs,defStyle); 在里面(); } private void init(){ ViewTreeObserver observer = getViewTreeObserver(); observer.addOnGlobalLayoutListener(globalLayoutListener); setOnScrollListener(scrollListener); } 私人ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener(){ @覆盖 public void onGlobalLayout(){ readyForMeasurement = true; calculateDistanceToEnd(); } }; public void registerScrollDirectionListener(ScrollDirectionListener listener){ scrollDirectionListener = listener; } public void unregisterScrollDirectionListener(){ scrollDirectionListener = null; } 私人的OnScrollListener scrollListener = new OnScrollListener(){ @覆盖 public void onScrollStateChanged(AbsListView absListView,int i){ calculateDistanceToEnd(); } @覆盖 public void onScroll(AbsListView absListView,int i,int i1,int i2){ // 没做什么 } }; private void calculateDistanceToEnd(){ if(readyForMeasurement){ //我正在使用布局的高度,水平滚动条和 //内容以及向下滚动偏移量 // computeVerticalScrollExtent用于计算滚动条轨道中的拇指长度。 //拇指的长度是视图高度和内容长度的函数。 int verticalScrollExtent = computeVerticalScrollExtent(); int verticalScrollOffset = computeVerticalScrollOffset(); int verticalScrollRange = computeVerticalScrollRange(); int horizontalScrollBarHeight = getHorizontalScrollbarHeight(); / ** * 1.让“R”代表垂直滚动条的范围。 这对应于内容的长度 *在视图中。 * 2.让“E”代表垂直滚动条的范围。 程度是一个恒定的价值,是 *(可能)等于与视图高度成比例的值。 * 3.偏移量“o”代表用户可见范围内的当前位置。 它可以采取 *值从“0到E”。 * *现在DistanceToEnd是使用以下三个值计算的: * * DistanceToEnd =(R-o)/ E * * DistanceToEnd将保存NumberOfScreenToEnd单位的值。 * * / float distanceToEnd = ((float)(verticalScrollRange - verticalScrollOffset))/((float)(verticalScrollExtent)); if(prevDistanceToEnd == -1){ prevDistanceToEnd = distanceToEnd; } else { if(listener!= null){ if(distanceToEnd> prevDistanceToEnd){ //用户正在滚动 listener.onScrollingUp(); } else { //用户正在滚动 listener.onScrollingDown(); } } prevDistanceToEnd = distanceToEnd; } if(isScrollable == null){ //检查视图高度是否小于屏幕(即不启用滚动) if((horizontalScrollBarHeight + verticalScrollExtent)> = verticalScrollRange){ isScrollable = Boolean.FALSE; } else { isScrollable = Boolean.TRUE; } } } } 公共接口ScrollDirectionListener { public void onScrollingUp(); public void onScrollingDown(); } }
这个想法是计算distanceToEnd。 如果distanceToEnd增加,则用户向上滚动,如果减小,则用户向下滚动。 这也会给你到列表末尾的确切距离。
如果您只是想知道用户是否向上或向下滚动,则可以重写onInterceptTouchEvent来检测滚动方向,如下所示:
@覆盖 公共布尔onInterceptTouchEvent(MotionEvent事件){ switch(event.getAction()){ 大小写MotionEvent.ACTION_DOWN: mInitialX = event.getX(); mInitialY = event.getY(); 返回true; 大小写MotionEvent.ACTION_MOVE: final float x = event.getX(); final float y = event.getY(); final float yDiff = y - mInitialY; // yDiff小于0.0意味着向下滚动而yDiff大于0.0意味着向上滚动。 如果我尝试添加小于或大于符号,则预览将拒绝显示它。 if(yDiff小于0.0)listener.onScrollingDown(); else if(yDiff大于0.0)listener.onScrollingUp(); 打破; } 返回super.onInterceptTouchEvent(event); }
窍门关于检测在列表视图中向上或向下滚动,您只需在ListView的OnScrollListener中的onScroll函数上调用此函数。
private int oldFirstVisibleItem = -1; private protected int oldTop = -1; // you can change this value (pixel) private static final int MAX_SCROLL_DIFF = 5; private void calculateListScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (firstVisibleItem == oldFirstVisibleItem) { int top = view.getChildAt(0).getTop(); // range between new top and old top must greater than MAX_SCROLL_DIFF if (top > oldTop && Math.abs(top - oldTop) > MAX_SCROLL_DIFF) { // scroll up } else if (top < oldTop && Math.abs(top - oldTop) > MAX_SCROLL_DIFF) { // scroll down } oldTop = top; } else { View child = view.getChildAt(0); if (child != null) { oldFirstVisibleItem = firstVisibleItem; oldTop = child.getTop(); } } }
在android listview上检测向上/向下滚动的简单方法
@Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount){ if(prevVisibleItem != firstVisibleItem){ if(prevVisibleItem < firstVisibleItem) //ScrollDown else //ScrollUp prevVisibleItem = firstVisibleItem; }
别忘了
yourListView.setOnScrollListener(yourScrollListener);
出于某种原因,Android文档不包括这个,甚至在文档中使用的方法…花了我一会儿才find它。
要检测您的滚动是否在顶部您将使用此。
public boolean checkAtTop() { if(listView.getChildCount() == 0) return true; return listView.getChildAt(0).getTop() == 0; }
这将检查您的滚动器是否在顶部。 现在,为了做到这一点,你必须通过你有的孩子的数量,并检查这个数字。 您可能需要一次计算出屏幕上有多less人,并从您的孩子人数中扣除。 我从来没有这样做。 希望这可以帮助
以下是我会先尝试的:
1)用这些方法创build一个接口(让我们称之为OnScrollTopOrBottomListener):
void onScrollTop();
void onScrollBottom();
2)在列表的适配器中,添加一个成员实例,input为您创build的接口,并提供setter和getter。
3)在适配器的getView()实现中,检查position参数是否为0或者getCount()-1。同时检查OnScrollTopOrBottomListener实例是否为空。
4)如果位置为0,则调用onScrollTopOrBottomListener.onScrollTop()。 如果position为getCount() – 1,则调用onScrollTopOrBottomListener.onScrollBottom()。
5)在您的OnScrollTopOrBottomListener实现中,调用适当的方法来获取所需的数据。
希望有一些帮助。
布兰登
我遇到了一些使用ListView的单元大小很好的例子的问题。 所以我find了解决我的问题,检测到你的手指轻微移动。 我已经简化到尽可能最小,如下所示:
private int oldScrolly; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { View view = absListView.getChildAt(0); int scrolly = (view == null) ? 0 : -view.getTop() + absListView.getFirstVisiblePosition() * view.getHeight(); int margin = 10; Log.e(TAG, "Scroll y: " + scrolly + " - Item: " + firstVisibleItem); if (scrolly > oldScrolly + margin) { Log.d(TAG, "SCROLL_UP"); oldScrolly = scrolly; } else if (scrolly < oldScrolly - margin) { Log.d(TAG, "SCROLL_DOWN"); oldScrolly = scrolly; } } });
PD:我使用MARGIN来检测滚动,直到你达到边缘。 当我显示或隐藏视图并避免它们闪烁时,这可以避免出现问题。