通过android.widget.Scroller制作齿轮滚动的

2017-10-22  本文已影响248人  生活简单些

想要做成类似如下效果:

scroller-sample.gif

实现自定义view的代码如下:

public class ScrollerLayout extends ViewGroup {
    private Scroller mScroller;

    /**
     * 触摸移动距离超过此值才认为是有效滑动
     */
    private int mTouchSlop;

    /**
     * 移动前第一次按下会记录下
     */
    private float mXDown;

    /**
     * 每一次移动都给它赋值
     */
    private float mXMove;

    /**
     * 按下以及移动过程中都会给它赋值
     */
    private float mXLastMove;

    /**
     * 界面可滚动的左边界, 小于它则重置到此边界
     */
    private int mLeftBorder;

    /**
     * 界面可滚动的右边界,大于它则重置到此边界
     */
    private int mRightBorder;

    public ScrollerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
        mTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(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());
            }

            // 初始化左右边界值
            mLeftBorder = getChildAt(0).getLeft();
            mRightBorder = getChildAt(getChildCount() - 1).getRight();
        }
    }

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

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mXMove = event.getRawX();

                // 因为view向右滑scrollX越小,所以计算滑动了多少距离反着减
                int scrolledX = (int) (mXLastMove - mXMove);

                // 滑动左边过界进行重置
                if (getScrollX() + scrolledX < mLeftBorder) {
                    scrollTo(mLeftBorder, 0);
                    return true;
                }

                // 滑动右边过界进行重置
                // 因为即便滑到最右边也会有最后一个屏幕宽度不能滑动,所以下面要加上getWidth()
                if (getScrollX() + getWidth() + scrolledX > mRightBorder) {
                    scrollTo(mRightBorder - getWidth(), 0);
                    return true;
                }
                scrollBy(scrolledX, 0);
                mXLastMove = mXMove;
                break;
            case MotionEvent.ACTION_UP:
                int targetIndex;
                // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面
                boolean slideLeft = event.getRawX() < mXDown;
                if (slideLeft) {
                    // 原则是左滑一整屏才会进入下一个getWidth()的倍数,加上0.9意味着右滑到达getWidth()的0.1倍就进入下一个倍数了
                    targetIndex = (getScrollX() + getWidth() * 9 / 10) / getWidth();
                } else {
                    // 原则上只要右滑一点getScrollX()的长度就不够getWidth()的倍数了,如果未超过0.1的宽度则保持当前倍数
                    targetIndex = (getScrollX() + getWidth() / 10) / getWidth();
                }

                int dx = targetIndex * getWidth() - getScrollX();
                // 调用startScroll()方法来初始化滚动数据并刷新界面, 最后参数还可以设定动画时间
                mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

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

用法也简单:

<?xml version="1.0" encoding="utf-8"?>
<com.apptest.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 1 child view"
        android:background="@android:color/background_light"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is 2 child view"
        android:background="@android:color/holo_red_light"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is 3 child view"
        android:background="@android:color/holo_blue_light"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is 4 child view"
        android:background="@android:color/holo_blue_dark"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is 5 child view"
        android:background="@android:color/holo_orange_light"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is 6 child view"
        android:background="@android:color/holo_red_dark"/>

</com.apptest.ScrollerLayout>
上一篇 下一篇

猜你喜欢

热点阅读