1 如何添加View或者ViewGroup到ViewGroup中
两种方式可以添加View或者ViewGroup到ViewGroup中,一种是xml布局文件中,一种是动态代码添加。
两个View在RelativeLayout中一左一右的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/black"
android:layout_alignParentLeft="true"
android:text="ParameterDemo1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:layout_alignParentRight="true"
android:text="ParameterDemo2" />
</RelativeLayout>
动态代码添加
RelativeLayout layout = new RelativeLayout(this);
TextView textView1 = new TextView(this);
TextView textView2 = new TextView(this);
textView1.setText("ParameterDemo1");
textView2.setText("ParameterDemo2");
RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
RelativeLayout.LayoutParams params2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params1.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
params2.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
textView1.setLayoutParams(params1);
textView2.setLayoutParams(params2);
layout.addView(textView1);
layout.addView(textView2);
动态代码添加和xml添加view,都用到了alignParentLeft,alignParentRight,WRAP_CONTENT等属性,只是表达方式不同它们意义是完全相同的。
我们知道通过xml之所以可以定义布局文件,其实是Android系统帮我们解析了xml文件,然后生成对应的对象。如果自己要解析xml文件可以用LayoutInflater的inflate()方法。
/**
* 从xml 文件生成view
* @param resource xml资源文件
* @param root 如果attachToRoot 为true则 root作为xml的parent,否则root可以为生成的view提供LayoutParams
* @param attachToRoot 是否添加到root
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
。。。。。。。。。。。。。。。。。。。。。。。。
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
内部利用root.generateLayoutParams生成layoutParams。
LayoutParams作用于View,ViewGroup告诉它们的Parents他们想怎么排布(类似宽,高,偏移,比例等),所以LayoutParams是设置给ViewGroup内部的View或ViewGroup,但最终由父ViewGroup使用,简单说LayoutParams就是View,ViewGroup在其父布局内部的位置信息。
动态添加view,设置LayoutParams的方式:
-
第一种,第二种相同:
textView2.setLayoutParams(params2);
layout.addView(textView1); -
第三种:
public void addView(View child, LayoutParams params) {
addView(child, -1, params);
} -
第四种,第五种:
public void addView(View child, int width, int height) {
final LayoutParams params = generateDefaultLayoutParams();
params.width = width;
params.height = height;
addView(child, -1, params);
}
最终都会调用:
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
所以最终都用到了LayoutParams。
2 LayoutParams
ViewGroup内的LayoutParams是所有ViewGroup的基类,layout_width,layout_height就定义在内部 FILL_PARENT ,MATCH_PARENT, WRAP_CONTENT,width,height等也都定义在内部。
几乎所有的系统提供的view都支持margin属性,LayoutParams是不支持margin的所以一般需要继承MarginLayoutParams。
public static class MarginLayoutParams extends ViewGroup.LayoutParams ;
重要的函数generateLayoutParams()函数:
如何让ViewGroup内部的View或者ViewGroup拥有parent ViewGroup希望内部子view拥有的属性,需要实现的generateLayoutParams方法。generateLayoutParams方法会生成内部子view需要的layoutParams类,这样内部子View就会使用generateLayoutParams生成的LayoutParams作为他们LayoutParams的类型,parent ViewGroup就可以从view的LayoutParams获取想要的属性。
3 LinearLayout的LayoutParams
LinearLayout内部的子View设置LayoutParams时必须使用LinearLayout.LayoutParams,我们来看一下LinearLayout.LayoutParams支持什么属性。
generateLayoutParams(attrs)
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LinearLayout.LayoutParams(getContext(), attrs);
}
**LinearLayout.LayoutParams 继承了MarginLayoutParams **
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* Indicates how much of the extra space in the LinearLayout will be
* allocated to the view associated with these LayoutParams. Specify
* 0 if the view should not be stretched. Otherwise the extra pixels
* will be pro-rated among all views whose weight is greater than 0.
*/
@ViewDebug.ExportedProperty(category = "layout")
public float weight;
/**
* Gravity for the view associated with these LayoutParams.
*
* @see android.view.Gravity
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = -1, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
@ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
@ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
@ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
@ViewDebug.IntToString(from = Gravity.START, to = "START"),
@ViewDebug.IntToString(from = Gravity.END, to = "END"),
@ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
@ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
@ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
@ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
@ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
@ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
})
public int gravity = -1;
/**
* {@inheritDoc}
*/
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a =
c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
a.recycle();
}
/**
* {@inheritDoc}
*/
public LayoutParams(int width, int height) {
super(width, height);
weight = 0;
}
/**
* Creates a new set of layout parameters with the specified width, height
* and weight.
*
* @param width the width, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param height the height, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param weight the weight
*/
public LayoutParams(int width, int height, float weight) {
super(width, height);
this.weight = weight;
}
/**
* {@inheritDoc}
*/
public LayoutParams(ViewGroup.LayoutParams p) {
super(p);
}
/**
* {@inheritDoc}
*/
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
/**
* Copy constructor. Clones the width, height, margin values, weight,
* and gravity of the source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(LayoutParams source) {
super(source);
this.weight = source.weight;
this.gravity = source.gravity;
}
@Override
public String debug(String output) {
return output + "LinearLayout.LayoutParams={width=" + sizeToString(width) +
", height=" + sizeToString(height) + " weight=" + weight + "}";
}
/** @hide */
@Override
protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
super.encodeProperties(encoder);
encoder.addProperty("layout:weight", weight);
encoder.addProperty("layout:gravity", gravity);
}
}
LinearLayout.LayoutParams 继承MarginLayoutParams 支持了margin同时还添加了
gravity ,weight属性。
onMeasure时如何获取设置的LayoutParams:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
int consumedExcessSpace = 0;
int nonSkippedChildCount = 0;
// See how tall everyone is. Also remember max width.
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;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//获取LayoutParams 读取设置的属性,进行测量
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//获取weight
totalWeight += lp.weight;
//获取宽高
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
//获取margin属性
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {