android自定义View之从入门到放弃(三)实现viewPager详解 记录学习

本次博客主要介绍的是如何使用自定义ViewGroup实现viewPager 的详细介绍 :
从中你还可以学习到scrollTo() 和scrollBy()的详细使用的方法 ,手势识别器的使用
好了,废话不多比比 开始咯
首先:实现ViewPager的思路:
在这里插入图片描述
1.首先需要几张图片,这你可以随便来几张图片,最好宽高都一样 相差别太大
从上图我们就可以拿到图片的left,top,right,bottom的值 ,然后根据ViewGroup特有的Layout()方法将图片的位置进行确定
上代码:
布局文件中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".widget.LunBoActivity">
<com.example.zidingyidemo.widget.LunboView
    android:id="@+id/lunboView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />
</LinearLayout>

要实例化的activity:

public class LunBoActivity extends AppCompatActivity {
    int arr[] = {R.drawable.first,R.drawable.second,R.drawable.third,R.drawable.four,R.drawable.five};
    private LunboView lunboView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lun_bo);
        lunboView = findViewById(R.id.lunboView);
        for(int i=0;i<arr.length;i++){
            ImageView imageView = new ImageView(this);
            imageView.setBackgroundResource(arr[i]);
            lunboView.addView(imageView);
        }
    }
}

从上我们可以看到 我们在activity中将我们准备好的图片资源放入了一个数组中,然后再代码中进行imageView的实例化 将准备好的图片资源放入到我们的imageView中了
自定义的View -->精华所在

public class LunboView extends ViewGroup {
 
    public LunboView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
    
    //View的具体位置
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       //拿到孩子  确定孩子的具体位置
        for(int i=0;i<getChildCount();i++){//循环遍历孩子个数
            View childView = getChildAt(i);//拿到孩子们-->imageView
            childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());//确定孩子的具体位置
         }
    }

}

当完成以上步骤 你就可以将图片进行显示出来了 ,但是 由于我们定义的孩子位置的坐标 当前只能看到第一张图片(占满屏幕)
想要实现左右滑动显示其他的图片 这时我们就要接触到手势识别器(GestureDetector )
手势识别器的详细使用步骤:
* 1.定义出来
* 2.实例化 将想要的方法进行实现(其中有跟多的方法 如滚动事件监听,双击事件监听 按需重写方法就可以)
* 3.在onTouchEvent中注册(传递给手势识别器)
一起来看代码:

public class LunboView extends ViewGroup {
    /**
     * 手势识别器
     * 1.定义出来
     * 2.实例化 将想要的方法进行实现
     * 3.在onTouchEvent中注册(传递给手势识别器)
     *
     */
    private GestureDetector detector;//1.定义出来
    public LunboView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        scroller = new Scroller(context);
        detector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){ //2.实例化 将想要的方法进行实现
            /**
             *
             * @param e1  手指按下
             * @param e2   手指抬起
             * @param distanceX  距离x
             * @param distanceY   距离y
             * @return
             */
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
               // return super.onScroll(e1, e2, diseX, distanceY);
                scrollBy((int) distanceX,getScrollY());   //getScrollY初始值   0
                return true;
            }
        });
    }


    //View的具体位置
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       //拿到孩子  确定孩子的具体位置
        for(int i=0;i<getChildCount();i++){
            View childView = getChildAt(i);
            childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());
         }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);//这一步可以省略 因为下面返回了true
       detector.onTouchEvent(event);//3.在onTouchEvent中注册(传递给手势识别器)
        return true;
    }

}

从上面就可以对图片进行滑动 但是会有一个问题 就是当我们滑动未滑动到下一张图片时 会停留再当前你滑动的位置 ,下面我们就解决这个bug 但是之前 还是来介绍一下scrollTo()和scrollBy()
首先通过字面意思我们就可以得知 scrollTo(x,y)是到达指定的位置
源码:

 public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

是不是有点懵 ,不急 我们记住他的字面意思 然后我们来看一下scrollBy()
scrollBy(x,y) 他的意思在初始值的状态下移动到指定位置的间隔距离(x,y)
源码:

  public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

从中我们可以看出 scrollBy源码里面又执行了scrollTo()
其中我们可以看到mScrollX,和mScrollY 他们其实就是我们页面的坐标原点 就是0,0
由此就可以清晰的区分出scrollTo和ScrollBy()
scrollTo(x,y)->到达我们指定的x,y坐标位置
scrollBy(x,y)->达到我们指定的x,y坐标的距离
举个简单的例子:
假设当前我们的坐标值x,y为(20,0) 当我们想要移动到(40,0)这个坐标的时候
scrollTo(40,0); scrollBy(20,0)
当然还有scrollTo()和scrollBy()都是在内部移动的 不会改变当前视图的位置 (很重要)
在这里插入图片描述
从上图我们可以清晰的看到 :当我们想要显示下一张图片时我们当前的left,top值 要从之前的(getWidth,0)移动到屏幕(0,0)的位置才可以显示,由此可得 上一张图片要显示在当前屏幕上时 我们的left,top值就从(-getWidth,0)变成了(0,0) 由此我们可以得出结论,在以当前屏幕为参照物的时候,当我们想要显示下一张图片 我们的坐标是减少了的,当我们想要显示上一张图片时 我们的坐标反而是加加了 但是当我们想要显示下一张图片时我们的index是要加一的

