LayoutParams详解
非常好的参考:
https://www.jianshu.com/p/36b200a0bff4
动态布局:
如果想要代码动态写出上面的布局,就需要使用 LayoutParams 这个关键类了
LayoutParams 的作用是:子控件告诉父控件,自己要如何布局。
在添加到父布局时,设置 LayoutParams,通知父布局如何摆放自己
<span style="color:#cccccc"><code class="language-java">ll.<span style="color:#f08d49">addView</span>(vt, layoutParams);<span style="color:#999999">// 在添加到父布局的时候</span></code></span>
利用setLayoutParams方法对控件的layout进行布局更新
<span style="color:#000000"><span style="color:#cccccc"><code class="language-css">textView.<span style="color:#f08d49">setLayoutParams</span>(textParams);</code></span></span>
<span style="color:#000000"><span style="color:#cccccc"><code class="language-csharp"> <span style="color:#f8c555">RelativeLayout<span style="color:#cccccc">.</span>LayoutParams</span> <span style="color:#cc99cd">params</span> <span style="color:#67cdcc">=</span> <span style="color:#cc99cd">new</span> <span style="color:#f8c555">RelativeLayout<span style="color:#cccccc">.</span>LayoutParams</span>(mVgLp);
<span style="color:#cc99cd">params</span>.<span style="color:#f08d49">addRule</span>(RelativeLayout.ALIGN_PARENT_BOTTOM);
<span style="color:#cc99cd">params</span>.<span style="color:#f08d49">addRule</span>(RelativeLayout.ALIGN_PARENT_RIGHT);
mIv.<span style="color:#f08d49">setLayoutParams</span>(<span style="color:#cc99cd">params</span>);</code></span></span>
MATCH_PARENT
,而 TextView 使用 WRAP_CONTENT
,至于为什么,我们要分析一下源码。
ViewGroup里面的addview();
所以测量由父类控制饿了
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
动态设置view的padding
private void changesize(){
if(weeklyRewardObtainTv.getText()!=null&&weeklyRewardObtainTv.getText().toString()!=null&&weeklyRewardObtainTv.getText().toString().length()>=10){
int left=weeklyRewardObtainTv.getPaddingLeft();
int top=weeklyRewardObtainTv.getPaddingTop();
int bottom=weeklyRewardObtainTv.getPaddingBottom();
int right=weeklyRewardObtainTv.getPaddingRight();
weeklyRewardObtainTv.setPadding(DensityUtil.dip2px(ActivityWeeklyReward.this,53),top,right,bottom);
}
}
虽然setContentView()方法大家都会用,但实际上Android界面显示的原理要比我们所看到的东西复杂得多。任何一个Activity中显示的界面其实主要都由两部分组成,标题栏和内容布局。标题栏就是在很多界面顶部显示的那部分内容,比如刚刚我们的那个例子当中就有标题栏,可以在代码中控制让它是否显示。而内容布局就是一个FrameLayout,这个布局的id叫作content,我们调用setContentView()方法时所传入的布局其实就是放到这个FrameLayout中的,这也是为什么这个方法名叫作setContentView(),而不是叫setView()。
最后再附上一张Activity窗口的组成图吧,以便于大家更加直观地理解:
LayoutInflater的基本用法有2种:
1:LayoutInflater layoutInflater = LayoutInflater.from(context);
2 :LayoutInflater layoutInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
第一种方法是第二种的封装简写。
得到了LayoutInflater的实例之后就可以调用它的inflate()方法来加载布局了,:layoutInflater.inflate(resourceId, root);
那LayoutInflater 是如何工作的呢 ?
LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。
public class LayoutRunHealthCheck extends FrameLayout implements View.OnClickListener {
private LinearLayout textViewClose;
private HealthAnimationCircleProgressBar healthAnimationCircleProgressBar;
private TextView tv_body_socre, tv_body_des, tv_body_detail, tv_start;
private TextView tv_face_score;
private SimpleDraweeView simpleDraweeView;
private RelativeLayout layoutStart;
private void buildView() {
View view = LayoutInflater.from(context).inflate(R.layout.layout_run_health, null);
textViewClose = view.findViewById(R.id.tv_close);
tv_body_socre = view.findViewById(R.id.tv_body_socre);
tv_body_des = view.findViewById(R.id.tv_body_des);
tv_body_detail = view.findViewById(R.id.tv_body_detail);
tv_start = view.findViewById(R.id.tv_start);
layoutStart = view.findViewById(R.id.layout_start);
simpleDraweeView = view.findViewById(R.id.body_feature);
tv_face_score = view.findViewById(R.id.tv_face_score);
healthAnimationCircleProgressBar = view.findViewById(R.id.cpb_face_score);
textViewClose.setOnClickListener(this);
layoutStart.setOnClickListener(this);
view.setPadding(0, 0, 0, DensityUtil.dip2px(context, 0));
this.addView(view);
iniView();
setData();
}
2.布局里面设置
<LinearLayout
android:id="@+id/frame_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"></LinearLayout>
3.在代码里面动态添加:
frameLayout = bottomView.findViewById(R.id.frame_layout);
addHealthView();
if (layoutRunHealthCheck == null && activity != null) {
layoutRunHealthCheck = new LayoutRunHealthCheck(activity, type, healthListener);
frameLayout.addView(layoutRunHealthCheck);
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
发现merge,include标签,tag标签也在这个类里面
public abstract class LayoutInflater {
private static final String TAG = LayoutInflater.class.getSimpleName();
private static final boolean DEBUG = false;
/** Empty stack trace used to avoid log spam in re-throw exceptions. */
private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
/**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected final Context mContext;
// these are optional, set by the caller
private boolean mFactorySet;
private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;
private Filter mFilter;
final Object[] mConstructorArgs = new Object[2];
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
new HashMap<String, Constructor<? extends View>>();
private HashMap<String, Boolean> mFilterMap;
private TypedValue mTempValue;
private static final String TAG_MERGE = "merge";
private static final String TAG_INCLUDE = "include";
private static final String TAG_1995 = "blink";
private static final String TAG_REQUEST_FOCUS = "requestFocus";
private static final String TAG_TAG = "tag";
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
@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);
setMeasuredDimension:测量自己
LayoutParams:子空间的参数测量
MeasureSpec.makeMeasureSpec(widthSize, widthMode); 测量的计算模式
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);
MeasureSpec.makeMeasureSpec(widthSize, widthMode);