Android自定义Scrollview

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014476720/article/details/84875016

效果:

主要的几个知识点有 Scroller 、VelocityTracker

主要操作View里面的几个方法有  onMeasure、onTouchEvent 、computeScroll 、scrollTo() 、scrollBy()

Scroller

是一个专门用于处理滚动效果的工具类,直接调用ScrollTo()或者ScrollBy()的方式来移动的话是瞬间完成,用户体验感觉不是很好,然后使用Scroller就可以有个一个种平滑的效果

使用的时候也可传入自定义的插值器,常用的插值器有

AccelerateDecelerateInterpolator  在动画开始与介绍的地方速率改变比较慢,在中间的时候加速

AccelerateInterpolator   在动画开始的地方速率改变比较慢,然后开始加速

AnticipateInterpolator   开始的时候向后然后向前甩

AnticipateOvershootInterpolator   开始的时候向后然后向前甩一定值后返回最后的值

BounceInterpolator     动画结束的时候弹起

CycleInterpolator    动画循环播放特定的次数,速率改变沿着正弦曲线

DecelerateInterpolator 在动画开始的地方快然后慢

LinearInterpolator     以常量速率改变

OvershootInterpolator   向前甩一定值后再回到原来位置(快速完成动画,超出再回到结束样式)

 系统默认是 ViscousFluidInterpolator

用法:

 Scroller mScroller = new Scroller(context, new OvershootInterpolator());
相应方法解释:
mScroller.getCurrX() //获取mScroller当前水平滚动的位置 
mScroller.getCurrY() //获取mScroller当前竖直滚动的位置
mScroller.forceFinished(true); //停止一切滑动
mScroller.computeScrollOffset()//判断是否还在滑动中, true滑动中 ,false滑动完成


mScroller.startScroll  //设置滑动 ,执行这个方法,之后执行invalidate()才会触发View里面的computeScroll方法的回调的
  /**
     *开始滚动只要提供一个开始位置和结束位置,滚动将使用默认值为250毫秒的持续时间。
     * 
     * @param startX Starting horizontal scroll offset in pixels. Positive
     *        numbers will scroll the content to the left.
     * @param startY Starting vertical scroll offset in pixels. Positive numbers
     *        will scroll the content up.
     * @param dx Horizontal distance to travel. Positive numbers will scroll the
     *        content to the left.
     * @param dy Vertical distance to travel. Positive numbers will scroll the
     *        content up.
     */
    public void startScroll(int startX, int startY, int dx, int dy) {

    }

感觉有点不好理解,要先知道,滑动的开始都是左上角(0,0),最开始时的状态也就是刚刚说的(0,0),向下滑动getScrollY()的值是负数,向上滑动是正数

   mScroller.fling() //惯性滑动的,需要配合VelocityTracker.getYVelocity();来获取初速度

   /**
         * fling 方法参数注解
         *
         * startX 滚动起始点X坐标
         * startY 滚动起始点Y坐标
         * velocityX   当滑动屏幕时X方向初速度,以每秒像素数计算
         * velocityY   当滑动屏幕时Y方向初速度,以每秒像素数计算
         * minX    X方向的最小值,scroller不会滚过此点。
         * maxX    X方向的最大值,scroller不会滚过此点。
         * minY    Y方向的最小值,scroller不会滚过此点。
         * maxY    Y方向的最大值,scroller不会滚过此点。
         */

 public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY) {
 }

用法:

  mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
        float yVelocity = mVelocityTracker.getYVelocity(pointerId);
        mScroller.fling(0, getScrollY(), 0, (int) -yVelocity * 2, 0, 0, 0, measuredHeight - height);

VelocityTracker

 追踪触摸事件速率,实现flinging和其他手势的帮助类 

  1. 当开始追踪的时候,使用obtain来获取VelocityTracker类的实例  
  2. 把接收到的MotionEvent放入到addMovement(android.view.MotionEvent)中  
  3. 当要确定速度时调用computeCurrentVelocity(int),使用getXVelocity(int)和getYVelocity(int)来检测每个触摸点id的速率  

onMeasure

主要用于测量控件的大小,刚开始直接用getMeasuredHeight()获取到的高度,其实这个是当前内容可见区域高度,全部内容的滚动长度需要计算的,如下,计算好所有子控件的高度之后需要调用setMeasuredDimension更改高度

