Android Scroller的简单使用

1 前言

Scroller是一个专门用于处理滚动效果的工具类,一般在我们自定义View时,要实现滚动或者滑动效果,除了使用动画之外,我们往往会使用Scroller来帮我们完成。在android中如ViewPager、ListView等,内部也是使用了Scroller来实现的。

2 View的Scroll

在介绍Scroller之前,我们知道,在View中自带scroll属性,那就是View中有两个方法,可以完成view的滚动。他们是:

void    scrollBy(int x, int y)//滚动x,y
Move the scrolled position of your view. 

void    scrollTo(int x, int y) //滚动到x y
Set the scrolled position of your view. 

需要说明的的两点
1 根据android的坐标体系,当x为负时,向右滚动,为正时向左滚动,y为负向下滚动,y为正,向上滚动
2 以上两个滚动只是滚动的View的内容,并没有滚动View本身,需要滚动view本身时,可以考虑布局属性LayoutParams及属性动画。
因此,针对这一点:我们队某个View做scroll往往并无太大的意义,但是对于ViewGroup来说,其内容往往就是某个子View,对ViewGroup做scroll就有意义!
3 以上滚动是瞬间完成,并没有过渡过程。

另外,以下两个API也是需要注意的,分别返回滚动时的x,y

final int getScrollX() //返回滚动时的left位置 x
Return the scrolled left position of this view. 

final int getScrollY()  //返回滚动时的top 位置 y
Return the scrolled top position of this view. 

下面来看一个View使用scroll的例子

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.guolin.scrollertest.MainActivity">

    <Button
        android:id="@+id/scroll_to_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="scrollTo"/>

    <Button
        android:id="@+id/scroll_by_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="scrollBy"/>

</LinearLayout>

外层我们使用了一个LinearLayout,然后在里面包含了两个按钮,一个用于触发scrollTo逻辑,一个用于触发scrollBy逻辑。
接着修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

    private LinearLayout layout;

    private Button scrollToBtn;

    private Button scrollByBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layout = (LinearLayout) findViewById(R.id.layout);
        scrollToBtn = (Button) findViewById(R.id.scroll_to_btn);
        scrollByBtn = (Button) findViewById(R.id.scroll_by_btn);
        scrollToBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                layout.scrollTo(-60, -100);
            }
        });
        scrollByBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                layout.scrollBy(-60, -100);
            }
        });
    }
}

效果如下:
这里写图片描述

3 Scroller API

一般来说,我们使用Scroller主要涉及到以下几个api

final int   getCurrX()  //获取当前的x偏移
Returns the current X offset in the scroll.

final int   getCurrY()  //获取当前的y偏移
Returns the current Y offset in the scroll.

final int   getDuration()   //获取持续时间
Returns how long the scroll event will take, in milliseconds. 

//开始滚动
void    startScroll(int startX, int startY, int dx, int dy, int duration)
Start scrolling by providing a starting point, the distance to travel, and the duration of the scroll.

//开始滚动
void    startScroll(int startX, int startY, int dx, int dy)
Start scrolling by providing a starting point and the distance to travel. 

//判断滚动是否完成,true表示没有完成
boolean     computeScrollOffset()
Call this when you want to know the new location. 

4 Scroller使用

Scroller的基本用法分为以下几个步骤
1 创建Scroller的实例
2 调用Scroller#startScroll()方法来初始化滚动数据并调用invalidate()刷新界面
3 重写View#computeScroll()方法,并在其内部完成平滑滚动的逻辑,在滚动时,会不断的调用这个方法。由于默认的View的computeScroll()方法为空,我们需要自己实现,不过一般代码也固定

例子如下:
我们自定义一个ViewGroup,仿照ViewPager的类似效果,水平布局

/**
 * Created by qiyei2015 on 17/6/19.
 */
public class ScrollerLayout extends ViewGroup {

    /**
     * 用于完成滚动操作的实例
     */
    private Scroller mScroller;

    /**
     * 判定为拖动的最小移动像素数
     */
    private int mTouchSlop;

    /**
     * 手机按下时的屏幕坐标
     */
    private float mXDown;

    /**
     * 手机当时所处的屏幕坐标
     */
    private float mXMove;

    /**
     * 上次触发ACTION_MOVE事件时的屏幕坐标
     */
    private float mXLastMove;

    /**
     * 界面可滚动的左边界
     */
    private int leftBorder;

    /**
     * 界面可滚动的右边界
     */
    private int rightBorder;

    public ScrollerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 第一步,创建Scroller的实例
        mScroller = new Scroller(context);
        ViewConfiguration configuration = ViewConfiguration.get(context);
        // 获取TouchSlop值
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            // 为ScrollerLayout中的每一个子控件测量大小
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                // 为ScrollerLayout中的每一个子控件在水平方向上进行布局
                childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
            }
            // 初始化左右边界值
            leftBorder = getChildAt(0).getLeft();
            rightBorder = getChildAt(getChildCount() - 1).getRight();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mXDown = ev.getRawX();
                mXLastMove = mXDown;
                break;
            case MotionEvent.ACTION_MOVE:
                mXMove = ev.getRawX();
                float diff = Math.abs(mXMove - mXDown);
                mXLastMove = mXMove;
                // 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
                if (diff > mTouchSlop) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mXMove = event.getRawX();
                int scrolledX = (int) (mXLastMove - mXMove);
                if (getScrollX() + scrolledX < leftBorder) {
                    scrollTo(leftBorder, 0);
                    return true;
                } else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
                    scrollTo(rightBorder - getWidth(), 0);
                    return true;
                }
                scrollBy(scrolledX, 0);
                mXLastMove = mXMove;
                break;
            case MotionEvent.ACTION_UP:
                // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面
                int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
                int dx = targetIndex * getWidth() - getScrollX();
                // 第二步,调用startScroll()方法来初始化滚动数据并刷新界面
                mScroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        // 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }
}

注意:在重写computeScroll()时,当中的代码往往是固定的,就是判断滚动有没有完成,没有完成就调用View的scrollTo()滚动到指定位置,然后invalidate()刷新界面。

<?xml version="1.0" encoding="utf-8"?>
<com.example.scrollertest.ScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is first child view"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is second child view"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is third child view"/>

</com.example.scrollertest.ScrollerLayout>

可以看到,这里我们在ScrollerLayout中放置了三个按钮用来进行测试,其实这里不仅可以放置按钮,放置任何控件都是没问题的。

效果如下:
这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值