ViewGroup练习防viewpager

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


121.jpg

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

for (int i=0;i<ids.length;i++){
      ImageView childView = new ImageView(this);
      childView.setBackgroundResource(ids[i]);
      //添加到MyViewPager中,但是不会显示,因为viewgroup要重写onLayout
      myViewPager.addView(childView);
}

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

protected void onLayout(boolean flag, int l, int t, int r, int b) {
        //遍历每个孩子,给每个孩子指定在屏幕的坐标位置
        for (int index =0; index<getChildCount();index++){
            View childView = getChildAt(index);
            childView.layout(index*getWidth(),0,(index+1)*getWidth(),getHeight());
        }
    }

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

viewgroup4.png

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

二、显示ViewGroup页面


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<getChildCount();i++){
            View childView = getChildAt(i);
            childView.measure(widthMeasureSpec,heightMeasureSpec);
        }
    }

最终显示上图结果。

测量OnMeasure()过程:


1111.png
123232.png
222222.png
33333.jpg

三、页面滑动


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页面切换的效果。所以需要计算松开手后的移动距离来判断是否滑动到下一页或上一页或留在当前页。


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是直接定位到坐标点,停留在当前页时没有回弹效果,跳转也非常生硬,所以需要为滑动页面时增加回调效果。

四、回弹效果
实现思路:


3434.jpg

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

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<totalTime){
            //还没移动结束
            //计算平均速度 = 移动总距离/总时间
            /*float voleCity = distanceX/totalTime;*/
            //distanceSmallX是当前移动到的位置的l和startX的l的距离
            // 移动这一小段的距离 = 时间*速度
            float distanceSmallX = passTime * distanceX/totalTime;

            currX = startX + distanceSmallX;

        }else{
            //移动结束
            isFinish = true;
            currX = startX + distanceX;
        }
        return true;
    }
}

五、滑动冲突/分发机制


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;
    }

分发机制说明:


23232444.png

View分发:


kk.png

ViewGroup分发:


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);
            }
        });
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容