【参考链接】
ScrollView、ListView剖析 -上下拉伸回弹阻尼效果http://www.jianshu.com/p/834e522d02dc
前面讲到了Scroller,这里就不得不讲下OverScroller。
系统自带的ScrollView,只能在内容范围内滑动(在滑动到达边界时会有发光效果)
但是实际上,ScrollView内部已经实现了 能够使滑动超出内容边界以后继续滑动一段距离,并且手指松开以后可以回弹。只不过可能因为专利的原因,将这段距离的大小设置成了0,所以默认没有这个效果。
我们可以重写ScrollView的overScrollBy()方法来加入这个效果。
@Override
protected booleanoverScrollBy(intdeltaX, intdeltaY, intscrollX, intscrollY, intscrollRangeX, intscrollRangeY, intmaxOverScrollX, intmaxOverScrollY, booleanisTouchEvent){
Log.e("shadowfaxghh","MyScrollView overScrollBy()");
//给maxOverScrollY赋值100
return super.overScrollBy(deltaX,deltaY,scrollX,scrollY,scrollRangeX,scrollRangeY,0,100,isTouchEvent);
}
这是为什么呢?
其实借助于ScrollView中OverScroller类。
OverScroller是Scroller的增强类,既有Scroller的startScroll()和fling()方法,还提供了springBack()方法
下面通过分析ScrollView滑动处理的源码流程,搞清楚OverScroller各个方法的含义和功能。
基于android-2.3.3_r1源码
所以,其实就是
View提供了overScrollBy()方法,预留了onOverScrolled()方法用于重写。当调用overScrollBy()的时候,会调用到onOverScrolled(),自己的View可以重写onOverScrolled()进行处理
而OverScroller的springBack()方法其实只在超出滑动范围时起作用,设置数据和启动标志位,用于产生回弹效果。
(其实startScroll()和fling()也只是在设置数据和启动标志位)
以如下场景为例
当向上滑动超出滑动范围,但未超出额外距离时松手,日志如下
当向上滑动超出滑动范围,并且超出额外距离时松手,日志如下
借助于上面ScrollView的思路,我们可以给自己的ViewGroup增加OverScroll效果。主要方法为
1、在ACTION_MOVE中调用overScrollBy()方法,在ACTION_UP中调用OverScroller的springBack()方法
2、重写onOverScrolled()
3、重写computeScroll()
在前面例子的基础上,不使用Scroller而使用OverScroller,实现滑动时的overScroll效果
public classMyFrameLayoutextendsFrameLayout {
//内容的最大值、最小值
private intminX=0;
private int maxX=0;
//滑动坐标的最大值、最小值//不加入额外距离
private intminScrollX;
private int maxScrollX;
//x方向上的额外距离
private intoverScrollX=200;
private int lastX;
VelocityTrackermVelocityTracker;
int scaledMaximumFlingVelocity;
int scaledMinimumFlingVelocity;
private OverScrollermOverScroller;
public MyFrameLayout(Context context) {
super(context);
init();
}
publicMyFrameLayout(Context context,AttributeSet attrs) {
super(context,attrs);
init();
}
publicMyFrameLayout(Context context,AttributeSet attrs, intdefStyleAttr) {
super(context,attrs,defStyleAttr);
init();
}
public voidinit() {
ViewConfiguration configuration = ViewConfiguration.get(getContext());
scaledMinimumFlingVelocity=configuration.getScaledMinimumFlingVelocity();
scaledMaximumFlingVelocity=configuration.getScaledMaximumFlingVelocity();
Log.e("shadowfaxghh","scaledMinimumFlingVelocity="+scaledMinimumFlingVelocity+" scaledMaximumFlingVelocity="+scaledMaximumFlingVelocity);
mOverScroller=newOverScroller(getContext());
}
@Override
protected voidonMeasure(intwidthMeasureSpec, intheightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
@Override
protected voidonLayout(booleanchanged, intleft, inttop, intright, intbottom) {
super.onLayout(changed,left,top,right,bottom);
//计算出内容的最大值最小值
for(inti =0;i < getChildCount();i++) {
View childView = getChildAt(i);
if (minX> childView.getLeft())
minX= childView.getLeft();
if (maxX< childView.getRight())
maxX= childView.getRight();
}
//计算出滑动坐标的最大值、最小值//只有内容大小超出自身大小时,才能进行滑动
if(minX<0)
minScrollX=minX;
else
minScrollX=0;
if (maxX> getWidth())
maxScrollX=maxX- getWidth();
else
maxScrollX=0;
}
@Override
public booleanonTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()){
caseMotionEvent.ACTION_DOWN:
// Log.e("shadowfaxghh", "ACTION_DOWN");
lastX= (int) event.getRawX();
obtaionVelocityTracker();
mVelocityTracker.addMovement(event);
if (mOverScroller!=null& !mOverScroller.isFinished()) {
mOverScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
// Log.e("shadowfaxghh", "ACTION_MOVE");
intnewX = (int) event.getRawX();
int deltaX = newX -lastX;
//这里的deltaX要取反,是根据滑动事件计算出来的//跟屏幕坐标系方向相同
overScrollBy(-deltaX,0,getScrollX(),getScrollY(),maxScrollX,100,overScrollX,0, true);
//更新坐标
lastX= newX;
obtaionVelocityTracker();
mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_UP:
caseMotionEvent.ACTION_CANCEL:
Log.e("shadowfaxghh","ACTION_UP");
lastX= (int) event.getRawX();
if (mVelocityTracker!=null) {
//计算速度
mVelocityTracker.computeCurrentVelocity(1000,scaledMaximumFlingVelocity);
//获取x方向的速度
intxVelocity = (int)mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity)<3000) {
Log.e("shadowfaxghh","springBack()");
mOverScroller.springBack(getScrollX(),0,minScrollX,maxScrollX,0,0);//只是设置了状态
}else{
Log.e("shadowfaxghh","fling()");
//原来OverScroller也重载的有带overScroll参数的fling()方法
mOverScroller.fling(getScrollX(),0,-xVelocity,0,minScrollX,maxScrollX,0,0,overScrollX,0);
}
invalidate();//需要触发重绘
recycleVelocityTracker();
}
break;
default:
break;
}
return true;
}
@Override
public voidcomputeScroll() {
// super.computeScroll();
if(mOverScroller!=null&&mOverScroller.computeScrollOffset()) {
intcurrX =mOverScroller.getCurrX();
int deltaX=currX -getScrollX();
//这里的deltaX不需要取反//是OverScroller计算出来的
overScrollBy(deltaX,0,getScrollX(),0,maxScrollX,0,overScrollX,0, false);
invalidate();//继续触发重绘
}
}
@Override
protected voidonOverScrolled(intscrollX, intscrollY, booleanclampedX, booleanclampedY) {
// super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);//父类实现为空
if(mOverScroller!=null&& !mOverScroller.isFinished()) {
intoldScrollX = getScrollX();
int oldScrollY =getScrollY();
setScrollX(scrollX);//这两个方法需要API14及以上//其实也可以使用scrollTo()
setScrollY(scrollY);
onScrollChanged(scrollX,scrollY,oldScrollX,oldScrollY);
if (clampedX) {//针对fling()的情况
mOverScroller.springBack(getScrollX(),0,minScrollX,maxScrollX,0,0);
}
} else{
scrollTo(scrollX,scrollY);
}
}
private voidobtaionVelocityTracker() {
if(mVelocityTracker==null)
mVelocityTracker= VelocityTracker.obtain();
}
private voidrecycleVelocityTracker() {
if(mVelocityTracker!=null) {
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker=null;
}
}
@Override
protected voidonScrollChanged(intl, intt, intoldl, intoldt) {
super.onScrollChanged(l,t,oldl,oldt);
}
}
<?xml version="1.0"encoding="utf-8"?>
<com.example.shadowfaxghh.demo.MyFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="always"
tools:context="com.example.shadowfaxghh.demo.MainActivity">
<TextView
android:layout_width="300px"
android:layout_height="wrap_content"
android:text="0"
android:textColor="#000000"
android:background="#FFFFFF"
android:layout_marginLeft="0px"
/>
。。。省略一些TextView。。。
<TextView
android:layout_width="300px"
android:layout_height="wrap_content"
android:text="12"
android:textColor="#000000"
android:background="#FFFFFF"
android:layout_marginLeft="3600px"
/>
</com.example.shadowfaxghh.demo.MyFrameLayout>
需要设置overScrollMode=always,因为View.overScrollBy()方法中会判断只有设置了overScrollMode才会使用overScroll。
也移除了第一个marginLeft=-300的TextView,因为View.overScrollBy()方法中会认为滑动范围是0~scrollRange。要实现-300效果可能需要重写overScrollBy()方法。