当然,在做的时候可能有很多方法,当然这也不是最好的方法。
首先、我们要熟知android界面是怎么绘制出来的。今天就简单的谈一下,我们都知道,布局容器绘制经过三个步骤,就是测量,布局,绘制,对应到代码中就是
onMeasure-----------onLayout-----------onDraw。哪具体是怎么绘制的呢?下面是系统源码。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
以LinearLayout为例,从源码可以看出,系统先判断布局方向,才开始测量。
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
lp.height = LayoutParams.WRAP_CONTENT;
}
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
lp.height = 0;
consumedExcessSpace += childHeight;
}
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
很明显:
1、系统将会拿出嵌套的容器组件的子控件,进行测量然后记录总的高度或宽度,要注意要么测量高度,要么测量宽度取决于布局方向,当然相对布局没有该方法,他是通过相对比较来绘制的。
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
2、判断非容器类控件是否是GONE。如果是就不进行测量
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
3、判断是否包含边线,如果有记录线的高度
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
4、最后才去测量,控件真正的高度。并作记录
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
5、将所得的宽和高,和最大尺寸作对比。
但这些似乎好像和正提没关系。那为什么ListView嵌套listview,或者ScrollView嵌套listview,gridview无法绘制高度呢?
原来,在设计中他只会测量非容器布局的高度,因此当这样嵌套因为都是要动态测量的所以获取不到。就达不到我们想要的效果
第一步中测量非容器布局的源码如下:
View getVirtualChildAt(int index) {
return getChildAt(index);
}
那我们怎样解决呢?
下面就让我教你怎么做:
当ListView嵌套listview,或者ScrollView嵌套listview,gridview外层的布局测量到内层发现没有非容器布局,因此高度就是自身高度,而测量又是从最内(上)层开始测量的,测量后并不给自身设置布局参数。
因此呢,内层listview是能测量到item高度的,而外层已经限制了宽和高,所以就不能达到效果。为了达到效果我们要让外层容器认为内层是个非容器布局,是一个有固定高度的容器。
具体我们就要必须给内层容器设置布局参数,具体如下:这是封装后的listview,解决办法,没封装,当然就不需要holder了
public class LvHeightUtil {
public static void setListViewHeightBasedOnChildren(ListView listView, BaseHolder holder) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
// View listItem = listAdapter.getView(i, holder.mItemRootView, listView);
// listItem.measure(0, 0);
// totalHeight += listItem.getMeasuredHeight();
totalHeight +=150;//如果是lv嵌套lv某种条件下需要固定item高度,不然测不出来。
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = UIUtil.dip2px(totalHeight)
+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
public static void setGridViewHeightBasedOnChildren(GridView gridView ,BaseHolder holder) {
ListAdapter adapter = gridView.getAdapter();
if (adapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < adapter.getCount()/2; i++) {
View listItem = adapter.getView(i, holder.mItemRootView, gridView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = gridView.getLayoutParams();
params.height = totalHeight;
gridView.setLayoutParams(params);
}
}
listView源码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childWidth = 0;
int childHeight = 0;
int childState = 0;
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0, mIsScrap);
// Lay out child directly against the parent measure spec so that
// we can obtain exected minimum width and height.
measureScrapChild(child, 0, widthMeasureSpec, heightSize);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
}
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState & MEASURED_STATE_MASK);
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}
看了这些,相信你也能解决这些小问题了。