andorid自定义view Scroller详解含Demo 记录学习

最近在看自定义view相关的内容 在Scroller 这里的确是卡了一些时间 故写下来自己的心得总结
声明:此文章demo转载于guolin大神的Scroller完全解析 详情请戳这里
废话不多说
Scroller 的作用:是一个专门用于处理滚动效果的工具类
Scroller的使用方法 (代码中也会将详细的使用步骤进行注释)
1.创建Scroller实例
2.调用startScroller()方法初始化滚动数据并刷新界面
3.重写computescroll()方法,并在内部完成平滑滚动逻辑
其中的scrollTo() ,scrollBy()这里就不多做介绍 详情可以看guolin作者的小demo

好 现在开始一点点的抠代码
效果图:
在这里插入图片描述

public class ScrollerLayout extends ViewGroup {

 private Scroller mScroller;
 private int mTouchSlop;//为拖动的最小移动像素数
public ScrollerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 第一步,创建Scroller的实例
        mScroller = new Scroller(context);
        // 获取TouchSlop值
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        Log.i("minnum","@"+mTouchSlop);
    }

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


}

这里比较简单 就是对于我们自定义view的初始化 在ScrollerLayout 中我们先创建了Scroller的实例 又拿到最小偏移量 然后再onMeasure 方法中定义了每一个子容器的宽高 这里就是父容器的宽高

    @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(l,t,r,b)
                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);
                Log.i("diff","@"+diff);
                mXLastMove = mXMove;
                Log.i("inter","@"+mXLastMove);
                // 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
                if (diff > mTouchSlop) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

每一个子容器的宽高定义好了 下一步就是确定孩子显示的位置 分别拿到每一个孩子 然后使用child Viewlayout()确定孩子显示的位置 然后再onInterceptTouchEvent()中对事件进行拦截 不让事件传递到子控件上
event.getRawX:表示的是触摸点距离屏幕左边界的距离 当移动的距离大于最小偏移量时 就开始拦截 就会将事件传递到onTouchEvent中

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:

                mXMove = event.getRawX();
                Log.i("inters","@"+mXMove+"last"+mXLastMove);
                int scrolledX = (int) (mXLastMove - mXMove);
                Log.i("imports","scrllx"+getScrollX()+"juli"+scrolledX);
                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();
                Log.i("import","scrllx"+getScrollX()+"width"+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();
        }
    }

本文的难点就来了 其中getScrollX()确实是我捉摸不透 先来看下图 假设我们的屏幕在当前1的位置上
在这里插入图片描述
那getScrollX()值是如何获取的呢
公式:当前view视图x的坐标减去初始位置X坐标
其中当前view视图x的坐标时是随着滑动当前x坐标发生改变的 初始位置x,y坐标是不变的 在我们这个demo中就是(0,0)
当我们屏幕现在在第二个图形中 当我们向右移动100 我们的视图x的坐标就会从该开始的400 变成现在的500
getScrollX() = 500-0 = 500
假设我们在第二个图形上 不管你的手指放在图形的那个位置 他的左上角x坐标就是400 同理 当我们在第1个或者第三个时他的初始getScrollX()默认就是当前view视图的x坐标

然后在onTouchEvent 的移动事件中 int scrolledX = (int) (mXLastMove - mXMove); 用上从次的值减去当前移动的值 因为scrollTo scrollBy他们移动时取反的 这点需要牢记 值为正代表向右移动
然后用当前位置和 移动的位置进行判断 是否小于左边界和大于右边界 如果条件成立 则说明已超出边界然后让其移动到左右边界的位置 不超出的话就在当前位置上进行距离的增量
在手指抬起的事件中 算出移动的位置dx 然后调用Scroller.startScroll();进行移动
重写computeScroll()拿到当前mScroller 的x,y值 一点点的移动到最后目的值

发布了46 篇原创文章 · 获赞 67 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/yuhang01/article/details/104388629