前言

Android 中存在很多Scroller,实际上其自身和View的关系并不大,由于很多时分,自定义View你都不会用到Scroller,那么Scroller起到什么效果呢?

关于Scroller

Scroller究竟起到什么效果,以及为什么要运用Scroller?

Scroller的效果

不管从构造办法仍是其他办法,以及 Scroller 的属性可知,其并不会持有 View,驱动ViewGroup 滑动。

Scroller 只是个核算器,供给插值核算,让翻滚进程具有动画属性,但它并不是View有必要要有的,也不能驱动View翻滚,真正效果是为了View滑动作参阅,而参阅办法一般是在View#computeScroll()办法中进行,而View#computeScroll()办法的调用仍然是经过View的invalidate -> draw 办法来驱动。

Scroller 核算机制

Scroller核算距离是经过Scroller#computeScrollOffset办法来进行的,而computeScrollOffset办法的调用一般是在View#computeScroll()中进行

如何保证核算结果接连

如何让 Scroller 的核算也是接连的?

这个就问到了什么时分调用computeScroll 了,如上所说computeScroll 调用 Scroller#computeScrollOffset(),只要computeScroll 调用接连,Scroller 也会接连,实质上computeScroll 的接连性又 invalidate 办法操控,scrollTo,scrollBy 都会调用 invalidate,而 invalidate 回去触发 draw, 从而computeScroll 被接连调用,综上,Scroller 也会被接连调用,除非 invalidate 中止调用。

这点很像补间动画,在draw的时分触发,下面代码中,scrollTo中会调用到invalidate办法

Android Scroller 是如何运转的

此外,其内部也维护了时钟

int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

Scroller 经典事例

经过一个 SlidePanel 的例子,咱们来深入的了解一下 留意:在移动平台中,要清晰知道 “滑动” 与 “翻滚” 的不同,具体来说,滑动和翻滚的方向总是相反的。

事例简介

咱们利用Scroller来完成一个SlidingPanel,能够完成左边和右侧都能侧滑

publicclassSlidingPanelextendsRelativeLayout{}

当然,咱们需求定义三个View,并且加入到布局中

privateFrameLayoutleftMenu; //左边菜单
privateFrameLayoutmiddleMenu; //中心内容
privateFrameLayoutrightMenu; //右侧菜单
// 省掉一些代码
addView(leftMenu);
addView(middleMenu);
addView(rightMenu);

接下来咱们创立一个Scroller,使其能够匀减速运动

mScroller=newScroller(context,newDecelerateInterpolator());

咱们按正常方式丈量和布局,但是左边菜单和右侧菜单不能掩盖整个屏幕,这里给其宽度为 0.8f * screenWidth,布局按从左到右布局即可

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    middleMenu.measure(widthMeasureSpec, heightMeasureSpec);
    middleMask.measure(widthMeasureSpec, heightMeasureSpec);
    int realWidth = MeasureSpec.getSize(widthMeasureSpec);
    int tempWidthMeasure = MeasureSpec.makeMeasureSpec(
            (int) (realWidth * 0.8f), MeasureSpec.EXACTLY);
    leftMenu.measure(tempWidthMeasure, heightMeasureSpec);
    rightMenu.measure(tempWidthMeasure, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    middleMenu.layout(l, t, r, b);
    middleMask.layout(l, t, r, b);
    leftMenu.layout(l - leftMenu.getMeasuredWidth(), t, r, b);
    rightMenu.layout(
            l + middleMenu.getMeasuredWidth(),
            t,
            l + middleMenu.getMeasuredWidth()
                    + rightMenu.getMeasuredWidth(), b);
}

事情处理

在Android中,一般滑动都是由事情驱动的,这里咱们要记住需求在dispatchTouchEvent中处理事情,由于滑动进程中事情或许被拦截,因而在dispatchTouchEvent处理是十分必要的。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (!isSlideCompete) {
        handleSlideEvent(ev);
        return true;
    }
    if (isHorizontalScroll) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_MOVE:
                int curScrollX = getScrollX();
                int dis_x = (int) (ev.getX() - point.x);
                //滑动方向和翻滚翻滚条方向相反,因而dis_x有必要取负值
                int expectX = -dis_x + curScrollX;
                if (dis_x > 0) {
                    Log.d("I", "Right-Slide,Left-Scroll");//向右滑动,向左翻滚
                } else {
                    Log.d("I", "Left-Slide,Right-Scroll");
                }
                Log.e("I", "ScrollX=" + curScrollX + " , X=" + ev.getX() + " , dis_x=" + dis_x);
                //规则expectX的改变范围
                int finalX = Math.max(-leftMenu.getMeasuredWidth(), Math.min(expectX, rightMenu.getMeasuredWidth()));
                scrollTo(finalX, 0);
                point.x = (int) ev.getX();//更新,保证滑动滑润
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                curScrollX = getScrollX();
                if (Math.abs(curScrollX) > leftMenu.getMeasuredWidth() >> 1) {
                    if (curScrollX < 0) {
                        mScroller.startScroll(curScrollX, 0,
                                -leftMenu.getMeasuredWidth() - curScrollX, 0,
                                200);
                    } else {
                        mScroller.startScroll(curScrollX, 0,
                                leftMenu.getMeasuredWidth() - curScrollX, 0,
                                200);
                    }
                } else {
                    mScroller.startScroll(curScrollX, 0, -curScrollX, 0, 200);
                }
                invalidate();
                isHorizontalScroll = false;
                isSlideCompete = false;
                break;
        }
    } else {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_UP:
                isHorizontalScroll = false;
                isSlideCompete = false;
                break;
            default:
                break;
        }
    }
    return super.dispatchTouchEvent(ev);
}

