文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android 自定义ViewPager

2022-06-06 14:12

关注

项目地址

概述 处理滑动到左边界和右边界时,不允许滑动。 页面滑动一半回弹,滑动一半以上自动切换下一界面。 当页面内存在ScrollView这类子控件,事件要正常分发,不允许自定义ViewPager拦截事件。 回弹与切换动画处理。 源码分析 初始化
public ViewPagerY(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mContext = context;
    // Scroller设置的是一个匀速插值器
    myScroll = new Scroller(context, new LinearInterpolator());
    // 初始化ImageLoader
    ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(mContext));
}
设置资源,如图片id,布局,图片链接等

public void setRes(ArrayList res) {
    for (ResType mResType : res) {
        if (mResType.getmType() == ResType.Type.IAMG) {
            // 如果是资源图片id,创建ImageView对象
            ImageView imageView = new ImageView(mContext);
            imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
            imageView.setImageResource((Integer) mResType.getRes());
            // 将设置好的ImageView添加进ViewPagerY控件中
            this.addView(imageView);
        }
        if (mResType.getmType() == ResType.Type.URL) {
            // 如果资源是图片URL,创建ImageView对象.
            ImageView imageView = new ImageView(mContext);
            imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
            // 使用ImageLoader将图片从网络获取设置到ImageView中.
            ImageLoader.getInstance().displayImage((String) mResType.getRes(), imageView);
            // 将设置好的ImageView添加进ViewPagerY控件中
            this.addView(imageView);
        }
        if (mResType.getmType() == ResType.Type.LAYOUT) {
            // 如果资源是自己写的布局文件,就获取该布局文件对应的View
            View view = LayoutInflater.from(mContext).inflate((Integer) mResType.getRes(), null);
            // 将获取到的View添加进ViewPagerY控件中
            this.addView(view);
        }
    }
}
// 资源类型类
public class ResType {
    public enum Type {
        IAMG,
        LAYOUT,
        URL
    }
    private T mRes;
    private Type mType;
    public ResType(T res, Type mType) {
        this.mRes = res;
        this.mType = mType;
    }
    public T getRes() {
        return mRes;
    }
    public Type getmType() {
        return mType;
    }
}
ViewPagerY对子View的测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 设置ViewPagerY尺寸
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // 获取ViewPagerY宽高
    height = getMeasuredHeight();
    widht = getMeasuredWidth();
    // 创建子View的MeasureSpec, 创建MeasureSpec是有规律的,可以看这个笔记: https://blog.csdn.net/MoLiao2046/article/details/105708819
    int wMeasureSpec = MeasureSpec.makeMeasureSpec(widht, MeasureSpec.EXACTLY);
    int hMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    for (int i = 0; i < getChildCount(); i++) {
        // 遍历子View开始测量子View.
        getChildAt(i).measure(wMeasureSpec, hMeasureSpec);
    }
}
ViewPagerY对子View进行布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    for (int i = 0; i < getChildCount(); i++) {
        // 资源集合中每一个资源对应一个页面.
        // 分别设置这些View的左上角与右下角坐标.
        this.getChildAt(i).layout(i * widht, 0, i * widht + widht, height);
    }
}
判断手势,如果手势为水平滑动就拦截, 否则就正常将事件分发给子View处理.
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    // 默认交给ViewGroup拦截事件,ViewGroup一般是不会拦截事件.
    boolean interceptChildeEvent = super.onInterceptTouchEvent(event);
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            // mLastX与mDownX是手指按下时候的坐标,这两个值会在ViewPagerY中onTouchEvent中用到,处理页面滑动用的.
            // onTouchEvent()中也能获取ACTION_DOWN事件,但是在onTouchEvent中获取手指按下坐标再进行相应移动处理会出现页面跳动的问题.
            mLastX = mDownX = interceptLastX = event.getX();
            interceptLastY = event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            // 获取当前手指坐标
            float moveX = event.getX();
            float moveY = event.getY();
            // 移动后,计算出滑动后与上一个坐标点之间的距离.
            float slopX = moveX - interceptLastX;
            float slopY = moveY - interceptLastY;
            // 得到手指滑动距离绝对值
            float slopAbsX = Math.abs(slopX);
            float slopAbsY = Math.abs(slopY);
            if ((slopAbsX > 0 || slopAbsY > 0) && (slopAbsX - slopAbsY) >= 6) {
                // 如果手指移动距离大于0,且横向移动距离减去纵向移动距离大于6像素
                // 那么ViewPagerY就将该事件拦截, 不分发给它的子View使用,留给自己使用了.
                // 这样会导致mFirstTouchTarget=null,之后子View就再也接收不到事件组的其他事件了.
                interceptChildeEvent = true;
            }
            interceptLastX = moveX;
            interceptLastY = moveY;
            break;
    }
    return interceptChildeEvent;
}
处理页面滑动回弹的逻辑
@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_MOVE:
            float mMoveX = event.getX();
            // mLastX:是在onInterceptTouchEvent()中得到的值
            float mDiffX = mMoveX - mLastX;
            mLastX = mMoveX;
            if (event.getPointerId(event.getActionIndex()) == 0 && event.getPointerCount() == 1) {// 这个条件可以控制只追踪屏幕中的一个手指的滑动.
                int scrollX = getScrollX();
                if (currentIndex == 0) {
                    // 第一页
                    if (mDiffX = 0) {
                            // 内容左边距离控件左边的距离减去向右滑动距离,如果大于0,说明内容左边距离控件左边还有间隔距离,滑动距离取手指移动距离.
                            ViewPagerY.this.scrollBy((int) -mDiffX, 0);
                        } else {
                            // 内容左边距离控件左边的距离减去向右滑动距离,如果小于0,说明内容左边与控件左边需要重合,滑动距离取getScrollX().
                            ViewPagerY.this.scrollBy(-scrollX, 0);
                        }
                    }
                }
                if (currentIndex == getChildCount() - 1) {
                    // 最后一页
                    if (mDiffX > 0) {
                        // 如果向右滑动
                        ViewPagerY.this.scrollBy((int) -mDiffX, 0);
                    } else {
                        // 处理先向右滑动,然后又向左滑动.
                        // (((getChildCount() - 1) * widht) - scrollX): 表示内容右边距离控件右边的距离
                        float mDiffMargin = (((getChildCount() - 1) * widht) - scrollX) + mDiffX;
                        if (mDiffMargin >= 0) {
                            // 说明内容右边与控件右边还有距离,滑动距离取手指移动距离.
                            ViewPagerY.this.scrollBy((int) -mDiffX, 0);
                        } else {
                            // 说明内容右边与控件右边需要重合,滑动距离取内容右边与控件右边的距离.
                            ViewPagerY.this.scrollBy(-(((getChildCount() - 1) * widht) - scrollX), 0);
                        }
                    }
                }
                if (currentIndex != 0 && currentIndex != getChildCount() - 1) {
                    // scrollBy总是和移动的相反
                    ViewPagerY.this.scrollBy((int) -mDiffX, 0);
                }
            }
            break;
        case MotionEvent.ACTION_UP:
            float mUpX = event.getX();
            // mDownX:是在onInterceptTouchEvent()中得到的值
            if (mUpX - mDownX > getWidth() / 2) {
                // 移动到上一个
                moveTo(currentIndex - 1);
            } else if (mUpX - mDownX < -getWidth() / 2) {
                // 移动到下一个
                moveTo(currentIndex + 1);
            } else {
                // 移动到当前页面
                moveTo(currentIndex);
            }
            break;
    }
    return true;
}
页面切换逻辑

