ListView滚动方向和滚动位置的探索

Posted ykb19891230

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ListView滚动方向和滚动位置的探索相关的知识,希望对你有一定的参考价值。

在开发的过程中,有时候会需要知道可以滚动的视图当前的滚动方向、是否滚动到顶部或底部等信息,ScrollView因为在新API中才加入了滚动回调接口,在之前都是之定义回调接口,通过onScrollChanged方法来回调,相比之下,ListView就要简单得多,因为其自带滚动回调接口。

但是往往有时候,系统的API返回的数据不能满足我们的需求,比如下面这种效果:

1.内容向上滚动时,滚动到底部时按钮显示,其他状态则隐藏
2.内容向下滚动时,按钮显示

那么问题就来了,如何知道ListView是在向上或向下滚动,如何知道ListView是否滚动到了顶部或底部。网上有很多判断ListView是否滚动到顶部或底部的方法,但是仅凭firstVisibleItem或LastVisibleItem判断是不准确的,因为“看见了并不代表显示完全”。当然,还有人认为可以用定时器去获取getScrollY来判断,不过这个值永远是0。

首先要解决的是getScrollY,如果了解ListView的两个方法或许下面就好理解的多:getCount、getChildCount。我们只需要知道Listview当前“所有可见”item的第一个的顶部坐标,即scrollY,这个是用来判断ListView是否有必要继续回调滚动状态,获取方式如下:

   /**
     * scrollY
     *
     * @return scrollY
     */
    private int getFirstViewScrollY() 
        View c = scrollView.getChildAt(0);//第一个可见的view
        if (c == null) 
            return 0;
        
        int top = c.getTop() + scrollView.getPaddingTop();
        return -top;
    

其次是获取Listview当前的位置,是在顶部、底部或其他位置。这个就需要综合getLastVisiblePosition和getFirstVisiblePosition来判断了:

   /**
     * 判断当前滚动内容的位置
     *
     * @return
     */
    private int getPosition() 
        //滑动到底部,最后可见的item为list最后一个数据,且自后一个item已完全显示,底部padding也完全显示
        if (scrollView.getLastVisiblePosition() == scrollView.getCount() - 1 && scrollView.getChildAt(scrollView.getChildCount() - 1).getBottom() + scrollView.getPaddingBottom() == scrollView.getBottom()) 
            return OnScrollCallback.SCROLL_POSITION_BOTTOM;
        
        //滑动到顶部
        else if (scrollView.getFirstVisiblePosition() == 0 && scrollView.getChildAt(0).getTop() == scrollView.getPaddingTop()) 
            return OnScrollCallback.SCROLL_POSITION_TOP;
        
        //其他
        else 
            return OnScrollCallback.SCROLL_POSITION_OTHER;
        
    

都有注释,我就不在赘述了。

最后,获取到的值要怎么用呢?在Listview的OnScrollListener的两个方法里去回调滚动位置、滚动状态和滚动方向即可:

   /**
     * 设置scroll回调
     */
    private void setUpScroll() 
        scrollView.setOnScrollListener(new AbsListView.OnScrollListener() 
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) 
                if (null == callback) 
                    return;
                
                switch (scrollState) 
                    //滑动停止
                    case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                        int position = getPosition();
                        lastScrollY = getFirstViewScrollY();
                        SLog.d("position : " + position + " lastScrollY : " + lastScrollY);
                        callback.onScrollChanged(OnScrollCallback.STATE_STOPPED, OnScrollCallback.SCROLL_DIRECTION_NOTHING, position);
                        break;
                    //手指在滑动
                    case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                        fromTouch = true;
                        break;
                    //手指移开
                    case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                        break;
                
            

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) 
                if (!fromTouch) 
                    return;
                
                if (null == callback) 
                    return;
                
                final int tempY = getFirstViewScrollY();
                if (lastScrollY == tempY) 
                    return;
                
                SLog.d("scrollY : " + tempY + " lastScrollY :" + lastScrollY);
                lastScrollY = tempY;
                View childAt = scrollView.getChildAt(0);
                int[] location = new int[2];
                childAt.getLocationOnScreen(location);
                SLog.d("firstVisibleItem= " + firstVisibleItem + " , y=" + location[1]);
                int direction = OnScrollCallback.SCROLL_DIRECTION_NOTHING;
                int state = OnScrollCallback.STATE_SCROLLING;
                if (firstVisibleItem != lastVisibleItem) 
                    if (firstVisibleItem > lastVisibleItem) 
                        SLog.d("向上滑动");
                        direction = OnScrollCallback.SCROLL_DIRECTION_UP;
                     else if (firstVisibleItem < lastVisibleItem) 
                        SLog.d("向下滑动");
                        direction = OnScrollCallback.SCROLL_DIRECTION_DOWN;
                     
                    lastVisibleItem = firstVisibleItem;
                    mTouchY = location[1];
                 else 
                    if (mTouchY > location[1]) 
                        SLog.d("->向上滑动");
                        direction = OnScrollCallback.SCROLL_DIRECTION_UP;
                     else if (mTouchY < location[1]) 
                        SLog.d("->向下滑动");
                        direction = OnScrollCallback.SCROLL_DIRECTION_DOWN;
                     else 
                        SLog.d("->未滑动");
                        state = OnScrollCallback.STATE_STOPPED;
                    
                    mTouchY = location[1];
                
                callback.onScrollChanged(state, direction, getPosition());
            
        );
    