从上面的代码中咱们能够看到,Scroller一般运用在事情CANCEL或者UP时,这也是Scroller一般的用法,用于滑动速度丈量和差值核算

一起咱们不要忘了,mScroller.startScroll()调用之后,需求触发View#draw办法,当然能够运用invalidate

if (Math.abs(curScrollX) > leftMenu.getMeasuredWidth() >> 1) {
    if (curScrollX < 0) {
        mScroller.startScroll(curScrollX, 0,
                -leftMenu.getMeasuredWidth() - curScrollX, 0,
                200);
    } else {
        mScroller.startScroll(curScrollX, 0,
                leftMenu.getMeasuredWidth() - curScrollX, 0,
                200);
    }
} else {
    mScroller.startScroll(curScrollX, 0, -curScrollX, 0, 200);
}
invalidate();

循环调用

咱们最初说过,Scroller不会驱动View的滑动,一切的滑动都需求经过View自身来驱动,而在Vsync信号执行期间,咱们需求经过computeScroll来获取Scroller滑动的参阅值。

/**
 * 经过invalidate操作,此办法经过draw办法调用
 */
@Override
public void computeScroll() {
    super.computeScroll();
    if (!mScroller.computeScrollOffset()) {
        //核算currX,currY,并检测是否已完成“翻滚”
        return;
    }
    int tempX = mScroller.getCurrX();
    scrollTo(tempX, 0); //会重复调用invalidate
}

经过上述代码咱们就完成了策划菜单,这里就不贴图了。

完好代码

