PullScrollView详解(四)——完全使用listview实现下拉回弹(方法一)

前言:今天看到一篇文章,非常喜欢其中的一句话,送给大家——如果万事俱备,那还要你干嘛。

相关文章:

1、《PullScrollView详解(一)——自定义控件属性》
2、《PullScrollView详解(二)——Animation、Layout与下拉回弹》
3、《PullScrollView详解(三)——PullScrollView实现》
4、《PullScrollView详解(四)——完全使用listview实现下拉回弹(方法一)》
5、《PullScrollView详解(五)——完全使用listview实现下拉回弹(方法二)》
6、《PullScrollView详解(六)——延伸拓展(listview中getScrollY()一直等于0、ScrollView中的overScrollBy)》


在前面三篇中,我为大家展示了使用ScrollView实现下拉回弹的效果。但如果ScrollView里如果嵌套使用ListView就可能会出现问题,因为两者都会有滑动监听。操作起来可能会起冲突,然后解决了冲突问题,到后面页面性能也会很差强人意。即然如此,那我们就直接使用listview来实现下拉回弹的效果就好了。
在这篇中,我先给大家展示一种比较容易出效果的方法——重写overScrollBy()函数。在下一篇中,我们将模仿PullScrollView中的实现方式自己对OnTouchEvent()进行监听、操作。

注意注意!!!!本篇文章讲述的OverScrollBy(),大家应该把最大注意力放在《4、用途:捕捉当前listview是否到底或到顶》部分,至于下拉回弹,大家看看就好,OverScrollBy()实现的下拉回弹,bug一堆,根本无法实际运用到实际项目中,大家看看就好,下篇将带着大家利用OnTouchEvent()实现下拉回弹的效果。

一、setOverScrollMode()与OverScrollBy()

1、OverScrollBy()

OverScrollBy()是Android 9 之后才新增的API. 用于设定listview滚出屏幕后的回弹效果。先看OverScrollBy()函数的定义:

protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) 
它的参数多的有点让人模糊,下面给大家说下各个参数的意思。这些意思目前大家看不懂也没关系,先大致了解下,后面讲例子时还会再讲。
  • deltaX:当前X轴滑动的像素数
  • deltaY:当前Y轴滑动的像素数
  • scrollX:在加上deltaX值以前的X轴总的滑动量
  • scrollY:在加上deltaY值以前的Y轴总滑动量
  • scrollRangeX:
  • scrollRangeY:这两个什么意思,我也没弄懂
  • maxOverScrollX:X轴最大的OverScroll范围(最大可超过边界的像素)
  • maxOverscrollY:Y轴最大的OverScroll范围(最大可超过边界的像素)
  • isTouchEvent:当前overScrollBy函数的调用是否由Touch事件引起的

2、setOverScrollMode()

上面OverScrollBy()是当listview超过顶部或者底部的时候,会被调用。那定义listView能不能超过顶部或底部滑动,也就是说让不让OverScrollBy()调用,是通过setOverScrollMode()函数来定义的。

public void setOverScrollMode(int mode)
mode有三个取值:
//一直允许超过顶部/底部下拉
public static final int OVER_SCROLL_ALWAYS = 0;
//只有Content足够大到能scroll的时候,才允许超过顶部/底部下拉(系统默认值)
public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1;
//不允许超过顶部/底部下拉
public static final int OVER_SCROLL_NEVER = 2;
当然,我们也可以不设置setOverScrollMode(int mode),系统默认的滚动属性为OVER_SCROLL_IF_CONTENT_SCROLLS;即只有listview的content足够大到可以滚动的情况下,才允许超过顶部/底部下拉
这里大家应该对OverScrollBy()有个初步的认识了,下面我们先看看怎么实现下拉回弹,然后再讲解,为什么要这么做。

二、简单示例

先看下效果:


注意,overScrollBy()实现的回弹效果是不能设置下拉方向的,即,不但顶部下拉会回弹,在底部下拉时也会回弹。但从效果图中也可以看到,顶部下拉会回弹,底部下拉是不会回弹的,这是因为在最终代码中做了顶部还是底部判断,当在底部时,就不让用户上拉回弹了,至于怎么做到的,来一起看代码吧。

下面我们就通过一个最简单的例子来看下OverScrollBy()的用法及效果。

1、实现OverScrollView

首先,新建一个类OverScrollView,重写ListView
代码如下:

