Scroller和平滑滚动

Scroller 类在滚动过程的的几个主要作用如下:
启动滚动动作;
根据ᨀ供的滚动目标位置和持续时间计算出中间的过渡位置;
判断滚动是否结束;
介入 View 或 ViewGroup 的重绘流程,从而形成滚动动画。

Scroller涉及到的方法:
public Scroller(Contextcontext)
public Scroller(Contextcontext, Interpolatorinterpolator)
public Scroller(Contextcontext, Interpolatorinterpolator, booleanflywheel)
构造方法, interpolator 指 定 插 速器,如果没有指定,默认插速器为ViscousFluidInterpolator,flywheel 参数为 true 可以ᨀ供类似“飞轮”的行为;
public final void setFriction(floatfriction)
设置一个摩擦系数,默认为 0.015f,摩擦系数决定惯性滑行的距离;
public final int getStartX()
返回起始 x 坐标值;
public final int getStartY()
返回起始 y 坐标值;
public final int getFinalX()
返回结束 x 坐标值;
public final int getFinalY()
返回结束 y 坐标值;
public final int getCurrX()
返回滚动过程中的 x 坐标值,滚动时会ᨀ供 startX(起始)和 finalX(结束),currX 根据这两个值计算而来;
public final int getCurrY()
返回滚动过程中的 y 坐标值,滚动时会ᨀ供 startY(起始)和 finalY(结束),currY 根据这两个值计算而来;
public booleancomputeScrollOffset()
计算滚动偏移量,必调方法之一。主要负责计算 currX 和 currY 两个值,其返回值为true 表示滚动尚未完成,为 false 表示滚动已结束;
public void startScroll(int startX,int startY,int dx,int dy)
public void startScroll(int startX,int startY,int dx,int dy,int duration)
启动滚动行为,startX 和 startY 表示起始位置,dx、dy 表示要滚动的 x、y 方向的距离,duration 表示持续时间,默认时间为 250 毫秒;
public final boolean isFinished()
判断滚动是否已结束,返回 true 表示已结束;
public final void forceFinished(booleanfinished)
强制结束滚动,currX、currY 即为当前坐标;
public void abortAnimation()
与 forceFinished 功用类似,停止滚动,但 currX、currY 设置为终点坐标;
public void extendDuration(int extend)
延长滚动时间;
public int timePassed()
返回滚动已耗费的时间,单位为毫秒;
public void setFinalX(int newX)
设置终止位置的 x 坐标,可能需要调用 extendDuration()延长或缩短动画时间;
public void setFinalY(int newY)
设置终止位置的 y 坐标,可能需要调用 extendDuration()延长或缩短动画时间。

上面的方法中,常用的主要有 startScroll()、computeScrollOffset()、getCurrX()、getCurrY()和

abortAnimation()等几个方法。


ScrollerViewGroup:

public class ScrollerViewGroup extends ViewGroup{
    private Scroller scroller;
    private Button customButtom;
    private LayoutParams layoutParams;

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

    public ScrollerViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);

        init(context);
    }

    public ScrollerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void init(Context context){
        scroller = new Scroller(context);//实例化Scroller

        customButtom = new Button(context);
        layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        customButtom.setText("自定义Button");
        addView(customButtom,layoutParams);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //为子View设置位置
        customButtom.layout(100,100,customButtom.getMeasuredWidth()+100,customButtom.getMeasuredHeight()+100);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getSize(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //MeasureSpec封装了父布局传递给子布局的布局要求
        //下面就是父布局传递给ScrollerViewGroup的布局要求
        if(widthMode==MeasureSpec.AT_MOST || heightMode==MeasureSpec.AT_MOST){
            //ScrollerViewGroup的宽或者高是不能设置为包裹
            throw new IllegalStateException("The ViewGroup must is match_parent or gudingzhi");
        }

        //测量子View宽高
        measureChildren(widthMeasureSpec,heightMeasureSpec);
        //测量自己宽高
        setMeasuredDimension(widthSize,heightSize);
    }

    @Override
    public void computeScroll() {
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),scroller.getCurrY());
            postInvalidate();

            scroller.getCurrX();
            scroller.getStartX();
            scroller.getFinalX();
            Log.i("log","getCurrX:"+ scroller.getCurrX()+",getStartX"+scroller.getStartX()+",getFinalX"+scroller.getFinalX());
        }
    }

    public void start(){
        scroller.startScroll(getScrollX(),getScrollY(),-200,0,10000);
        postInvalidate();//重绘将执行computeScroll
    }

    public void abort(){
        scroller.abortAnimation();//停止滚动动画
    }
}

activity_main:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.user.myapplication4.ScrollerViewGroup
        android:layout_width="match_parent"
        android:id="@+id/scroll_layout"
        android:layout_height="100dp"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="10dp">
        <Button
            android:id="@+id/start"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="开始滚动"/>
        <Button
            android:id="@+id/abort"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@android:color/holo_blue_bright"
            android:text="停止滚动"/>
    </LinearLayout>
</LinearLayout>

MainActivity:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final ScrollerViewGroup scroll_layout = (ScrollerViewGroup) findViewById(R.id.scroll_layout);
        Button start = (Button) findViewById(R.id.start);
        Button abort = (Button) findViewById(R.id.abort);

        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                scroll_layout.start();
            }
        });
        abort.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                scroll_layout.abort();
            }
        });
    }
}
平滑滚动的基本工作流程:
1) 调用 scroller 的 startScroll()方法定义滚动的起始位置和滚动的距离;
2) 通过 invalidate()或 postInvalidate()方法刷新,调用 draw(Canvas)方法重绘组件;
3) 调用 computeScroll()计算下一个位置的坐标;
4) 再次调用 invalidate()或 postInvalidate()方法刷新重绘;
5) 判断 computeScroll()方法的返回值,如果为 false 表示结束滚动,为 true 表示继续滚动。
上面的步骤其实构建了一个方法调用循环:1) ->2) ->3) ->4) ->5) ->3) ->4) ->5)……,3) ->4) ->5)就是一个循环,该循环用于不断计算下一个位置,并通过重绘移动到该位置,这样就产生了动画效果。


我们首先定义了一个 Scroller 类型的成员变量 scroller,并在构造方法中进行了实例化。重点是重写了 ViewGroup 的 computeScroll()方法,该方法的默认实现是空方法,在绘制 View 时调用。在 computeScroll()方法中,调用 scroller.computeScrollOffset()方法计算下一个位置的坐标值(currX,currY),再通过 this.scrollTo(scroller.getCurrX(), scroller.getCurrY())语句移动到该坐标位置,特别要注意的是一定要调用 invadate()或 postInvalidate()方法重绘,一旦 computeScrollOffset()方法返回false 表示滚动结束,停止重绘。


另外,我们还定义了两个用来与外部交互的方法:start()和 abort()。start()方法用于启动滚动动作,执行了 scroller.startScroll(this.getScrollX(), this.getScrollY(), - 900, 0,10000)语句,其中参数this.getScrollX()和 this.getScrollY()是容器内容的初始位置,x 方向向右移动 900 个单位距离(为负才表示向右),y 方向不变,也就是水平向右移动,为了更好的查看动画过程,将滚动持续时间设为 10 秒。和上面一样,就算调用了 startScroll()方法,也需要调用 invadate()或 postInvalidate()方
法进行重绘。在 abort()方法中调用了 scroller.abortAnimation()方法,用来停止滚动。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值