ViewGroup练习防viewpager

技术点:
一、全屏显示图片页面


ViewGroup练习防viewpager_第1张图片
121.jpg

往自定义viewGrou——myViewPager中添加ImageView。

for (int i=0;i

但是此时运行的话,ImageView不显示,是因为viewGroup需要重写ouLayout来指定子view位置。

protected void onLayout(boolean flag, int l, int t, int r, int b) {
        //遍历每个孩子,给每个孩子指定在屏幕的坐标位置
        for (int index =0; index

希望实现的效果是,每张图片都以水平方向,一张一张排列全屏显示,如下:

ViewGroup练习防viewpager_第2张图片
viewgroup4.png

所以第一张的(l,t)坐标是(0,0),(r,b)坐标是(getWidth(),getHeight()),以此类推第二第三张等等。
所以就可以套用公式:(index getWidth(),0)得出(l,t)坐标,((index+1)getWidth(),getHeight())得出(r,b)坐标。

二、显示ViewGroup页面


ViewGroup练习防viewpager_第3张图片
123.png
//增加一个ViewGroup的测试页面
        View testView = View.inflate(this,R.layout.test,null);
        myViewPager.addView(testView,3);

但是这样增加了testView,testView是本事是显示了,但是它里面的子View并不会显示。
问题1.为什么子View 没有显示?
-因为没有测量
问题2.为什么没有测量,ImageView也能显示?
因为给myViewPager的一级子view(7个页面),人为指定位置,绘制交给系统处理。而子view里面的子view并没有指定位置,则需要测量。

/**
     * 父页面测量孩子,孩子又测量其孩子
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for (int i=0; i

最终显示上图结果。

测量OnMeasure()过程:


ViewGroup练习防viewpager_第4张图片
1111.png
ViewGroup练习防viewpager_第5张图片
123232.png
ViewGroup练习防viewpager_第6张图片
222222.png
ViewGroup练习防viewpager_第7张图片
33333.jpg

三、页面滑动


ViewGroup练习防viewpager_第8张图片
12121212.jpg
public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //记录点击坐标
                tempX = startXByOnTouchEvent = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:

                //记录移动一小段距离的结束坐标
                float endXByOnTouchEvent = event.getX();
                //滚动这一段距离,scrollBy方向相反,所以取负
                scrollBy(-(int) (endXByOnTouchEvent - tempX),0);
                //将结束坐标设为开始坐标
                tempX = endXByOnTouchEvent;
                break;
            case MotionEvent.ACTION_UP:

              
                break;
        }
        return true;
    }

但是这样只能单纯的移动,并没有viewpager页面切换的效果。所以需要计算松开手后的移动距离来判断是否滑动到下一页或上一页或留在当前页。


ViewGroup练习防viewpager_第9张图片
viewgroup6.png

当往左滑动距离大于屏幕的1/2时,则显示下一张。
当往右滑动距离大于屏幕的1/2时,则显示上一张。

case MotionEvent.ACTION_UP:

                //记录结束移动的坐标
                float realEndX = event.getX();
                int tempIndex = currentIndex;
                //向左滑,显示下一个页面
                if ((startXByOnTouchEvent - realEndX) >getWidth()/2){
                    tempIndex++;
                }else if ((realEndX - startXByOnTouchEvent) >getWidth()/2){   //向右滑,显示上一张
                    tempIndex--;
                }
                //根据下标位置移动到指定的页面
                scrollToPager(tempIndex);
                break;
/**
     * 跳转到指定页面
     * @param tempIndex
     */
    public void scrollToPager(int tempIndex) {
        //屏蔽非法值
        if (tempIndex<0){
            tempIndex = 0;
        }
        if (tempIndex>getChildCount() - 1){
            tempIndex = getChildCount() - 1;
        }
        //当前的坐标位置
        currentIndex = tempIndex;
        //根据位置移动到指定页面
        scrollTo(currentIndex*getWidth(),0);
    }

但是因为scrollTo是直接定位到坐标点,停留在当前页时没有回弹效果,跳转也非常生硬,所以需要为滑动页面时增加回调效果。

四、回弹效果
实现思路:


ViewGroup练习防viewpager_第10张图片
3434.jpg

1)通过即将显示页面的(l,t)坐标的l值(startX)-左上角(l,t)坐标的l值得出需要回弹的总距离backDistanceX。

ViewGroup练习防viewpager_第11张图片
454545.png

2)再设置回弹时间totaltime为500毫米
3)计算速度 = backDistanceX/totaltime
4)绿色线段表示页面在一段时间内(passtime)移动的距离distanceSmallX。
5)计算这段距离distanceSmallX = passtime*速度。
6)得出每条线段的坐标(l,t)中的l ,currX= distanceSmallX +startX
7)一直刷新当前的currX,知道滚动完毕。

/**
     * 跳转到指定页面
     * @param tempIndex
     */
    public void scrollToPager(int tempIndex) {
        //屏蔽非法值
        if (tempIndex<0){
            tempIndex = 0;
        }
        if (tempIndex>getChildCount() - 1){
            tempIndex = getChildCount() - 1;
        }
        //当前的坐标位置
        currentIndex = tempIndex;
        //根据位置移动到指定页面
//        scrollTo(currentIndex*getWidth(),0);
        //回弹总距离(backDistanceX) = 显示页面的(l,t)坐标的l - 移动后左上角坐标的l,回弹距离为-时向右回弹
        int backDistanceX = currentIndex* getWidth() - getScrollX();
        scroller.startScroller(getScrollX(),getScrollY(),backDistanceX,0);
        //刷新,onDraw()和computeScroll()方法执行
        invalidate();
       
    }

    @Override
    public void computeScroll() {
//        super.computeScroll();
        if (scroller.cuputeScroller()){
            float currX = scroller.getCurrX();
            scrollTo((int) currX,0);
            invalidate();
        }
    }
public class MyScroller {

    /**
     * 起始坐标
     */
    private float startX;
    private float startY;
    /**
     * 在x轴的移动距离
     */
    private int distanceX;
    /**
     * 在y轴的移动距离
     */
    private int distanceY;
    /**
     * 开始时间
     */
    private long startTime;
    /**
     * 是否移动完成
     * true:完成
     */
    private boolean isFinish;

    /**
     * 总的移动时间
     */
    private long totalTime = 500;
    /**
     * 移动的当前位置
     */
    private float currX ;

    /**
     * 得到当前坐标
     * @return
     */
    public float getCurrX(){
        return currX;
    }
    public void startScroller(float startX, float startY, int distanceX, int distanceY) {
        this.startX = startX;
        this.startY = startY;
        this.distanceX = distanceX;
        this.distanceY = distanceY;
        this.startTime = SystemClock.uptimeMillis();//系统开机时间
        this.isFinish = false;
    }
/**
     * true:正在移动
     * false:移动结束
     * @return
     */
    public boolean cuputeScroller(){
        if (isFinish){
            return false;
        }
        long endTime = SystemClock.uptimeMillis();
        long passTime = endTime - startTime ;

        if (passTime

五、滑动冲突/分发机制


ViewGroup练习防viewpager_第12张图片
22222222.png

问题:在scrollview里面左右滑动没有效果,只能上下滑动。
-因为点击事件在scrollview里面被消耗了,没办法回传。

解决办法:在MyViewPager里面设置拦截onInterceptTouchEvent,判断是左右滑动还是上下滑动,如果是左右滑动则return true,拦截事件触发onTouch方法,否则return false,不拦截事件,传给scroller处理。

/**
     * 如果当前方法,返回true,拦截时间,将会触发当前控件的onTouchEvent()方法
     * 如果当前方法,返回false,不拦截时间,事件继续传递给孩子
     * @param event
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        boolean result = false;//默认传给孩子

        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:

                //记录坐标
                startXByOnTouchEvent = startXByOnInterceptTouchEvent = event.getX();
                startYByOnInterceptTouchEvent = event.getY();
                Log.d("TEST","onInterceptTouchEvent == ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                //如果左右滑动的距离>上下滑动则是左右滑动,方法返回true,拦截事件,触发onTouch
                //如果上下滑动的距离>左右滑动则是上下滑动,方法返回false,不拦截事件,将事件传递给子视图

                float endX = event.getX();
                float endY = event.getY();

                float distanceX = Math.abs(endX - startXByOnInterceptTouchEvent);
                float distanceY = Math.abs(endY - startYByOnInterceptTouchEvent);
                //水平方向
                if (distanceX > distanceY && distanceX >5){
                    result = true;
                }
                //这里tempX = startXByOnTouchEvent = startXByOnInterceptTouchEvent = endX;
                //是因为在scrollview里面左右滑动时方法调用顺序是:
                // onInterceptTouchEvent == ACTION_DOWN ——>onInterceptTouchEvent == ACTION_MOVE ——>onTouchEvent == ACTION_MOVE ——>onTouchEvent == ACTION_UP
                //并没有调用onTouchEvent == ACTION_DOWN,所以不能重新刷新startXByOnTouchEvent的值,所以tempX需要重新刷新,否则在移动时会出现页面闪动,因为移动距离突然增大
                tempX = startXByOnTouchEvent = startXByOnInterceptTouchEvent = endX;
                startYByOnInterceptTouchEvent = endY;
                Log.d("TEST","onInterceptTouchEvent == ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d("TEST","onInterceptTouchEvent == ACTION_UP");
                break;
        }
        return result;
    }

分发机制说明:


ViewGroup练习防viewpager_第13张图片
23232444.png

View分发:


ViewGroup练习防viewpager_第14张图片
kk.png

ViewGroup分发:


ViewGroup练习防viewpager_第15张图片
kkk.png

六、与radioGroup结合/回调
回调接口步骤:
1.定义接口

public interface OnPageListener{
        void getPage(int index);
    }
    private OnPageListener onPageListener;

2.传递接口实例

 public void setOnPageListener(OnPageListener onPageListener) {
        this.onPageListener = onPageListener;
    }

3.调用方法

 if (null != onPageListener){
            onPageListener.getPage(currentIndex);
        }

4.回调

myViewPager.setOnPageListener(new MyViewPager.OnPageListener() {
            @Override
            public void getPage(int index) {
                radioGroup.check(index);
            }
        });

你可能感兴趣的:(ViewGroup练习防viewpager)