简单实现ViewPager和熟悉一些api

先上代码:

public class MyViewPager extends ViewGroup {


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

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

    public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    int screenHight;
    Scroller scroller;
    int mTouchSlop;
    private void init(Context context) {

        screenHight = ((Activity)context).getWindowManager().getDefaultDisplay().getHeight();

        scroller = new Scroller(context);

        ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);

    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int y = (int) ev.getY();
        switch (ev.getAction()){

            case MotionEvent.ACTION_DOWN:

                mLastY = (int) ev.getY();
                mStart = getScrollY();

                break;

            case MotionEvent.ACTION_MOVE:

                int dy = Math.abs(y - mLastY);
//                mLastY = y;
                if(dy>mTouchSlop){
                    return true;
                }

                break;

        }

        return super.onInterceptTouchEvent(ev);
    }

    int mLastY;
    int mStart;
    int mEnd;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int y = (int) event.getY();
        switch (event.getAction()){
//            case MotionEvent.ACTION_DOWN:
//                mLastY = (int) event.getY();
//                break;
            case  MotionEvent.ACTION_MOVE:

                if(scroller.isFinished()){
                    scroller.abortAnimation();
                }
                int dy = mLastY - y;
                System.out.println("getScrollY():"+getScrollY());
                if(getScrollY() + dy<0){
                    dy = 0;
                }

                System.out.println("getMeasuredHeight():"+getMeasuredHeight());

                System.out.println("screenHight:"+screenHight);
                if(getScrollY() + dy>screenHight*(getChildCount()-1)){
                    dy = 0;
                }

                System.out.println("dy:"+dy);


                scrollBy(0,dy);
                mLastY = y;

                break;

            case MotionEvent.ACTION_UP:

                mEnd = getScrollY();
                int dScrollY = mEnd - mStart;
                if(dScrollY>0){
                    if(dScrollY<screenHight/3){
                        scroller.startScroll(0,getScrollY(),0,-dScrollY);
                    }else {
                        scroller.startScroll(0,getScrollY(),0,screenHight-dScrollY);
                    }
                }else {
                    if(-dScrollY<screenHight/3){
                        scroller.startScroll(0,getScrollY(),0,-dScrollY);
                    }else {
                        scroller.startScroll(0,getScrollY(),0,-screenHight-dScrollY);
                    }

                }

                break;
        }
        invalidate();
        return true;
    }


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

    }



    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int count = getChildCount();
        for (int i=0;i<count;i++){

            View childView = getChildAt(i);
            measureChild(childView,widthMeasureSpec,heightMeasureSpec);

        }

    }

    int topBorder;
    int bottomBorder;
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

//        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
//        mlp.height = screenHight * getChildCount();
//
//        setLayoutParams(mlp);

        for (int i=0;i<getChildCount();i++){

            View child = getChildAt(i);
            if(child.getVisibility()!=View.GONE){

                child.layout(l,i*screenHight,r,(i+1)*screenHight);

            }
        }


        topBorder = getChildAt(0).getLeft();
        bottomBorder = getChildAt(getChildCount()-1).getRight();


    }
}

API熟悉:

1.ViewConfiguration configuration = ViewConfiguration.get(context);

int mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);

mTouchSlop是系统判定移动的最小距离,超过这个距离就认为是move

 

2.ScrollTo()、ScrollBy()和scroller

2.1 ScrollTo()和ScrollBy()

https://blog.csdn.net/guolin_blog/article/details/48719871 郭神的解释很到位

(1).这两个方法都是把自己的内容做一个滚动,有一点需要注意,就是两个scroll方法中传入的参数,第一个参数x表示相对于当前位置横向移动的距离,正值向左移动,负值向右移动,单位是像素。第二个参数y表示相对于当前位置纵向移动的距离,正值向上移动,负值向下移动,单位是像素。

(2).scrollTo()是以本View的top和left为基准

(3).scrollBy()是以当前位置为基准

2.2. scroller

scrollTo或者ScrollBy和scroller的区别:设置一段距离前两者实现效果是跳跃式的,scroller能实现平滑滚动效果,但是注意的是,Scroller本身不会去移动View,它只是一个移动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹记录工具,最终还是通过View的scrollTo、scrollBy方法完成View的移动的。

Scroller的基本用法:

     (1).创建Scroller的实例 :Scroller scroller = new Scroller(context);

源码:

public class Scroller {
private int mStartX;//水平方向,滑动时的起点偏移坐标
private int mStartY;//垂直方向,滑动时的起点偏移坐标
private int mFinalX;//滑动完成后的偏移坐标,水平方向
private int mFinalY;//滑动完成后的偏移坐标,垂直方向

private int mCurrX;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,水平方向
private int mCurrY;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,垂直方向
private int mDuration; //本次滑动的动画时间
private float mDeltaX;//滑动过程中,在达到mFinalX前还需要滑动的距离,水平方向
private float mDeltaY;//滑动过程中,在达到mFinalX前还需要滑动的距离,垂直方向

...

}

     (2).调用startScroll()方法来初始化滚动数据并刷新界面:开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处。