class OverScrollList extends ListView {
    //定义最大滚动高度
    int mContentMaxMoveHeight = 300;

    public OverScrollList(Context context) {
        super(context);
    }

    public OverScrollList(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public OverScrollList(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {

        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mContentMaxMoveHeight, isTouchEvent);
    }
}

在这个类里面,只做了两件事:
第一:定义一下变量mContentMaxMoveHeight,来表示可滑动到最大高度

int mContentMaxMoveHeight = 300;
第二:重写overScrollBy函数,把return语句中的maxOverScrollY替换成我们定义的mContentMaxMoveHeight;
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {

        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mContentMaxMoveHeight, isTouchEvent);
    }
要实现可以overscroll的listview就需要做这么多,下面就是一如即往的往listview里填充数据的部分了。

2、ListView填充数据

在填充数据前,还是给大家看一下MainActivity的布局:(main.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"
        >
    <com.harvic.OverScrollDemo.OverScrollList
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
</LinearLayout>
下面就是正式填充数据的环节了。

先创建一个XML来做为Item的布局 (item_layout.xml)
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/text1"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:gravity="center_vertical"
          android:minHeight="40dp"
          android:background="#ffffff"/>
然后就是在MainActivity中的填充数据的部分了,代码如下 :
public class MainActivity extends Activity {
    private String[] mStrings = {"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
            "Allgauer Emmentaler", "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
            "Allgauer Emmentaler"};
    private LinkedList<String> mListItems;
    private OverScrollList mListView;
    private ArrayAdapter<String> mAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mListView = (OverScrollList) findViewById(R.id.listview);
		//配置Adapter
        mListItems = new LinkedList<String>();
        mListItems.addAll(Arrays.asList(mStrings));
        mAdapter = new ArrayAdapter<String>(this,R.layout.item_layout, mListItems);
		
        mListView.setAdapter(mAdapter);

    }
}
这些都是有关ListView的最基本的部分了,没什么难度,也没什么讲解的必要。
源码在文章底部给出

3、疑问

(1)、为什么在OverScrollList中重写了overScrollBy后,还要设定 super.overScrollBy()里的maxOverScrollY值呢?

答:通过打日志,大家可以看到,overScrollBy里的maxOverScrollY的值一直是0!!!
也就是说,google给我们实现了overScrollBy函数,但需要我们自己设定可overScroll的最大值。不然overScroll的最大值默认是0,即不会上下拉动!!!

(2)、overScrollBy()函数什么时候会调?

答:在listview正常滑动时,overScrollBy()函数是不会被调用的,只有在超出ListView滑动界限时才会被调用。

4、用途:捕捉当前listview是否到底或到顶

现在我们先总结一下:
  • listview具不具有下拉反弹的功能是靠setOverScrollMode()来设定的。
  • 具有下拉反弹功能以后下拉多少是靠 super.overScrollBy()里的maxOverScrollY值确定的。
  • overScrollBy()在listview到顶或到底时仍然下拉/上拉时才会被调用。
所以根据上面三点,我们就可以在listview中并不实现下拉回弹的效果时,判断当前Listview是否到顶或到底

(1)、简单计算方法

先列出代码如下:

protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
    if (deltaY>0){
        System.out.println("滑动到底端");
    }else if (deltaY < 0){
        System.out.println("滑动到顶端");
    }
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
很简单的原理:
1、首先,不改写super.overScrollBy()中的maxOverScrollY的值。因为maxOverScrollY默认是0,所以不会listview虽具有默认的滑动功能却不会滑动。
2、利用overScrollBy()只有在滑动到顶部、底部且超出滑动区域时,才会被调用的性质。当被overScrollBy调用时,肯定是到了顶部或者底部!!!所以,如果到了顶端,用户向下拉的时候overScrollBy才会被调用。此时手指向下滑动,deltaY必定小于0。同理,当listview到了底部时,只有用户向上拉的时候才会被调用,这时候手指肯定是向上移动的,所以deltaY肯定是大于0的。
deltaY的计算方法是用当前的触摸的手指位置减去上次捕捉到的手指位置。
(2)、系统给出的计算方法

看起来上面的方法好像是万无一失的,(其实我也没发现哪里会有问题),但系统研究了下源码,发现在View.java中的overScrollBy()的实现代码中有这么一段:

