1、自定义 View 和自定义 ViewGroup 之间的区别
自定义父类控件需重写的方法自定义ViewonMeasure() + onDraw()自定义ViewGrouponMeasure() + onLayout()View 在Activity 中显示出来要经历测量、布局和绘制三个步骤,分别对应measure、layout、draw,其中
测量:onMeasure() 决定 View 的大小布局:onLayout()决定 View 在 ViewGroup 中的位置绘制: onDraw()决定绘制这个 View各个方法的功能都知道了,为啥自定义 View 和自定义 ViewGroup重写的方法不一样呢? 这是个人理解,因为View是布局的最小单位,是各种组件的基类,而ViewGroup继承自View,是一个布局容器,负责将控制容器中的View的位置就行(对应onLayout());
2、自定义View的绘制流程 3、View的构造方法 view的构造方法一共有四个(第四个很少用到,具体什么时候用不太清楚)
// 如果View是在Java代码里面new的,则调用第一个构造函数 public XFlowLayout(Context context) { super(context); } // 如果View是在.xml里声明的,则调用第二个构造函数 , 自定义属性是从AttributeSet传进来的 public XFlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } // 不会自动调用 , 一般是在第二个构造函数里主动调用 public XFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }4、Android坐标系的认识 Android坐标系:屏幕左上角为坐标原点,向右为 x 增大方向,向下为 y 增大方向,其 View 的位置是相对父控件而言的:
top : 子View上边界到父View上边界的距离(View 的位置通过view.getTop()获取)bottom: 子View下边界到父View上边界的距离(View 的位置通过view.getBottom()获取)left : 子View左边界到父View左边界的距离(View 的位置通过view.getLeft()获取)right : 子View右边界到父View左边界的距离(View 的位置通过view.getRight()获取)另外,还有一个概念要分清,MotionEvent中 get()和getRaw()的区别
get 是触摸点相对于其所在组件坐标系的坐标—— event.getX(); event.getY();getRow 是触摸点相对于屏幕默认坐标系的坐标—— event.getRawX(); event.getRawY();5、LayoutParams 这是一个布局参数,子View通过LayoutParams告诉父容器(ViewGroup)应该如何放置自己,将对应宽高的值传给下面方法的第三个参数(其中MATCH_PARENT的值是-1 ,WRAP_CONTENT的值是-2,若设置了具体的宽高对应的值则是大于0),重点就是将LayoutParams转换成MeasureSpec,代码逻辑如下(ViewGroup源码):
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec);//父控件的测量模式 int specSize = MeasureSpec.getSize(spec);//父控件的测量大小 int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }以上逻辑可以通过生活中的一个现象来帮助理解:
6、MeasureSpec MeasureSpec 是 View下的内部类,封装了父容器对 view 的布局上的限制,内部提供了宽高的信息( SpecMode 、 SpecSize ),SpecSize是指在某种SpecMode下的参考尺寸,其中SpecMode 有如下三种:
UNSPECIFIED:父控件没有任何限制----相当于生活事例中的第三种情况EXACTLY:父控件已经知道你所需的精确大小,你的最终大小应该就是这么大----相当于生活事例中的第一种情况AT_MOST:你的大小不能大于父控件给你指定的size,但具体是多少,得看你自己的实现----相当于生活事例中的第一种情况第一步,创建好新的类继承ViewPager,写好构造函数,重写 onMeasure() 和 onLayout()方法; 第二步:准备处理 onMeasure()的度量逻辑,在此之前要确定是先度量子View的宽高来确定父View的宽高,还是先度量父View来确定子View的宽高(目前只有ViewPager是这样),选择大势所趋的先子后父。先搭出框架:
public class XFlowLayout extends ViewGroup { public XFlowLayout(Context context) { super(context); } public XFlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } public XFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } //布局 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { } // 度量 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int childCount = getChildCount(); int paddingLeft = getPaddingLeft(); int paddingRight = getPaddingRight(); int paddingTop = getPaddingTop(); int paddingBottom = getPaddingBottom(); // 第一步:先度量子View for (int i = 0; i < childCount; i++){ View childView = getChildAt(i); // TODO 重点1 如何度量子View childView.measure(width?, height?); //获取子view的度量宽高 int childMesauredWidth = childView.getMeasuredWidth(); int childMeasuredHeight = childView.getMeasuredHeight(); // 第二步:根据子View来度量父View setMeasuredDimension(realWidth?, realHeight?); } }那么问题来了,如何度量子 View 的宽高呢?此处首先需要理清 LayoutParams 转换成MeasureSpec 的实现,代码处理如下:
// 第一步:先度量子View for (int i = 0; i < childCount; i++){ View childView = getChildAt(i); + LayoutParams childLP = childView.getLayoutParams(); + int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, + paddingLeft + paddingRight, childLP.width); + int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, + paddingTop + paddingBottom, childLP.height); + childView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //获取子view的度量宽高 int childMesauredWidth = childView.getMeasuredWidth(); int childMeasuredHeight = childView.getMeasuredHeight(); // 第二步:根据子View来度量父View setMeasuredDimension(realWidth?, realHeight?); }以上只是度量了单个子View的宽高,后续则要对行和列的累加情况做处理,代码如下:
全局变量: + private int mHorizontalSpacing = dp2px(16); //每个item横向间距 + private int mVerticalSpacing = dp2px(8); //每个item横向间距 + private List<List<View>> allLines = new ArrayList<>(); // 记录所有的行,一行一行的存储,用于layout + List<Integer> lineHeights = new ArrayList<>(); // 记录每一行的行高,用于layout onMeasure方法中的局部变量: + int parentNeededWidth = 0; // measure过程中,子View要求的父ViewGroup的宽 + int parentNeededHeight = 0; // measure过程中,子View要求的父ViewGroup的高 + int lineWidthUsed = 0; //记录这行已经使用了多宽的size + int lineHeight = 0; // 一行的行高 + List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view + int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的父亲给我的宽度 + int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的父亲给我的高度 // 第一步:先度量子View的宽高 for (int i = 0; i < childCount; i++){ View childView = getChildAt(i); LayoutParams childLP = childView.getLayoutParams(); int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width); int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height); childView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //获取子view的度量宽高 int childMesauredWidth = childView.getMeasuredWidth(); int childMeasuredHeight = childView.getMeasuredHeight(); // 行和列累加显示的处理 // 换行: 若已用宽度 + 子View宽度 + 间距 > 父控件屏幕宽度则换行 + if (childMesauredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth){ //一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来 + allLines.add(lineViews); + lineHeights.add(lineHeight); + parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing; + parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing); // 重新将下一行的计算宽高的值清零 + lineViews = new ArrayList<>(); + lineWidthUsed = 0; + lineHeight = 0; } // view 是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局 + lineViews.add(childView); //每行都会有自己的宽和高 + lineWidthUsed = lineWidthUsed + childMesauredWidth + mHorizontalSpacing; + lineHeight = Math.max(lineHeight, childMeasuredHeight); }子View的宽高度量完成后,父View则是根据子View来度量:
// 作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量 + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth: parentNeededWidth; + int realHeight = (heightMode == MeasureSpec.EXACTLY) ?selfHeight: parentNeededHeight; setMeasuredDimension(realWidth, realHeight);onMeasure()方法完成后,就是对onLayout()的显示处理:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // 从左上角开始布局 所以第一个子View的位置是相对左/上 int curL = getPaddingLeft(); int curT = getPaddingTop(); int lineCount = allLines.size(); // 行循环 for (int i = 0; i < lineCount; i++){ // 对应第i行的View List<View> lineViews = allLines.get(i); int lineHeight = lineHeights.get(i); for (int j = 0; j < lineViews.size(); j++) { // 对应i行j列的view View view = lineViews.get(j); int left = curL; int top = curT; int right = left + view.getMeasuredWidth();// 与getWidth的区别? int bottom = top + view.getMeasuredHeight(); // 实际布局处 view.layout(left,top,right,bottom); curL = right + mHorizontalSpacing; } curT = curT + lineHeight + mVerticalSpacing; curL = getPaddingLeft(); } }在这,有个知识点要注意下:getWidth() 和 getMeasuredWidth() 的区别?
方法区别getMeasuredWidth在measure()过程结束后就可以获取到对应的值;通过setMeasuredDimension()方法来进行设置的.getWidth在layout()过程结束后才能获取到;通过视图右边的坐标减去左边的坐标计算出来的.所以onLayout() 中用 getMeasuredWidth() 获取的宽高在onMeasure() 中就被初始化了,不会有问题。
以上就是整个自定义 FlowLayout 的思路流程,代码是用Java语言实现的的,后面附上了 kotlin 版本的整体代码,其中解决了一些小BUG,比如最后一行的遗漏问题、visible 状态的显示问题以及gravity 的属性控制layout问题等…
4.1 FrameLayout源码分析 onMeasure() ——遍历所有子View并记录下子View的最大宽高,最大宽高加上padding/margin作为FrameLayout自身的尺寸; ——如果FrameLayout是不确定大小的模式,子View又是MATCH_PARENT属性的,则需要重新度量这些子View
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } final int childHeightMeasureSpec; if (lp.height == LayoutParams.MATCH_PARENT) { final int height = Math.max(0, getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }onLayout() ——FrameLayout对每个子View的layout过程是相同。遍历所有子View,通过子View的gravity属性进行xy轴偏移量的计算,最后调用child.layout对子View进行布局
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }4.2 LinearLayout源码分析 分为横向和纵向度量,度量/布局的流程和逻辑与FrameLayout一样
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } }现象描述:当viewPager高度设置为wrap_content时设置高度无效 通过百度查找资料提供的解决方案是继承ViewPager自定义重写onMeasure
package com.example.practicedemo.widget.java; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.viewpager.widget.ViewPager; public class MyViewPager extends ViewPager { public MyViewPager(@NonNull Context context) { super(context); } public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height = 0; 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) height = h; } heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }经过验证发现并没有效果,查看ViewPager源码发现它是先度量父View再度量子View,和其他控件的度量方式不一样
@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. // 度量父View 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); // 度量子View 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); } } } }经过分析需要将自定义的ViewPager中的onMeasure方法再做修改
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height = 0; for(int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); - // child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + // 以上注释代码为百度解决方案,实际无效,修改如下 + // 度量子View的大小得先通过LayoutParams获取子View的大小,然后再转变成MeasureSpec + ViewGroup.LayoutParams params = child.getLayoutParams(); + child.measure(widthMeasureSpec, getChildMeasureSpec(heightMeasureSpec, 0, params.height)); int h = child.getMeasuredHeight(); if(h > height) height = h; } heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); }结果还是翻车… 查资料试着修改viewPager的PagerAdapter,结果可行,但原理并不懂,后续弄明白后再来更新
package com.example.practicedemo.widget; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ListView; import android.widget.SimpleAdapter; import androidx.viewpager.widget.PagerAdapter; import com.example.practicedemo.R; import java.util.List; import java.util.Map; public class MyPagerAdapter extends PagerAdapter { private Context mContext; private int[] mData; public MyPagerAdapter(Context context, int[] data) { mContext = context; mData = data; } @Override public int getCount() { return mData.length; } @Override public Object instantiateItem(ViewGroup container, int position) { - // View view = View.inflate(mContext, R.layout.vp_item, null); + View view = LayoutInflater.from(mContext).inflate( R.layout.vp_item, container,false); ImageView imageView = view.findViewById(R.id.iv); imageView.setImageResource(mData[position]); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } }