Android自定义控件----继承ViewGroup自定义ViewPager2,使用Scroller实现平滑移动

在上一篇文章中
Android自定义控件—-继承ViewGroup自定义ViewPager(学习)
中由于使用scrollTo()和scrollBy()这两个方法进行滑动,但是有个问题就是滑动很生硬,所以在这篇文章使用Scroller对象实现平滑的滑动,Scroller对象的使用的基本步骤:

// 1. 创建Scroller的实例
// 2. 调用startScroll()方法来初始化滚动数据并刷新界面
// 3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑(里面代码时固定的)

具体代码如下:

package com.zhh.mybanner;

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

import com.orhanobut.logger.Logger;

/**
 * Created by 16838 on 2018/5/23.
 */
public class ImageViewGroup extends ViewGroup {
    //  子视图的个数
    private int children;
    //  子视图的宽度
    private int childwidth;
    //  子视图的高度
    private int childheight;
    //  此时的x值代表的是第一次按下位置的横坐标,每一次移动过程中 移动之前位置的横坐标
    private int myX;
    //  每张图片的索引
    private int index = 0;
//    Scroller的用法
//    1. 创建Scroller的实例
//    2. 调用startScroll()方法来初始化滚动数据并刷新界面
//    3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑(里面代码时固定的)
//  定义一个scroller对象
    Scroller scroller;

    /**
     * 控件在xml文件中声明的时候必须要重写这个
     *
     * @param context
     * @param attrs
     */
    public ImageViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        scroller = new Scroller(getContext());
    }

    /**
     * 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
     * 计算滑动
     * 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        // 如果返回true,表示动画还没有结束
        // 因为前面startScroll,所以只有在startScroll完成时 才会为false
        if (scroller.computeScrollOffset()) {
            // 产生了动画效果,根据当前值 每次滚动一点
            scrollTo(scroller.getCurrX(), 0);
            //此时同样也需要刷新View ,否则效果可能有误差
            postInvalidate();
        } else {
//          动画结束
        }
    }

    /**
     * 测量宽度和高度
     * 测量父布局的高度和宽度
     * 测量----布局----绘制
     * 绘制:针对绘制来说,因为我们是自定义的 ImageViewGroup容器,
     * 针对容器的绘制,其实就是容器内子控件的绘制过程,那么我们只需要
     * 调用系统自带的绘制,也就是说我们不需要重写该方法,调用系统自带的即可。
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//      1求出子视图的个数
        children = getChildCount();
//      2求出子视图的宽度和高度
        if (children == 0) {
//         如果子视图的个数是0,则设置ImageViewGroup的高度和宽度是0
            setMeasuredDimension(0, 0);
        } else {
//          测量子视图的宽度和高度
            measureChildren(widthMeasureSpec, heightMeasureSpec);
//          拿到第一个子视图,绝对存在
            View view = getChildAt(0);
//      3根据子视图的宽度和高度,求出ImageViewGroup的宽度和高度
            childheight = view.getMeasuredHeight();
            childwidth = view.getMeasuredWidth();
            int width = childwidth * children;
//          设置ImageViewGroup的宽高
            setMeasuredDimension(width, childheight);

        }

    }

    /**
     * 参数 changed 表示ImageViewGroup位置发生改变时时true,不发生改变为false
     * 设置子布局的位置
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed) {
            int leftMargin = 0;
            for (int i = 0; i < children; i++) {
//            当位置改变时设置每个子布局的位置
                View view = getChildAt(i);
//              设置子布局的位置
//                left 和 top 是控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离。
//                right 和 bottom是空间右边缘和下边缘相对于父类控件左边缘和上边缘的距离。
//                所以下面这样计算
                view.layout(leftMargin, 0, leftMargin + childwidth, childheight);
                leftMargin += childwidth;
            }
        }

    }

    /**
     * 事件处理
     * 事件的传递过程中的调用方法:我们需要 调用 容器的拦截方法 onInterceptTouchEvent
     * 针对该方法我们可以理解为 如果说 该方法的返回值为true的时候,那么自定义的ImageViewGroup就会处理此次拦截事件
     * 如果说返回的值为false的时候,不会接受此次事件的处理过程,将会继续向下传递事件,我们希望ImageViewGroup处理接收事件,那么我们的
     * 返回值就是true,如果是true,真正处理该事件的方法是onTouchEvent
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
//        按下一瞬间
            case MotionEvent.ACTION_DOWN:
                Logger.t("111").d("ACTION_DOWN");
                myX = (int) event.getX();

                break;
//        移动过程
            case MotionEvent.ACTION_MOVE:
//            距离父容器左边的距离,和x轴坐标相等
                int moveX = (int) event.getX();
                Logger.t("111").d("ACTION_MOVE+move" + moveX);
                int distance = moveX - myX;
//            scrollTo移动的是坐标,将整个父视图的左上角定为(0,0)
//            scrollBy移动的是距离
                scrollBy(-distance, 0);
                myX = moveX;
                break;
//        抬起一瞬间
            case MotionEvent.ACTION_UP:
                Logger.t("111").d("ACTION_UP");
//             移动的位移,原点减视图左上角坐标   0-左上角坐标
                int scrollX = getScrollX();
//              计算索引
                index = (scrollX + childwidth / 2) / childwidth;
                if (index < 0) {
                    //说明此时已经滑到左边第一张图片
                    index = 0;
                } else if (index > children - 1) {
                    //说明此时已经滑动到最后一张图片
                    index = children - 1;
                }
                int dx = index * childwidth - scrollX;
//            调用startScroll()方法来初始化滚动数据并刷新界面
//            开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy)出
                scroller.startScroll(scrollX, 0, dx, 0, 500);
//              重绘
                invalidate();
                break;
            default:
                break;

        }
        return true;
    }


}

参考文章
https://blog.csdn.net/guolin_blog/article/details/48719871
参考视频:
https://www.imooc.com/learn/793

猜你喜欢

转载自blog.csdn.net/zhaihaohao1/article/details/80564569