protected boolean overScrollBy(int deltaX, int deltaY,
        int scrollX, int scrollY,
        int scrollRangeX, int scrollRangeY,
        int maxOverScrollX, int maxOverScrollY,
        boolean isTouchEvent) {
    
    …………

int newScrollX = scrollX + deltaX;
int newScrollY = scrollY + deltaY;

    // Clamp values if at the limits and record
    final int left = -maxOverScrollX;
    final int right = maxOverScrollX + scrollRangeX;
    final int top = -maxOverScrollY;
    final int bottom = maxOverScrollY + scrollRangeY;

    boolean clampedX = false;
    if (newScrollX > right) {
        newScrollX = right;
        clampedX = true;
    } else if (newScrollX < left) {
        newScrollX = left;
        clampedX = true;
    }

    boolean clampedY = false;
    if (newScrollY > bottom) {
        newScrollY = bottom;
        clampedY = true;
    } else if (newScrollY < top) {
        newScrollY = top;
        clampedY = true;
    }
	…………
    return clampedX || clampedY;
}
我们把上拉、下拉的代码提取出来是这样的:
protected boolean overScrollBy(int deltaX, int deltaY,
        int scrollX, int scrollY,
        int scrollRangeX, int scrollRangeY,
        int maxOverScrollX, int maxOverScrollY,
        boolean isTouchEvent) {
    
    …………
int newScrollY = scrollY + deltaY;

    final int top = -maxOverScrollY;
    final int bottom = maxOverScrollY + scrollRangeY;

    boolean clampedY = false;
    if (newScrollY > bottom) {
        newScrollY = bottom;
        clampedY = true;
    } else if (newScrollY < top) {
        newScrollY = top;
        clampedY = true;
    }
	…………
    return clampedX || clampedY;
}
所以上面就是在overScrollBy中判断是否到顶、到底的方法,在OverScrollList中仿照一下,应该是这样写的:
protected boolean overScrollBy(int deltaX, int deltaY,
        int scrollX, int scrollY,
        int scrollRangeX, int scrollRangeY,
        int maxOverScrollX, int maxOverScrollY,
        boolean isTouchEvent) {
        
    int newScrollY = scrollY + deltaY;
    
    final int top = -maxOverScrollY;
    final int bottom = maxOverScrollY + scrollRangeY;

    if (newScrollY > bottom) {
        System.out.println("滑动到底端");
    } else if (newScrollY < top) {
         System.out.println("滑动到顶端");
    }
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
这才是正宗的判断是否到底、到顶的方法,至于为什么这么写,我也懒得研究了,无外乎就是弄懂scrollRangeY的含义而已。大家可以看看源码。其实,我觉得overScrollBy()的最大用途就是判断是否到底、到顶,至于什么下拉回弹,bug一堆!!!根本无法实际用到项目中!这里也只是给大家讲下这种实现方法而已,下篇我们会通过拦截OnTouchEvent()自己来实现下拉回弹。

三、带header的下拉回弹

这部分带着大家实现开篇的实现的效果。

1、listView添加透明header

(1)、概述

根据上面的效果图,我们直接来看看MainActivity的新布局代码:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent">
    <ImageView
            android:id="@+id/background_img"
            android:layout_width="match_parent"
            android:layout_height="400dp"
            android:layout_marginTop="-100dp"
            android:scaleType="fitXY"
            android:src="@drawable/pic3" />

    <com.harvic.OverScrollDemo.OverScrollList
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="@null"
            android:dividerPadding="0dp"
            android:dividerHeight="0dp"/>
</FrameLayout>
与前几篇一样,同样为ImageView添加layout_marginTop="-100dp",让其先向上移一部分,以防下拉时出现空白。
不一样的是,这里的OverScrollList控件,是全屏显示的,我们要怎么样把底部的小狗显示出来呢?
可能大家最先想到把底部的小狗图片给空出来的方法,就是给listview添加margin或者padding,那添加margin或者padding到底行不行呢?我们为OverScrollList添加一个android:paddingTop="100dp"看一下效果:
PullScrollView详解(四)——完全使用listview实现下拉回弹(方法一)_第1张图片

从效果图中可以看到,在上滑时,ListView没办法滑动到屏幕顶部。所以,我们唯一的解决方案就是为OverScrollList添加一个透明header来占据空间。

(2)、header布局

下面就为listview添加一个透明的header,布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="100dp">
</LinearLayout>
在这里什么都没有定义,只定义了一个高度。这里也可以定义背景是透明的,但如果listview有背景的话,依然会被盖住,所以要确保listview是没有背景的。不过我们不添加背景的话,默认就是透明的了。
(3)、MainActivity中添加header

给ListView添加Header很简单,直接调用addHeaderView(View v)就可以了。代码如下 :

public class MyActivity extends Activity {
    private String[] mStrings = {"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
            "Allgauer Emmentaler", "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
            "Allgauer Emmentaler"};
    private LinkedList<String> mListItems;
    private OverScrollListView mListView;
    private ArrayAdapter<String> mAdapter;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mListView = (OverScrollListView) findViewById(R.id.listview);

        ImageView headerView = (ImageView)findViewById(R.id.background_img);
        mListView.setmHeaderView(headerView);

        mListItems = new LinkedList<String>();
        mListItems.addAll(Arrays.asList(mStrings));
        mAdapter = new ArrayAdapter<String>(this,R.layout.item_layout, mListItems);

        LayoutInflater inflater = getLayoutInflater();
        View view = inflater.inflate(R.layout.headerview,mListView,false);
        mListView.addHeaderView(view);

        mListView.setAdapter(mAdapter);
    }
}
其实最关键的就是这句:
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(R.layout.headerview,mListView,false);
mListView.addHeaderView(view);
没什么难度,就是根据layout获取到view,然后将其设置给list

