Android自定义实现ScrollView---熟练掌握Scroller

Android自定义实现ScrollView---熟练掌握Scroller_第1张图片
一.实现的功能
1.ScrollView的滚动效果
2.边界处理及回弹
3.事件处理,防止子view消费。
二.基本知识点
1.Scroller的方法
/**填写初始的坐标和要滚动的距离,dx为正,则向左,dx为负,那么则向右移动。最后一个参数是动画执行的时间默认是250毫秒。
当在computeScroll()中调用computeScrollOffset时候,其实是将在startScroll()中传递的dx或是dy按照调用的时间
差按照250毫秒进行平分。然后通过scrollTo()进行平移。其中getCurrX()和getCurrY()就是获取平分的距离。
*/

	scroller.startScroll(int startX, int startY, int dx, int dy, int duration) ;
	scroller.computeScrollOffset()//如果返回true,那么证明动画还没执行完

/** velocityX 当滑动屏幕时X方向初速度,以每秒像素数计算
velocityY 当滑动屏幕时Y方向初速度,以每秒像素数计算
**/

	scroller.fling(int startX, int startY, int velocityX, int velocityY,
                 int minX, int maxX, int minY, int maxY);  
	scroller.forceFinished(boolean finished);强制停止
	scroller.getCurrVelocity();//        获取当前的速度
	scroller.getCurrX();//这是获得移动距离除以默认时间后的平均距离,因此使用Scroller时候有一个平滑的滚动效果
	scroller.getCurrY();//这是获得移动距离除以默认时间后的平均距离,因此使用Scroller时候有一个平滑的滚动效果
	scroller.extendDuration();//设置滚动的时间

使用的时候不要忘记重写方法,基本写法是固定的,就是获取x或y方向的移动距离的平均值,然后进行移动,由于不停的调用这个方法,因此产生平滑滚动的效果。

@Override
public void computeScroll() {
    super.computeScroll();
    //如果scroller还没停止,那么就还进行不停的绘制
    if (mScroller.computeScrollOffset()) {
        //注意这里的getCurrY()的源码获取的是进行微移后的当前的坐标,不是相对距离
        scrollTo(0, mScroller.getCurrY());
        postInvalidate();
    }
}

2.Scroller的使用场景
①滑动到边界的回弹效果
②快速滑动的fling效果 (但是我实现的效果感觉没有真正的scrollView流畅)

3.event.getX() event.getY() getScrollX() getScrollY() 的区别

event.getX()  event.getY()是获取的手指的点击的位置,它的作用仅仅是计算手指一动的距离,或是计算手指滑动速度。
getScrollX()  getScrollY()是又ScrollTo()进行直接的赋值(或是scrollBy() ,本质也是调用了scrollTo()),因此获取的是当前子view移动的距离,注意方向,getScrollY()为负值,就是向下移动。

4.速度识别器的使用

public class MyScrollView extends LinearLayout {

private Scroller mScroller;
//整个的ScrollView的高度
private int scrollHeight = 0;
private float lastX = 0;
private float lastY = 0;
/**
 * 在自定义view中经常使用的是重新绘制
 * 在viewGroup中经常使用scrollBy等进行移动
 */

private int windowHeight;
//这是进行记录横向或者竖向是否进行滑动了
private float moveX = 0;
private float moveY = 0;

private VelocityTracker mVelocityTracker;

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

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

public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mScroller = new Scroller(context);
    init();
}

public void init() {
    this.setOrientation(LinearLayout.VERTICAL);
}

