欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-08 17:15:30
...

在上一篇文章中
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