2、实现底部图片下拉回弹

(1)、OverScrollList中的实现

们现在能够通过重新OverScrollBy()函数让系统自已给我们实现ListView的下拉回弹,但底部的小狗图片可还是要我们自己实现下拉和回弹的。
非常庆幸的是,在OverScrollBy()的一堆参数中:
overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
有两个参数对我们来讲特别有用:
scrollY:在加上deltaY以前的滚动距离;(向上移动时为正值,向下移动时为负值)
deltaY:此次的手指移动距离(同样,向上移动时为正值,向下移动时为负值)
所以我们将scrollY与deltaY相加就可以得到当前的滚动距离了,由于在回弹时OverScrollBy()也会被调用,所以整个过程在下拉时newScrollY就会一直变到最大,而在回弹时,newScrollY会慢慢减为0;
所以我们根据这个特性,直接根据当前的滚动距离计算出当前小狗图片的位置即可
完整的代码如下:

/**
 * 阻尼系数,越小阻力就越大.
 */
public static final float SCROLL_RATIO = 0.25f;
private Rect mHeadInitRect = new Rect();
//设置topView
private View mTopView;
public void setTopView(View view) {
    mTopView = view;
}
//初始化TopView的原始位置
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        mHeadInitRect.set(mTopView.getLeft(), mTopView.getTop(), mTopView.getRight(), mTopView.getBottom());
    }
    return super.onInterceptTouchEvent(ev);
}
//随下拉滚动
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {

    int headerMoveHeight = (int)Math.abs((scrollY + deltaY) * SCROLL_RATIO);
    int mHeaderCurTop = (int) (mHeadInitRect.top + headerMoveHeight);
    mTopView.layout(mHeadInitRect.left, mHeaderCurTop, mHeadInitRect.right, (int) (mHeadInitRect.bottom + headerMoveHeight));
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mContentMaxMoveHeight, isTouchEvent);
}
这里总共分为三部分:
第一:将topView设置进来:
//设置topView
private View mTopView;
public void setTopView(View view) {
    mTopView = view;
}
第二:在手指下按点击时,初始化mTopView的位置。
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        mHeadInitRect.set(mTopView.getLeft(), mTopView.getTop(), mTopView.getRight(), mTopView.getBottom());
    }
    return super.onInterceptTouchEvent(ev);
}
在第二篇中,我们有讲过为什么mTopView的位置初始化操作要放在onInterceptTouchEvent中,这里再絮叨一遍:因为,要获取mTopView的位置获取要在onLayout()以后才可以得到,也就是要在整个控件绘制完以后才能获取它的显示位置。所以在OverScrollList的构造函数中,是获取不到它的位置的。大家还记得 《FlowLayout详解(一)——onMeasure()与onLayout()》   中说的那句吗:首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到.这里同样的道理。
第三:随下拉滚动
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {

    int headerMoveHeight = (int)Math.abs((scrollY + deltaY) * SCROLL_RATIO);
    int mHeaderCurTop = (int) (mHeadInitRect.top + headerMoveHeight);
    mTopView.layout(mHeadInitRect.left, mHeaderCurTop, mHeadInitRect.right, (int) (mHeadInitRect.bottom + headerMoveHeight));
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mContentMaxMoveHeight, isTouchEvent);
}
要注意:
  • 在向下移动时,scrollY和deltaY都是负值,所以要计算移动距离需要在外面加一层Math.abs()来取绝对值。
  • 至于为什么在计算headerMoveHeight时要将移动距离乘以SCROLL_RATIO,在第二篇中也提到过,就是让小狗图片移动的慢一些,显得难拉一点,这比较符合正常思维
