ListView作为最常用的控件之一,虽然说它的性能不如RecyclerView,但是通过一定的优化性能也是很不错的。那我们今天来实现一种比较常见的效果——弹性ListView
具有弹性ListView会有一种比较友好的UI体验,在滑动到ListView的顶部或者底部时候,我们会因为惯性继续滑动来确保是否是最开头或者最末尾的Item,那么我们给予它一种弹性效果,UI体验将显得更加友好。(例如QQ侧滑里的Item菜单效果)
说了这么多,那到底是怎样一种效果呢?我们来看一波效果图:
其实百度一搜会有很多不同的实现方式,那我这里提供一种比较简单,容易理解的实现方法。
首先,我们来看一个属性方法:
android:overScrollMode="always";
它的作用是处理边界回弹效果,当视图滚动到边界时,它会再滚动一点距离,附带一个发光的效果。(例如上图滚动到底部的蓝色弧形效果)。
它的属性总共有三种模式:1、always (表示:总是允许滚动)
2、ifContentScrolls(表示:如果视图内容到达边界时滚动)
3、never(表示:从不滚动)
当然,我们还可以通过代码的方式设置,比如这样:
/**
* OVER_SCROLL_ALWAYS
* OVER_SCROLL_IF_CONTENT_SCROLLS
* OVER_SCROLL_NEVER
*/
mListView.setOverScrollMode(View.OVER_SCROLL_ALWAYS);
这三种模式与上面介绍的一模一样,只是上面是在XML里的属性方法。
最后,我们介绍一个至关重要的方法,也是通过这个方法来实现我们的弹性效果的:
@Override
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, maxOverScrollY, isTouchEvent);
}
这个方法呢比较长,也是比较复杂的一个方法,作用是当视图滚动到达屏幕的边缘时会回调这个方法,我们在这个方法里就可以定制一些我们自己想要到效果了。重点来看一下这个方法讲了哪些吧:
1、int deltaX / int deltaY ,表示:偏移量,当前滚动的x,y值。
2、int scrollX / int scrollY ,表示:其scroll的值。
3、int scrollRangeX / int scrollRangeY ,表示:可以滚动的范围值。例如:内容视图大小(100,100)比父容器(50,50)大,则返回(100,100),反之。
4、int maxOverScrollX / int maxOverScrollY ,表示:允许滚动的最大距离。X或Y方向,默认值为0。
5、boolean isTouchEvent ,表示:是否在onTouchEvent()中调用此函数。
从此函数介绍中,我们可以发现有两个参数是我们可以用到的,int maxOverScrollX / int maxOverScrollY
既然我们要对ListView的弹性做文章(我们用垂直方向的),那就得从 int maxOverScrollY 这里下手了。我们得知它的默认值是为0的,所以它能滚出边界外的最大是就是0,也就是滚动距离太小,我们看不到而已。
所以我们这样,给予它应该大于0初始值,你便可以很清楚的看到效果了。当然,这么做的话,它滚动的距离是固定的,也不是很友好。
那么,我们来实现上图动态更改的效果吧。我们看一下代码:
/**
* @Created by xww.
* @Creation time 2018/7/16.
*/
public class ElasticityListView extends ListView {
private int maxOverScrollYDis;
private float y1, y2;
public ElasticityListView(Context context) {
super(context);
}
public ElasticityListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ElasticityListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
y1 = ev.getY(0);//获取手指在屏幕按下第一个点y坐标
break;
case MotionEvent.ACTION_MOVE:
y2 = ev.getY(0);//获取手指在移动过程中的y坐标
maxOverScrollYDis = (int) Math.abs((y2 - y1));
break;
case MotionEvent.ACTION_UP:
// maxOverScrollYDis = 0;
break;
}
return super.onTouchEvent(ev);
}
@Override
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,
maxOverScrollYDis / 2, isTouchEvent);
}
}
这个就是我们最终的自定义的ListView了,除了重载overScrollBy()这个方法,我们还重载了onTouchEvent()方法。这里实现思路是这样的:在onTouchEvent()方法中,我们获取了手指按下去的第一个点y1坐标,在手指移动过程中再获取了y2坐标,最后通过两个坐标的绝对值来动态更改滚出屏幕的距离,达到回弹效果。maxOverScrollYDis / 2 ,这也我感觉是比较合适的。
如果你想改变回弹速度的话,只需在UP事件里控制maxOverScrollYDis 的值就可以了,我这里直接给了一个初始值0,我们看一下效果图:
我们可以看到它将快速的回弹至初始状态。当然,如果你想定制更多的效果,通过这种方式实现比较有局限性。这里会出现一个bug,不知道为什么会出现这样的原因。
Bug情况:我们在回弹的时候,会发现它卡在那里不动了。
有没有知道的大佬可以回复一下,解决一下问题,感谢各位阅读。