publicclassSlidingPanelextendsRelativeLayout{
privateContextcontext;
privateFrameLayoutleftMenu;
privateFrameLayoutmiddleMenu;
privateFrameLayoutrightMenu;
privateFrameLayoutmiddleMask;
privateScrollermScroller;
publicfinalintLEFT_ID=0xaabbcc;
publicfinalintMIDEELE_ID=0xaaccbb;
publicfinalintRIGHT_ID=0xccbbaa;
privatebooleanisSlideCompete;
privatebooleanisHorizontalScroll;
privatePointpoint=newPoint();
privatestaticfinalintSLIDE_SLOP=20;
publicSlidingPanel(Contextcontext){
super(context);
initView(context);
}
publicSlidingPanel(Contextcontext,AttributeSetattrs){
super(context,attrs);
initView(context);
}
privatevoidinitView(Contextcontext){
this.context=context;
mScroller=newScroller(context,newDecelerateInterpolator());
leftMenu=newFrameLayout(context);
middleMenu=newFrameLayout(context);
rightMenu=newFrameLayout(context);
middleMask=newFrameLayout(context);
leftMenu.setBackgroundColor(Color.RED);
middleMenu.setBackgroundColor(Color.GREEN);
rightMenu.setBackgroundColor(Color.RED);
middleMask.setBackgroundColor(0x88000000);
addView(leftMenu);
addView(middleMenu);
addView(rightMenu);
addView(middleMask);
middleMask.setAlpha(0);
}
publicfloatonMiddleMask(){
returnmiddleMask.getAlpha();
}

@Override
publicvoidscrollTo(intx,inty){
super.scrollTo(x,y);
onMiddleMask();
//Log.e("getScrollX","getScrollX="+getScrollX());//能够是负值
intcurX=Math.abs(getScrollX());
floatscale=curX/(float)leftMenu.getMeasuredWidth();
middleMask.setAlpha(scale);

}
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
middleMenu.measure(widthMeasureSpec,heightMeasureSpec);
middleMask.measure(widthMeasureSpec,heightMeasureSpec);
intrealWidth=MeasureSpec.getSize(widthMeasureSpec);
inttempWidthMeasure=MeasureSpec.makeMeasureSpec(
(int)(realWidth*0.8f),MeasureSpec.EXACTLY);
leftMenu.measure(tempWidthMeasure,heightMeasureSpec);
rightMenu.measure(tempWidthMeasure,heightMeasureSpec);
}
@Override
protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
super.onLayout(changed,l,t,r,b);
middleMenu.layout(l,t,r,b);
middleMask.layout(l,t,r,b);
leftMenu.layout(l-leftMenu.getMeasuredWidth(),t,r,b);
rightMenu.layout(
l+middleMenu.getMeasuredWidth(),
t,
l+middleMenu.getMeasuredWidth()
+rightMenu.getMeasuredWidth(),b);
}
@Override
publicbooleandispatchTouchEvent(MotionEventev){
if(!isSlideCompete){
handleSlideEvent(ev);
returntrue;
}
if(isHorizontalScroll){
switch(ev.getActionMasked()){
caseMotionEvent.ACTION_MOVE:
intcurScrollX=getScrollX();
intdis_x=(int)(ev.getX()-point.x);
//滑动方向和翻滚翻滚条方向相反,因而dis_x有必要取负值
intexpectX=-dis_x+curScrollX;
if(dis_x>0)
{
Log.d("I","Right-Slide,Left-Scroll");//向右滑动,向左翻滚
}else{
Log.d("I","Left-Slide,Right-Scroll");
}
Log.e("I","ScrollX="+curScrollX+",X="+ev.getX()+",dis_x="+dis_x);
//规则expectX的改变范围
intfinalX=Math.max(-leftMenu.getMeasuredWidth(),Math.min(expectX,rightMenu.getMeasuredWidth()));
scrollTo(finalX,0);
point.x=(int)ev.getX();//更新,保证滑动滑润
break;
caseMotionEvent.ACTION_UP:
caseMotionEvent.ACTION_CANCEL:
curScrollX=getScrollX();
if(Math.abs(curScrollX)>leftMenu.getMeasuredWidth()>>1){
if(curScrollX<0){
mScroller.startScroll(curScrollX,0,
-leftMenu.getMeasuredWidth()-curScrollX,0,
200);
}else{
mScroller.startScroll(curScrollX,0,
leftMenu.getMeasuredWidth()-curScrollX,0,
200);
}
}else{
mScroller.startScroll(curScrollX,0,-curScrollX,0,200);
}
invalidate();
isHorizontalScroll=false;
isSlideCompete=false;
break;
}
}else{
switch(ev.getActionMasked()){
caseMotionEvent.ACTION_UP:
isHorizontalScroll=false;
isSlideCompete=false;
break;
default:
break;
}
}
returnsuper.dispatchTouchEvent(ev);
}
/**
*经过invalidate操作,此办法经过draw办法调用
*/
@Override
publicvoidcomputeScroll(){
super.computeScroll();
if(!mScroller.computeScrollOffset()){
//核算currX,currY,并检测是否已完成“翻滚”
return;
}
inttempX=mScroller.getCurrX();
scrollTo(tempX,0);//会重复调用invalidate
}
privatevoidhandleSlideEvent(MotionEventev){
switch(ev.getAction()&MotionEvent.ACTION_MASK){
caseMotionEvent.ACTION_DOWN:
point.x=(int)ev.getX();
point.y=(int)ev.getY();
super.dispatchTouchEvent(ev);
break;
caseMotionEvent.ACTION_MOVE:
intdX=Math.abs((int)ev.getX()-point.x);
intdY=Math.abs((int)ev.getY()-point.y);
if(dX>=SLIDE_SLOP&&dX>dY){//左右滑动
isHorizontalScroll=true;
isSlideCompete=true;
point.x=(int)ev.getX();
point.y=(int)ev.getY();
}elseif(dY>=SLIDE_SLOP&&dY>dX){//上下滑动
isHorizontalScroll=false;
isSlideCompete=true;
point.x=(int)ev.getX();
point.y=(int)ev.getY();
}
break;
caseMotionEvent.ACTION_UP:
caseMotionEvent.ACTION_OUTSIDE:
caseMotionEvent.ACTION_CANCEL:
super.dispatchTouchEvent(ev);
isHorizontalScroll=false;
isSlideCompete=false;
break;
}
}
}

弥补点

在Android 中,Scroller并没有一致的用法,也没有一致的标准,实际上Scroller只是是一个一般的类,但是Scroller 也未必必定需求依照现有形式运转。咱们以ViewFlinger为例,实际上它自身就依照自己形式运转,总体上来说,不管是Scroller仍是ViewFlinger都没有一致的标准。

总结

本篇到这里就完毕了,经过本篇咱们能够了解到Scroller与View的关系,其自身并不是完全依赖的,Scroller也不存在任何标准,只是供给运动差值核算罢了。