int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            //测量子控件的大小
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
            measuredHeight += (childView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin);
        }
        //调用此方法 重新更改高度
        setMeasuredDimension(getMeasuredWidth(), measuredHeight);

computeScroll

用scollTo/scollBy/startScroll方法来进行滑动时,都需要执行invalidate()才会触发其的回调,从而才会看到效果

@Override  
public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        } 
    }

下面是全部代码

public class ScrollerViewLayout extends LinearLayout {
    
    private int measuredHeight;//全部item高度

    private int height; //可见内容高度

    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;
    private float mMaxVelocity;

    public ScrollerViewLayout(Context context) {
        this(context, null, 0);
    }

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

    public ScrollerViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
        mScroller = new Scroller(context, new OvershootInterpolator());
        ViewConfiguration vc = ViewConfiguration.get(getContext());
        mMaxVelocity = vc.getScaledMaximumFlingVelocity();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measuredHeight = 0;
        //得到控件原始显示高度
        height = getMeasuredHeight();

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            //测量子控件的大小
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
            measuredHeight += (childView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin);
        }
        //调用此方法 重新更改高度
        setMeasuredDimension(getMeasuredWidth(), measuredHeight);
    }

    private float downY;
    private int pointerId;
    private boolean isSilde = false;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        handlerScroll(event);
        return true;
    }

    /**
     * 处理滚动事件
     * @param event
     */
    private void handlerScroll(MotionEvent event) {
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain();
                }
                pointerId = event.getPointerId(0);
                //停止一切滚动
                mScroller.forceFinished(true);
                mVelocityTracker.clear();
                mVelocityTracker.addMovement(event);
                downY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                int move = (int) (downY - y);
                if (isSilde || move == 0) {
                    return;
                }
                //向下滑动
                if (move < 0) {
                    scrollBy(0, move);
                    downY = y;
                }
                //向上滑动
                else if (move > 0) {
                    scrollBy(0, move);
                    downY = y;
                }

                Log.e("kawa", ">>>>move:" + move
                        + ">>>downY:" + downY
                        + ">>>y:" + y
                        + ">>>height:" + height
                        + ">>>measuredHeight:" + measuredHeight
                        + ">>>getScrollY:" + getScrollY());
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if ((measuredHeight - height) < getScrollY() || getScrollY() < 0) {
                    scrollReset();
                } else {
                    scrollFling();
                }
                break;
        }
    }

    @Override  //每次执行draw都会执行,获取当前的滚动位置进行重绘制
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            isSilde = true;
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        } else {
            isSilde = false;
        }
    }

    /**
     *  超出顶部/底部的进行复位
     */
    private void scrollReset() {
        int scrollY = getScrollY();
        if (scrollY < 0) {
            int startY = scrollY;
            int endY = -scrollY;
            mScroller.startScroll(0, startY, 0, endY);
            invalidate();
        } else {
            //向上滑动超出底部界限时才进行复位
            if ((measuredHeight - height) < getScrollY()) {
                int startY = scrollY;
                int endY = -(scrollY - (measuredHeight - height));
                mScroller.startScroll(0, startY, 0, endY);
                invalidate();
            }
        }
    }

    /**
     * 惯性滚动
     */
    private void scrollFling() {
        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
        float yVelocity = mVelocityTracker.getYVelocity(pointerId);
        /**
         * fling 方法参数注解
         *
         * startX 滚动起始点X坐标
         * startY 滚动起始点Y坐标
         * velocityX   当滑动屏幕时X方向初速度,以每秒像素数计算
         * velocityY   当滑动屏幕时Y方向初速度,以每秒像素数计算
         * minX    X方向的最小值,scroller不会滚过此点。
         * maxX    X方向的最大值,scroller不会滚过此点。
         * minY    Y方向的最小值,scroller不会滚过此点。
         * maxY    Y方向的最大值,scroller不会滚过此点。
         */
        mScroller.fling(0, getScrollY(), 0, (int) -yVelocity * 2, 0, 0, 0, measuredHeight - height);
        invalidate();
    }
}

这里有张图可以更好的理解使用Scroller (图来源网络) 

猜你喜欢

转载自blog.csdn.net/u014476720/article/details/84875016