startScroll()方法接收四个参数,第一个参数是滚动开始时X的坐标,第二个参数是滚动开始时Y的坐标,第三个参数是横向滚动的距离,正值表示向左滚动,第四个参数是纵向滚动的距离,正值表示向上滚动。紧接着调用invalidate()方法来刷新界面。

当startScroll执行过程中即在duration时间内,computeScrollOffset 方法会一直返回false,但当动画执行完成后会返回返加true.

如果不设置duration,默认DEFAULT_DURATION = 250.

源码:

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    mMode = SCROLL_MODE;
    mFinished = false;
    mDuration = duration;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;
    mFinalX = startX + dx;
    mFinalY = startY + dy;
    mDeltaX = dx;
    mDeltaY = dy;
    mDurationReciprocal = 1.0f / (float) mDuration;
}

使用的例子: 

                mEnd = getScrollY();
                int dScrollY = mEnd - mStart;
                if(dScrollY>0){
                    if(dScrollY<screenHight/3){
                        scroller.startScroll(0,getScrollY(),0,-dScrollY);
                    }else {
                        scroller.startScroll(0,getScrollY(),0,screenHight-dScrollY);
                    }
                }else {
                    if(-dScrollY<screenHight/3){
                        scroller.startScroll(0,getScrollY(),0,-dScrollY);
                    }else {
                        scroller.startScroll(0,getScrollY(),0,-screenHight-dScrollY);
                    }

                }
 

(3).重写computeScroll()方法,并在其内部完成平滑滚动的逻辑

Scroller类中最重要的两个方法就是startScroll()和computeScrollOffset(),但是Scroller类只是一个滑动计算辅助类,它的startScroll()和computeScrollOffset()方法中也只是对一些轨迹参数进行设置和计算,真正需要进行滑动还是得通过View的scrollTo()、scrollBy()方法。为此,View中提供了computeScroll()方法来控制这个滑动流程。computeScroll()方法会在绘制子视图的时候进行调用。其源码如下:

/** 
 * Called by a parent to request that a child update its values for mScrollX 
 * and mScrollY if necessary. This will typically be done if the child is 
 * animating a scroll using a {@link android.widget.Scroller Scroller} 
 * object. 
 * 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制  
 */
public void computeScroll() { //空方法 ,自定义滑动功能的ViewGroup必须实现方法体  
      
} 

最后我们还需要进行第三步操作,即重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 。在整个后续的平滑滚动过程中,computeScroll()方法是会一直被调用的,因此我们需要不断调用Scroller的computeScrollOffset()方法来进行判断滚动操作是否已经完成了,如果还没完成的话,那就继续调用scrollTo()方法,并把Scroller的curX和curY坐标传入,然后刷新界面从而完成平滑滚动的操作。

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

    }

scroller.computeScrollOffset()方法:滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中。

源码:

/**
     * 滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中
     * @return
*/
public boolean computeScrollOffset() {
	if (mFinished) {//已经完成了本次动画控制,直接返回为false
		return false;
	}
	int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
        if (timePassed < mDuration) {
		switch (mMode) {
		case SCROLL_MODE:
		final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
		mCurrX = mStartX + Math.round(x * mDeltaX);//计算出当前的滑动偏移位置,x轴
		mCurrY = mStartY + Math.round(x * mDeltaY);//计算出当前的滑动偏移位置,y轴
		break;
		...
            }
        }else {
		mCurrX = mFinalX;
		mCurrY = mFinalY;
		mFinished = true;
	}
	return true;
	}

因此Scroller类的基本使用流程可以总结如下:

(1)首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算;

(2)在调用startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;

(3)在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘。

3.getScrollX 和 getScrollY

https://blog.csdn.net/linmiansheng/article/details/17767795

图上面,褐色的框,其实就是我们眼睛看到的手机界面,就是一个窗口。

而绿色的长方体呢,就是一块可以左右拉动的幕布啦,其实也就是我们要显示在窗口上面的内容,它其实是可以很大的,大到无限大,只是没在窗口中间的,所以我们就看不到。

而getScrollX 其实获取的值,就是这块 幕布在窗口左边界时候的值了,而幕布上面哪个点是原点(0,0)呢?就是初始化时内容显示的位置。

所以当我们将幕布往右推动的时候,幕布在窗口左边界的值就会在0的左边(-100),而向左推动,则其值会是在0的右边(100)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值