使用了看看最终修改后的MyViewPager类

本文详细介绍如何通过自定义ViewGroup实现类似ViewPager的滑动效果,包括手势识别、事件处理和使用Scroller优化滚动体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍
学习自定义View,这个对于初学者来说确实有点难度。因为这需要你熟悉View绘制的基本流程,不仅如此,你还需要熟悉手势识别、解决事件冲突等知识。这是一系列综合性的学习,如果想在Android方面进阶,你必须攻克这个首要技能。今天,我们的目的是学习自定义ViewGroup实现ViewPager类似的滑动效果。

先来看一下我们要实现的效果,其实这个和原生ViewPager效果一样的。

效果图

此篇是为了学习自定义View为目的文章,因为处于初学阶段,我们先从简单的实现效果一步步进阶。自定义ViewGroup必须重写父类的一个方法onLayout(),我们来看一下这个方法的介绍。

onLayout

void onLayout (boolean changed,
int left,
int top,
int right,
int bottom)
Called from layout when this view should assign a size and position to each of its children. Derived classes with children should override this method and call layout on each of their children.

Parameters
changed
boolean
: This is a new size or position for this view
left
int
: Left position, relative to parent
top
int
: Top position, relative to parent
right
int
: Right position, relative to parent
bottom
int
: Bottom position, relative to parent
大意:这个方法用于对子视图进行布局,分配子视图大小和位置。ViewGroup必须重写这个方法,根据子视图的左上、右下角的坐标来确定其大小和位置。
代码及实现
那么,让我们继承ViewGroup来实现这样效果。先看一下我们的MyViewPager类,它简单的对子视图进行了布局。

/**

  • @Created by xww.
  • @Creation time 2018/8/13.
    */

public class MyViewPager extends ViewGroup {

private GestureDetector mGestureDetector;

public MyViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    mGestureDetector = new GestureDetector(context, new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {

        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            scrollBy((int) distanceX, getScrollY());
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {

        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return false;
        }
    });
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        //对于每个子View进行布局
        View childView = getChildAt(i);
        childView.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
    return true;
}

}
对于每个子视图(图片)进行了全屏幕的占用,我们看onLayout()方法里的代码,先获取了每个子视图,对子视图进行大小和位子的设置。这里我们依照ViewPager,将子视图横向排成一排,每一张图片占一整个屏幕。下面是我们添加图片的代码,不予解释了。

private int[] imgs = {R.drawable.bg_01, R.drawable.bg_02, R.drawable.bg_03, R.drawable.bg_04, R.drawable.bg_05, R.drawable.bg_06};

private void initViewPager() {
    for (int img : imgs) {
        AppCompatImageView imageView = new AppCompatImageView(getContext());
        imageView.setBackgroundResource(img);
        myViewpager.addView(imageView);
    }
}
这里我用到了GestureDetector类,这个类指明是手势识别器,它内部封装了一些常用的手势操作的接口,比如单机、双击、长按、滚动等。因为我们用到滚动手势,所以我们在这里中断(处理)它的事件。对于scrollTo、scrollBy、Scroller有疑问的可以看我之前的一篇文章:理解scrollTo、scrollBy、Scroller的一些区别及用法,掌握视图滚动方式以及屏幕坐标系

https://www.ximalaya.com/yinyue/25370853/
https://www.ximalaya.com/yinyue/25370843/
https://www.ximalaya.com/yinyue/25370829/
https://www.ximalaya.com/yinyue/25370823/
https://www.ximalaya.com/yinyue/25370820/
https://www.ximalaya.com/yinyue/25370787/
https://www.ximalaya.com/yinyue/25370817/
https://www.ximalaya.com/yinyue/25370777/
https://www.ximalaya.com/yinyue/25370772/
https://www.ximalaya.com/yinyue/25370767/
https://www.ximalaya.com/yinyue/25370724/
https://www.ximalaya.com/yinyue/25370719/
https://www.ximalaya.com/yinyue/25370715/
https://www.ximalaya.com/yinyue/25370707/
https://www.ximalaya.com/yinyue/25370702/
https://www.ximalaya.com/yinyue/25376302/
https://www.ximalaya.com/yinyue/25376297/
https://www.ximalaya.com/yinyue/25376293/
https://www.ximalaya.com/yinyue/25376291/
https://www.ximalaya.com/yinyue/25376284/
https://www.ximalaya.com/yinyue/25376269/
https://www.ximalaya.com/yinyue/25376273/
https://www.ximalaya.com/yinyue/25376248/
https://www.ximalaya.com/yinyue/25376243/
https://www.ximalaya.com/yinyue/25375806/
https://www.ximalaya.com/yinyue/25375801/
根据手指滑动产生的distanceX,我们进行子视图的滚动,并且在onTouchEvent中处理我们的手势识别器的事件。到了这一步,我们变可以看到它的滑动效果。

优化代码
是不是和我们预想的不太一样?动是可以动了,但它不会自己动,还会卡在中间,是不是感觉很不爽。那这样子的话,我们就得借助另一个滚动视图的类Scroller类。同理,这个类的用法在上面一个快捷链接中也有说明,这里我就不讲它是如何使用了。我们看看最终修改后的MyViewPager类。

/**

  • @Created by xww.
  • @Creation time 2018/8/13.
    */

public class MyViewPager extends ViewGroup {

private GestureDetector mGestureDetector;
private int currentIndex;
private int startX;
private int endX;
private Scroller mScroller;

public MyViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    mScroller = new Scroller(context);
    mGestureDetector = new GestureDetector(context, new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {

        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            scrollBy((int) distanceX, getScrollY());
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {

        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return false;
        }
    });
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        //对于每个子View进行布局
        View childView = getChildAt(i);
        childView.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            startX = (int) event.getX();
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            endX = (int) event.getX();

            int tempIndex = currentIndex;
            if (startX - endX > getWidth() / 2) {    //从右往左滑动
                tempIndex++;
            } else if (endX - startX > getWidth() / 2) {    //从左往右滑动
                tempIndex--;
            }
            scrollIndex(tempIndex);
            break;
    }
    return true;
}

/**
 * 移动到指定页面
 */
private void scrollIndex(int tempIndex) {
    //第一页,无法继续向左滑动
    if (tempIndex < 0) {
        tempIndex = 0;
    }
    //同理,最后一页无法向右滑动
    if (tempIndex > getChildCount() - 1) {
        tempIndex = getChildCount() - 1;
    }
    currentIndex = tempIndex;
    mScroller.startScroll(getScrollX(), 0, currentIndex * getWidth() - getScrollX(), 0);
    postInvalidate();
}

@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), 0);
        postInvalidate();
    }
}

}
它实现的思路:我们给予每张图片一个下标,当我们将当前页面滚动的距离大于屏幕宽度的一半时,子视图将自动滚动到下一张图;同理,当前页面滚动的距离小于屏幕宽度的一半,那将自动恢复原始状态,不会滚动到下一张图。主要是在onTouchEvent()方法中,标记起始点和滑动后的最终点的坐标距离,用滑动距离比对屏幕的一半宽度,然后在处理是否要切换图片。代码实现不是很难,多于理解它实现的原理,相信你很快就会掌握自定义View。

作者:威威喵
来源:优快云
原文:https://blog.youkuaiyun.com/smile_Running/article/details/81634308
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值