上面方法里要说的就是 getLocationOnScreen(int[2]),这是View自带的方法,获取当前视图锚点在屏幕中的绝对位置。还有就是if|else判断,当firstVisibleItem 发生变化时,直接通过变化的差值来判断滚动的方向:大于0向上滑动,小于0向下滑动;当firstVisibleItem 未发生变化时,通过获取Listview的第一个item位置的变化来判断滚动的方向和状态:如果新位置小于原位置(mTouchY > location[1])向上滑动,反之则向下滑动,如果位置相等则未滑动。

再来看看滚动回调接口的定义:

 public interface OnScrollCallback 
        int STATE_SCROLLING = 1;
        int STATE_STOPPED = 2;

        int SCROLL_DIRECTION_NOTHING = 0;
        int SCROLL_DIRECTION_UP = 1;
        int SCROLL_DIRECTION_DOWN = 2;

        int SCROLL_POSITION_TOP = 1;
        int SCROLL_POSITION_BOTTOM = 2;
        int SCROLL_POSITION_OTHER = 0;

        void onScrollChanged(int state, int direction, int position);
    

最后,我把它整理成了一个帮助类,希望对你有帮助。

package com.ykbjson.demo.customview.otherview;

import android.view.View;
import android.widget.AbsListView;

import com.ykbjson.demo.tools.SLog;


/**
 * 包名:com.ykbjson.demo.customview.otherview
 * 描述:设置AbsListView有滚动回调
 * 创建者:yankebin
 * 日期:2016/5/20
 */
