ViewPager中wrap_content失效

问题描述:
在开发中,经常遇到ViewPager中设置了高度wrap_content时,子布局的高度有可能会失效,或者match_parent。
解决方案:
1.直接修改wrap_content为一个固定的高度。但是这个做法将高度写死了,会产生一些不同屏幕分辨率的适配问题。
2.重写VIewPage中的onMeasure方法。查看了网上的大部分博客,一般都是采用了以下方法。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       int height = 0;
       //下面遍历所有child的高度
       for (int i = 0; i < getChildCount(); i++) {
           View child = getChildAt(i);
           child.measure(widthMeasureSpec,
                   MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
           int h = child.getMeasuredHeight();
           if (h > height) //采用最大的view的高度。
               height = h;
       }
       heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
               MeasureSpec.EXACTLY);
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

深入分析:
在一个视频中,偶然看到了关于这个的深入分析。
为什么会失效呢:查看ViewPage的源代码中onMuasure方法。

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       // For simple implementation, our internal size is always 0.
       // We depend on the container to specify the layout size of
       // our view.  We can't really know what it is since we will be
       // adding and removing different arbitrary views and do not
       // want the layout to change as this happens.
       setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
               getDefaultSize(0, heightMeasureSpec));

       final int measuredWidth = getMeasuredWidth();
       final int maxGutterSize = measuredWidth / 10;
       mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

       // Children are just made to fill our space.
       int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
       int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

       /*
        * Make sure all children have been properly measured. Decor views first.
        * Right now we cheat and make this less complicated by assuming decor
        * views won't intersect. We will pin to edges based on gravity.
        */
       int size = getChildCount();
       for (int i = 0; i < size; ++i) {
           final View child = getChildAt(i);
           if (child.getVisibility() != GONE) {
               final LayoutParams lp = (LayoutParams) child.getLayoutParams();
               if (lp != null && lp.isDecor) {
                   final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                   final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                   int widthMode = MeasureSpec.AT_MOST;
                   int heightMode = MeasureSpec.AT_MOST;
                   boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
                   boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;

                   if (consumeVertical) {
                       widthMode = MeasureSpec.EXACTLY;
                   } else if (consumeHorizontal) {
                       heightMode = MeasureSpec.EXACTLY;
                   }

                   int widthSize = childWidthSize;
                   int heightSize = childHeightSize;
                   if (lp.width != LayoutParams.WRAP_CONTENT) {
                       widthMode = MeasureSpec.EXACTLY;
                       if (lp.width != LayoutParams.MATCH_PARENT) {
                           widthSize = lp.width;
                       }
                   }
                   if (lp.height != LayoutParams.WRAP_CONTENT) {
                       heightMode = MeasureSpec.EXACTLY;
                       if (lp.height != LayoutParams.MATCH_PARENT) {
                           heightSize = lp.height;
                       }
                   }
                   final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
                   final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
                   child.measure(widthSpec, heightSpec);

                   if (consumeVertical) {
                       childHeightSize -= child.getMeasuredHeight();
                   } else if (consumeHorizontal) {
                       childWidthSize -= child.getMeasuredWidth();
                   }
               }
           }
       }

       mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
       mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

       // Make sure we have created all fragments that we need to have shown.
       mInLayout = true;
       populate();
       mInLayout = false;

       // Page views next.
       size = getChildCount();
       for (int i = 0; i < size; ++i) {
           final View child = getChildAt(i);
           if (child.getVisibility() != GONE) {
               if (DEBUG) {
                   Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);
               }

               final LayoutParams lp = (LayoutParams) child.getLayoutParams();
               if (lp == null || !lp.isDecor) {
                   final int widthSpec = MeasureSpec.makeMeasureSpec(
                           (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
                   child.measure(widthSpec, mChildHeightMeasureSpec);
               }
           }
       }
   }
   该方法中首先就调用了 
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
             getDefaultSize(0, heightMeasureSpec));

这是先对自身进行测量,而没有先去测量子View的高度,这时候传入的高度并不是子View的高度。而修改后的方法,就是先去测量子View的高度,然后在调用 super.onMeasure方法,并且把子View的高度传入进去做参数。
而在后续中,发现这个修改并不是一直有效的,也会失效。问题就出在

child.measure(widthMeasureSpec,
                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

这里调用的measure 方法,并且在MeasureSpec.makeMeasureSpec中传入了参数0,和一个 MeasureSpec.UNSPECIFIED(可以解释为我也不知道,由系统决定)参数。
这里我们用measure方法是不可取的,应该使用LayoutParams(获取XML的布局参数),首先去获取子View的Params,然后再从将值传入到measure中。因此修改为

ViewGroup.LayoutParams params=child.getLayoutParams();
child.measure(widthMeasureSpec,getChildMeasureSpec(heightMeasureSpec,0,params.height))

但是发现这样修改后依旧失效。这里就涉及到XML文件解析的时候的反射问题了,因此我们修改下VIewpage适配器中的的反射的方法
将View view= LayoutInflater.from(mContext).inflate(R.layou.item,null);修改为
View view= LayoutInflater.from(mContext).inflate(R.layou.item,viewGroup,false);。

附上代码

public class mPageAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return false;
    }
    @Override
    public Object instantiateItem(ViewGroup viewGroup,int position){

        //....
        //修改前
        //    View view= LayoutInflater.from(mContext).inflate(R.layou.item,null);

         //修改后
        View view= LayoutInflater.from(mContext).inflate(R.layou.item,viewGroup,false);
        //...
    }
}
public class mViewPage extends ViewPager {
    public mViewPage(@NonNull Context context) {
        super(context);
    }

    public mViewPage(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = 0;
        //下面遍历所有child的高度
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);

//修改后
            ViewGroup.LayoutParams params=child.getLayoutParams();
            child.measure(widthMeasureSpec,getChildMeasureSpec(heightMeasureSpec,0,params.height));


//         修改前
//            child.measure(widthMeasureSpec,
//                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

            int h = child.getMeasuredHeight();
            if (h > height) //采用最大的view的高度。
                height = h;
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

}

总结:
现在的ViewPage一般都是被用来搭配使用Fragment 或者Banner使用,但是ViewPage开发的最初目的不是用于这两个,因此在绘制的过程中先绘制了自身,而我们在ViewGroup的绘制中,一般是自下而上的绘制,先获取子View的LayoutParams信息。

新手第一次写总结类的博客,有不对的地方请大家指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ViewPager嵌套ListView时,可能会遇到ListView无法滑动的问题,这是因为ViewPager会拦截ListView的滑动事件。解决方法如下: 1. 自定义ListView,重写其onInterceptTouchEvent()方法,返回false,让ViewPager不拦截ListView的滑动事件。 ``` public class MyListView extends ListView { public MyListView(Context context) { super(context); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); } public MyListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: setParentScrollAble(false); break; case MotionEvent.ACTION_UP: setParentScrollAble(true); break; } return super.onInterceptTouchEvent(ev); } private void setParentScrollAble(boolean flag) { getParent().requestDisallowInterceptTouchEvent(!flag); } } ``` 2. 在ViewPager的适配器,将ListView所在的布局设置为android:descendantFocusability="blocksDescendants",防止ListView获取焦点而导致ViewPager无法滑动。 ``` <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:descendantFocusability="blocksDescendants"> <com.example.MyListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> ``` 以上两种方法都可以解决ViewPagerListView失效的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值