public void moveTo(int index) {
    int duration = 0;
    if (index  getChildCount() - 1) {
        index = getChildCount() - 1;
    }
    // 中间经历几个界面, 每个页面切换时长是固定的.
    int count = currentIndex - index;
    if (count != 0) {
        duration = mDuration * count;
    } else {
        duration = mDuration;
    }
    currentIndex = index;
    if (onPageChangeListener != null) {
        // 页面切换的监听.
        onPageChangeListener.onPageSelect(currentIndex);
    }
    // getScrollX(): 内容左边与ViewPager控件左边距离
    // currentIndex * getWidth(): 切换到currentIndex界面时的getScrollX()值.
    // 得到需要移动的距离.
    int distanceX = currentIndex * getWidth() - getScrollX();
    //给MyScroll 计算的类赋初始值
    myScroll.startScroll(getScrollX(), 0, distanceX, 0, Math.abs(duration));
    invalidate();
}
// 想要缓慢滑动这个也很重要.
@Override
public void computeScroll() {
    //如果为true说明移动还没结束
    if (myScroll.computeScrollOffset()) {
        //得到计算的位置,然后移动
        float currX = myScroll.getCurrX();
        scrollTo((int) currX, 0);
        invalidate();
    }
}
效果图

效果图

碧云天丶 原创文章 65获赞 14访问量 3万+ 关注 私信 展开阅读全文
作者:碧云天丶


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