public class AbsListViewCompat<T extends AbsListView> 
    public interface OnScrollCallback 
        int STATE_SCROLLING = 1;
        int STATE_STOPPED = 2;

        int SCROLL_DIRECTION_NOTHING = 0;
        int SCROLL_DIRECTION_UP = 1;
        int SCROLL_DIRECTION_DOWN = 2;

        int SCROLL_POSITION_TOP = 1;
        int SCROLL_POSITION_BOTTOM = 2;
        int SCROLL_POSITION_OTHER = 0;

        void onScrollChanged(int state, int direction, int position);
    

    /**
     * 滚动回调接口
     */
    private OnScrollCallback callback;
    /**
     * 最后一次滚动值
     */
    private int lastScrollY;
    /**
     * 滚动视图最后一个可见的item
     */
    private int lastVisibleItem;
    /**
     * 手指在屏幕的y值
     */
    private int mTouchY;
    /**
     * 滚动视图
     */
    private T scrollView;

    /**
     * 是否是手动滑动,排除setselection
     */
    private boolean fromTouch;

    /**
     * 设置需要滚动的view
     *
     * @param scrollView
     * @return
     */
    public AbsListViewCompat setScrollView(T scrollView) 
        if (null == scrollView) 
            return this;
        

        this.scrollView = scrollView;
        setUpScroll();

        return this;
    

    /**
     * 获取当前滚动的view
     *
     * @return
     */
    public T getScrollView() 
        return scrollView;
    

    public AbsListViewCompat setOnScrollCallback(OnScrollCallback callback) 
        this.callback = callback;
        return this;
    

    /**
     * 设置scroll回调
     */
    private void setUpScroll() 
        scrollView.setOnScrollListener(new AbsListView.OnScrollListener() 
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) 
                if (null == callback) 
                    return;
                
                switch (scrollState) 
                    //滑动停止
                    case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                        int position = getPosition();
                        lastScrollY = getFirstViewScrollY();
                        SLog.d("position : " + position + " lastScrollY : " + lastScrollY);
                        callback.onScrollChanged(OnScrollCallback.STATE_STOPPED, OnScrollCallback.SCROLL_DIRECTION_NOTHING, position);
                        break;
                    //手指在滑动
                    case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                        fromTouch = true;
                        break;
                    //手指移开
                    case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                        break;
                
            

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) 
                if (!fromTouch) 
                    return;
                
                if (null == callback) 
                    return;
                
                final int tempY = getFirstViewScrollY();
                if (lastScrollY == tempY) 
                    return;
                
                SLog.d("scrollY : " + tempY + " lastScrollY :" + lastScrollY);
                lastScrollY = tempY;
                View childAt = scrollView.getChildAt(0);
                int[] location = new int[2];
                childAt.getLocationOnScreen(location);
                SLog.d("firstVisibleItem= " + firstVisibleItem + " , y=" + location[1]);
                int direction = OnScrollCallback.SCROLL_DIRECTION_NOTHING;
                int state = OnScrollCallback.STATE_SCROLLING;
                if (firstVisibleItem != lastVisibleItem) 
                    if (firstVisibleItem > lastVisibleItem) 
                        SLog.d("向上滑动");
                        direction = OnScrollCallback.SCROLL_DIRECTION_UP;
                     else if (firstVisibleItem < lastVisibleItem) 
                        SLog.d("向下滑动");
                        direction = OnScrollCallback.SCROLL_DIRECTION_DOWN;
                     
                    lastVisibleItem = firstVisibleItem;
                    mTouchY = location[1];
                 else 
                    if (mTouchY > location[1]) 
                        SLog.d("->向上滑动");
                        direction = OnScrollCallback.SCROLL_DIRECTION_UP;
                     else if (mTouchY < location[1]) 
                        SLog.d("->向下滑动");
                        direction = OnScrollCallback.SCROLL_DIRECTION_DOWN;
                     else 
                        SLog.d("->未滑动");
                        state = OnScrollCallback.STATE_STOPPED;
                    
                    mTouchY = location[1];
                
                callback.onScrollChanged(state, direction, getPosition());
            
        );
    

    /**
     * scrollY
     *
     * @return scrollY
     */
    private int getFirstViewScrollY() 
        View c = scrollView.getChildAt(0);//第一个可见的view
        if (c == null) 
            return 0;
        
        int top = c.getTop() + scrollView.getPaddingTop();
        return -top;
    

    /**
     * 判断当前滚动内容的位置
     *
     * @return
     */
    private int getPosition() 
        //滑动到底部,最后可见的item为list最后一个数据,且自后一个item已完全显示,底部padding也完全显示
        if (scrollView.getLastVisiblePosition() == scrollView.getCount() - 1 && scrollView.getChildAt(scrollView.getChildCount() - 1).getBottom() + scrollView.getPaddingBottom() == scrollView.getBottom()) 
            return OnScrollCallback.SCROLL_POSITION_BOTTOM;
        
        //滑动到顶部
        else if (scrollView.getFirstVisiblePosition() == 0 && scrollView.getChildAt(0).getTop() == scrollView.getPaddingTop()) 
            return OnScrollCallback.SCROLL_POSITION_TOP;
        
        //其他
        else 
            return OnScrollCallback.SCROLL_POSITION_OTHER;
        
    

以上是关于ListView滚动方向和滚动位置的探索的主要内容,如果未能解决你的问题,请参考以下文章

在listview中颤动Custompaint:忽略两个手指滚动

滚动自定义 ListView 时,复选框值发生变化

Flutter ListView 不滚动(我觉得我已经尝试了互联网上的所有解决方案)

接收滚动方向,power ios

当 ipad 中的方向发生两次变化时,UIScrollView 的位置会发生变化

使 ListView 在垂直方向上可滚动