Scroller的用法和一些理解

本文介绍Scroller类的基本用法,通过创建一个简易版的ViewPager来演示如何利用Scroller实现平滑滚动效果。文章详细讲解了scrollTo和scrollBy的区别,并通过实例展示了如何结合Scroller完成流畅的触摸滚动。

Scroller是一个专门用于处理滚动效果的工具类,可能在大多数情况下,我们直接使用Scroller的场景并不多,但是很多大家所熟知的控件在内部都是使用Scroller来实现的,如ViewPagerListView等。而如果能够把Scroller的用法熟练掌握的话,我们自己也可以轻松实现出类似于ViewPager这样的功能。

先撇开Scroller类不谈,其实任何一个控件都是可以滚动的,因为在View类当中有scrollTo()和scrollBy()这两个方法

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);
            }
        });

没错,代码就是这么简单。当点击了scrollTo按钮时,我们调用了LinearLayout的scrollTo()方法,当点击了scrollBy按钮时,调用了LinearLayout的scrollBy()方法。那有的朋友可能会问了,为什么都是调用的LinearLayout中的scroll方法?这里一定要注意,不管是scrollTo()还是scrollBy()方法,滚动的都是该View内部的内容,而LinearLayout中的内容就是我们的两个Button,如果你直接调用button的scroll方法的话,那结果一定不是你想看到的。

注意scrollTo和scrollBy都是滚动的view内部的内容,切记.

scrollTo()方法是让View相对于初始的位置滚动某段距离,由于View的初始位置是不变的,因此不管我们点击多少次scrollTo按钮滚动到的都将是同一个位置。而scrollBy()方法则是让View相对于当前的位置滚动某段距离,那每当我们点击一次scrollBy按钮,View的当前位置都进行了变动,因此不停点击会一直向右下方移动。
 

目前使用这两个方法完成的滚动效果是跳跃式的,没有任何平滑滚动的效果。没错,只靠scrollTo()和scrollBy()这两个方法是很难完成ViewPager这样的效果的,因此我们还需要借助另外一个关键性的工具,也就我们今天的主角Scroller

Scroller的基本用法其实还是比较简单的,主要可以分为以下几个步骤:

创建Scroller的实例
调用startScroll()方法来初始化滚动数据并刷新界面
重写computeScroll()方法,并在其内部完成平滑滚动的逻辑

那么下面我们就按照上述的步骤,通过一个模仿ViewPager的简易例子来学习和理解一下Scroller的用法。
新建一个ScrollerLayout并让它继承自ViewGroup来作为我们的简易ViewPager布局,代码如下所示:

package com.example.viewpager2test.ScrollerTest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

public class ScrollerLayout extends ViewGroup {
    private static final String TAG = "ScrollerLayout";

    private Scroller mScroller;

    private int leftBorder;

    private int rightBorder;

    private int mTouchSlop;

    private float mXDown;

    private float mXMove;

    private float mXLastMove;

    public ScrollerLayout(Context context) {
        this(context, null);
    }

    public ScrollerLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScrollerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScroller = new Scroller(context);
        ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledPagingTouchSlop();
        Log.d(TAG, "ScrollerLayout: mTouchSlop:"+mTouchSlop);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            int childCount = this.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = this.getChildAt(i);
                childView.layout(i * childView.getMeasuredWidth(), 0
                        , (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
            }
            leftBorder = getChildAt(0).getLeft();
            rightBorder = getChildAt(getChildCount() - 1).getRight();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = this.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = this.getChildAt(i);
            this.measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    /**
     * 事件拦截,当手指移动超过touchSlop,就拦截事件,不让他传给子控件,
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mXDown = ev.getRawX();
                Log.d(TAG, "onInterceptTouchEvent: mXDown:"+mXDown);
                mXLastMove = mXDown;
                break;
            case MotionEvent.ACTION_MOVE:
                mXMove = ev.getRawX();
                Log.d(TAG, "onInterceptTouchEvent: mXMove:"+mXMove);
                float diff = Math.abs(mXMove - mXDown);
                mXLastMove = mXMove;
                if (diff > mTouchSlop) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 精华
     * 用scrollto和scrollby实现丝滑滚动,就像smoothscrollTo和smoothscrollBy
     * 实现原理:不断的调用scrollBy(dx),dx的值只要足够小,调用的次数足够多,就能看起来很丝滑
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                mXMove = event.getRawX();
                Log.d(TAG, "onTouchEvent: mXMove:"+mXMove);
                int scrolledX = (int) ( mXLastMove-mXMove );
                Log.d(TAG, "onTouchEvent: scrolledX:"+scrolledX);
                if (this.getScrollX() + scrolledX < leftBorder) {
                    scrollTo(leftBorder, 0);
                    Log.d(TAG, "onTouchEvent: 已到左边界");
                    return true;
                } else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
                    scrollTo(rightBorder - getWidth(), 0);
                    Log.d(TAG, "onTouchEvent: 已到右边界");
                    return true;
                }
                scrollBy(scrolledX, 0);

                mXLastMove = mXMove;
                Log.d(TAG, "onTouchEvent: mXLastMove:"+mXLastMove);
                break;
            case MotionEvent.ACTION_UP:
                int targetIndex = (this.getScrollX() + getWidth() / 2) / getWidth();
                int dx = targetIndex * getWidth() - getScrollX();
                mScroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
        }
        return super.onTouchEvent(event);
    }

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

    }
}


 

PowerBI系列课程之内置视觉对象常用自定义视觉对象专题 内置视觉对象Stacked bar chart  堆积条形图Stacked column chart 堆积柱形图 - X轴按类别显示Clustered bar chart  簇状条形图Clustered column chart  簇状柱形图100% Stacked bar chart 百分比堆积条形图100% Stacked column chart 百分比堆积柱形图Line Chart  折线图-预测功能Area Chart  面积图Stacked Area Chart  堆积面积图Line and stacked column chart 折线堆积柱形图-双Y轴Line and clustered column chart 折线簇状柱形图Ribbon Chart  丝带图Waterfall Chart  瀑布图Funnel  漏斗图Scatter chart  散点图Pie Chart  饼图 Donut Chart 环形图TreeMap 树状图Map  地图 Filled Map 着色地图Shape Map 形状地图Gauge 仪表Card 卡片图Multi-row card 多行卡片KPI  Table 表格-条件样式Matrix 矩阵详解  Key influencers  关键影响者Decomposition tree 分解树Q&A 问答2021.6月增加分页表格视觉对象自定义视觉对象视觉对象使用建议下载排名前20,免费实用的视觉对象 AllDemo pbix介绍 Pareto 帕累托图DrillDown Donut  可钻取饼图Word Cloud   文字云Gantt  甘特图Infographic Designer  信息柱状图Timeline Slicer  时间轴切片器Chiclet Slicer 图片切片器Text Filter 文本筛选器HierarchySlicer  层级切片器Pulse Chart脉动图Power KPI Matrix  KPI矩阵Animated Bar Chart Race 动态条形图Advance Card  高级卡片Sankey Chart  桑基图Radar Chart   雷达图Dial Gauge  码表Waffle 华夫占比图Quadrant 象限图 Tornado Chart  龙卷风图Histogram Chart  直方图 Box and Whisker chart 盒线图Sunburst  阳光图Chord Chart 弦图Bullet Chart  子弹图HTML Content  html解析视觉对象  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值