(2)、MainActivity中调用setmHeaderView

这里就非常简单了,就是调用OverScrollList的setTopView()将底部的小狗图片设置进去。
代码如下:(这段代码没什么难度,就不再细讲了)

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mListView = (OverScrollList) findViewById(R.id.listview);

    //设置topView
    ImageView topView = (ImageView)findViewById(R.id.background_img);
    mListView.setTopView(topView);

    //设置headerView
    LayoutInflater inflater = getLayoutInflater();
    View view = inflater.inflate(R.layout.headerview,mListView,false);
    mListView.addHeaderView(view);

    //初始化Adapter
    mListItems = new LinkedList<String>();
    mListItems.addAll(Arrays.asList(mStrings));
    mAdapter = new ArrayAdapter<String>(this,R.layout.item_layout, mListItems);

    mListView.setAdapter(mAdapter);

}
好了,到这里基本上就结束了,下面还有两个问题需要改进,然后就完整给出大家源码。

3、问题改进

(1)、OverScrollList的透明headerView在点击时会变白

答:我们就让它不可点击就可以了。在添加headview时,调用public void addHeaderView(View v, Object data, boolean isSelectable),将isSelectable设置为false即可,代码如下:

//设置headerview不可点击
mListView.addHeaderView(view, null, false);
非常注意,在headview对应的xml中设置clickable="false"是没有作用的;也就是下面的代码是没有作用的:(headview.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="100dp"
              android:clickable="false">
    <!--如果要让headview不可点击,在这里设置clickable="false"是没用的,只有通过ListView.addHeaderView(view,null,false);来设置-->

</LinearLayout>
(2)、到底部时,仍然可以向上拉,怎么实现只顶部下拉
答:还记得我们上面有讲过,如何利用overScrollView监听到底到顶吗?这里就派上用场啦。我们可以监听是否已经到底,如果到底就将maxOverScrollY设为0,如果是顶部就设为mContentMaxMoveHeight;

protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
    //监听是否到底,如果到底就将maxOverScrollY设为0
    int newScrollY = scrollY + deltaY;
    final int bottom = maxOverScrollY + scrollRangeY;
    final int top = -maxOverScrollY;
    if (newScrollY > bottom) {
        maxOverScrollY = 0;
    } else if (newScrollY < top) {
        maxOverScrollY = mContentMaxMoveHeight;
    }
    //在向下移动时,scrollY是负值,所以scrollY + deltaY应该是当前应当所在位置。而由于scrollY + deltaY是负值,所以外层要包一个Math.abs()来取绝对值
    int headerMoveHeight = (int)Math.abs((scrollY + deltaY) * SCROLL_RATIO);
    int mHeaderCurTop = (int) (mHeadInitRect.top + headerMoveHeight);
    mTopView.layout(mHeadInitRect.left, mHeaderCurTop, mHeadInitRect.right, (int) (mHeadInitRect.bottom + headerMoveHeight));
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
好了,所有的都讲完了
源码讲在文章底部给出。

四、OverScrollBy()存在问题

在实际应用中,发现有两个内部实现问题:
1、手指滑出屏幕后,不会回弹。
2、如果在下拉到底以后,弹出一个Fragment把当前页面盖在后面,即使这个Fragment是透明的,也会导致OverScrollBy()卡住不会回弹,即使自己回弹,也不会在回弹时调用overScrollBy(),导致其它底部小狗图片回弹出错。

好啦,这里就不再啰嗦了,下篇给大家讲述怎么使用OnTouchEvent()来实现下拉回弹的效果,这才正宗。

源码内容:

1、《二、简单示例》:对应框架部分代码

2、《最终源码》:最终代码


如果本文有帮到你,记得加关注哦

源码下载地址:http://download.csdn.net/detail/harvic880925/9052605

请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/48021931 谢谢



你可能感兴趣的:(PullScrollView详解(四)——完全使用listview实现下拉回弹(方法一))