//    如果是继承了LinearLayout 那么就没必要进行测量高度和摆放布局,仅仅进行计算滑动的距离就可以了。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int childCount = getChildCount();
    scrollHeight = 0;
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
        MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //添加上所有view的margin,获取整个子view的高度
        scrollHeight += child.getMeasuredHeight() + lp.bottomMargin + lp.topMargin;
    }
    //获取ScrollView的高度
    windowHeight = getMeasuredHeight();
    Log.e("TAG", "MyScrollView---scrollHeight---" + scrollHeight + "   windowHeight----" + windowHeight);

}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//        Log.e("TAG", "MyScrollView---dispatchTouchEvent---" + ev.getAction());
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = moveX = ev.getX();
            lastY = moveY = ev.getY();
            mScroller.forceFinished(true);
            if(mVelocityTracker==null){
                mVelocityTracker=VelocityTracker.obtain();
            }else{
                mVelocityTracker.clear();
            }
            mVelocityTracker.addMovement(ev);
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e("TAG", "MyScrollView---getScrollY====" + getScrollY());
            mVelocityTracker.addMovement(ev);
            float curX = ev.getX();
            float curY = ev.getY();
            //垂直滑动的时候进行移动
            if (Math.abs(curY - lastY) > Math.abs(curX - lastX)) {
                scrollBy(0, -(int) (curY - lastY));
//                  或者使用下面的方法,因为设置了平滑过渡时间为0
//                   mScroller.startScroll(0,getScrollY(),0,-(int)(curY-lastY),0);

                invalidate();
            }
            lastX = curX;
            lastY = curY;
            break;
        case MotionEvent.ACTION_UP:

            //这里做了一个回弹的效果,在第二个参数中设置了滚动了多少距离,然后dy为它的负值,立马就回弹回去
            //这里肯定不能传递ev.getX(),因为getX()是获取的手指点击的位置,因此一定要使用getScrollY(),这是获取的滚动后的距离。
            //这里getScrollY()是在scrollTo()或scrollBy()中进行赋值。因此要调用这个方法,一定要先调用这两个方法。
            //startScroll()方法不适合在action_move中调用,因为这个方法默认的时间就是250毫秒,频繁的使用postInvalidate()进行刷新,就会导致移动动作的
            //覆盖,反而出现很难移动的效果。因为action_move的回调很快,每个十几像素就回调了。如果将startScroll()的第五个参数设置为0,也就是间隔时间设置为0
            //mScroller.startScroll(0,(int)getScrollY(),0,-(int)(curY-lastY),0);  那么就出现了和scrollBy()相似的效果
            mScroller.abortAnimation();
            if (getScrollY() < 0) {//证明达到上边界了,这时候要进行回弹处理
                mScroller.startScroll(0, getScrollY(), 0, -getScrollY());
                postInvalidate();// 重绘执行computeScroll()
            } else if (windowHeight + getScrollY() > scrollHeight) {//达到最底部
                mScroller.startScroll(0, getScrollY(), 0, -(getScrollY() - (scrollHeight - windowHeight)));
                postInvalidate();// 重绘执行computeScroll()
            } else {
                mVelocityTracker.computeCurrentVelocity(1000);
                float yVelocity = mVelocityTracker.getYVelocity();
//                    Log.e("TAG", "MyScrollView---yVelocity====" + yVelocity);
                /**
                 * fling 方法参数注解
                 * startX 滚动起始点X坐标
                 * startY 滚动起始点Y坐标
                 * velocityX   当滑动屏幕时X方向初速度,以每秒像素数计算
                 * velocityY   当滑动屏幕时Y方向初速度,以每秒像素数计算
                 * minX    X方向的最小值,scroller不会滚过此点。
                 * maxX    X方向的最大值,scroller不会滚过此点。
                 * minY    Y方向的最小值,scroller不会滚过此点。
                 * maxY    Y方向的最大值,scroller不会滚过此点。
                 */
                if (Math.abs(yVelocity) > 50) {
//                        mScroller.extendDuration(2000);
                    mScroller.fling(0, getScrollY(), 0, -(int) yVelocity,
                            0, 0,
                            0, (scrollHeight - windowHeight));
                    postInvalidate();
                }
            }
            //进行计算移动的距离
            moveX = Math.abs(ev.getX() - moveX);
            moveY = Math.abs(ev.getY() - moveY);
            //如果横向或者竖向已经移动了一段距离,那么就不能响应子控件的点击事件
            if (moveY > 10) {
                return true;
            }
            break;
    }
    return super.dispatchTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//        Log.e("TAG", "MyScrollView---onInterceptTouchEvent---" + ev.getAction());
    return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    Log.e("TAG", "MyScrollView---onTouchEvent---" + ev.getAction());
    return true;
}

@Override
public void computeScroll() {
    super.computeScroll();
    //如果scroller还没停止,那么就还进行不停的绘制
    if (mScroller.computeScrollOffset()) {
        Log.e("TAG", "MyScrollView---computeScroll---");
        //注意这里的getCurrY()的源码获取的是进行微移后的当前的坐标,不是相对距离
        scrollTo(0, mScroller.getCurrY());
        postInvalidate();
    }
}}

具体效果请看demo:
https://github.com/yunzheyue/myScrollView

你可能感兴趣的:(自定义View)