然后就可以根据MotionEvent的抬起和按下的时候,获取当前的x坐标,然后计算出偏移的距离,根据偏移的距离进行判断,如果偏移的距离大于整个屏幕宽度的一半就显示下一张图片,反之还显示当前图片

@Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
       detector.onTouchEvent(event);
        switch (event.getAction()){
            //手指按下
            case MotionEvent.ACTION_DOWN:
                 startX= event.getX();//获取按下的时候的X值
                break;
            case MotionEvent.ACTION_UP:
                 float endX = event.getX();//获取抬起的时候的X值
                 float pian = endX-startX;//x偏移量
                int currentindex =  current;
                Log.i("startend","start"+startX+"endx"+endX);
                 if((startX-endX)>getWidth()/2){
                     //显示下一个页面
                     currentindex++;  //注意这里时下标位置
                 }else if((endX-startX)>getWidth()/2){
                     currentindex--;
                 }
                 //根据下标移动到具体页面
                 scrollto(currentindex);
                break;
        }
        return true;
    }

    private void scrollto(int currentindex) {
        Log.i("nidaodiyou","@"+getChildCount());
        if(currentindex<0){
            currentindex = 0;
        }
        if(currentindex>getChildCount()-1){
            currentindex = getChildCount()-1;

        }
        current = currentindex;

        //移动到指定位置(瞬间)
        scrollTo(current*getWidth(),getScrollY());
    }

在这里插入图片描述
其中下标的确认由图清楚可得 我就不再做更多的解释了

这时我们就可以解决这个bug了 但是新的问题又出现了 当我们滑动超过中间的一半 是瞬时滑动到了上一张或者下一张 没有缓慢划过去的那种动画 感觉不美观
这时我们可以调用系统提供的Scroller这个类来解决 这里不做过多的介绍 我就将完整的代码给贴出来

public class LunboView extends ViewGroup {
    private  float startX;
    private int current;  //当前页面的下标位置
    //private Scroller scroller;
    private android.widget.Scroller scroller;
    /**
     * 手势识别器
     * 1.定义出来
     * 2.实例化 将想要的方法进行实现
     * 3.在onTouchEvent中注册(传递给手势识别器)
     *
     */
    private GestureDetector detector;
    public LunboView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        scroller = new Scroller(context);
        detector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
            /**
             *
             * @param e1  手指按下
             * @param e2   手指抬起
             * @param distanceX  距离x
             * @param distanceY   距离y
             * @return
             */
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
               // return super.onScroll(e1, e2, diseX, distanceY);
                scrollBy((int) distanceX,getScrollY());   //getScrollY初始值   0
                return true;
            }
        });
    }


    //View的具体位置
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       //拿到孩子  确定孩子的具体位置
        for(int i=0;i<getChildCount();i++){
            View childView = getChildAt(i);
            childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());
         }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
       detector.onTouchEvent(event);
        switch (event.getAction()){
            //手指按下
            case MotionEvent.ACTION_DOWN:
                 startX= event.getX();
                break;
            case MotionEvent.ACTION_UP:
                 float endX = event.getX();
                 float pian = endX-startX;
                int currentindex =  current;
                Log.i("startend","start"+startX+"endx"+endX);
                 if((startX-endX)>getWidth()/2){
                     //显示下一个页面
                     currentindex++;  //注意这里时下标位置
                 }else if((endX-startX)>getWidth()/2){
                     currentindex--;
                 }
                 //根据下标移动到具体页面
                 scrollto(currentindex);
                break;
        }
        return true;
    }

    private void scrollto(int currentindex) {
        Log.i("nidaodiyou","@"+getChildCount());
        if(currentindex<0){
            currentindex = 0;
        }
        if(currentindex>getChildCount()-1){
            currentindex = getChildCount()-1;

        }
        current = currentindex;

        int  distance = current*getWidth()-getScrollX();
        scroller.startScroll(getScrollX(),getScrollY(),distance,0);

        invalidate();  //导致ondraw()重新执行  还有computeScroll


        //移动到指定位置(瞬间)
        //scrollTo(current*getWidth(),getScrollY());
    }

    //
    @Override
    public void computeScroll() {
     //   super.computeScroll();
        if(scroller.computeScrollOffset()){
            float currx = scroller.getCurrX();
            scrollTo((int) currx,0);
            invalidate();  //再去执行
        }
    }
}

ok,这样就完成了效果 喜欢的话请点一个小赞👍 你的支持是我最大的动力!!!!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安东尼肉店

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值