preface
By learning the source code of Android official Layout, you can help yourself to better understand the Android UI framework system, understand the internal convenient encapsulation good API call, help to Layout optimization and custom view implementation and other work. Here the learning results through writing blog summary, easy to remember, not forgotten in the future.
The source code in this blog is based on Android 8.1
RelativeLayout characteristics
RelativeLayout is one of the most commonly used layouts in Android development. It supports relative placement among child views. You can specify sibling views as anchor points for each view.
To do this, presumably, RelativeLayout supports the ability for subviews to set relative placement rules for LayoutParams, as well as the ability to calculate relative positions and dependencies between subviews during measurement and layout.
The source code to explore
The constructor
public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
// Initializes the custom attributes
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
// Set the compatibility mode flag bit
queryCompatibilityModes(context);
}
Copy the code
The constructor for a RelativeLayout does two things: initialize properties and set compatibility tags.
Initialize properties
private void initFromAttributes(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.RelativeLayout, defStyleAttr, defStyleRes);
// Subview ID that is not affected by mGravity. Default is not ignored.
mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID);
/ / alignment, the default Gravity. START | Gravity. The TOP.
mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity);
a.recycle();
}
Copy the code
RelativeLayout allows you to set the ignoreGravity and gravity properties, which override the relative position property of the child view, and ignoreGravity allows you to set the child view independent of the gravity property.
For example:
- Although the child view sets the layout_alignParentLeft property, the RelativeLayout property sets the gravity property
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="right">
<View
android:id="@+id/view"
android:layout_width="160dp"
android:layout_height="100dp"
android:layout_alignParentLeft="true"
android:background="@android:color/holo_blue_dark"/>
</RelativeLayout>
Copy the code
- RelativeLayout sets the ignoreGravity property
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ignoreGravity="@+id/view"
android:gravity="right">
<View
android:id="@+id/view"
android:layout_width="160dp"
android:layout_height="100dp"
android:layout_alignParentLeft="true"
android:background="@android:color/holo_blue_dark"/>
</RelativeLayout>
Copy the code
Set compatibility flags
private void queryCompatibilityModes(Context context) {
int version = context.getApplicationInfo().targetSdkVersion;
// Android4.2 = true (if not, remember that this variable is false)
mAllowBrokenMeasureSpecs = version <= Build.VERSION_CODES.JELLY_BEAN_MR1;
// Android4.3 is true (if you don't consider the lower version, remember that this variable is true)
mMeasureVerticalWithPaddingMargin = version >= Build.VERSION_CODES.JELLY_BEAN_MR2;
}
Copy the code
- mAllowBrokenMeasureSpecs
Compatibility hack. Old versions of the platform had problems with MeasureSpec value overflow and RelativeLayout was one source of them. Some apps came to rely on them. 🙁
On versions no less than Android4.3, when a RelativeLayout generates MeasureSpec during the measurement phase, the measurement mode of the generated MeasureSpec can also be UNSPECIFIED if the measurement mode passed in is UNSPECIFIED.
- MMeasureVerticalWithPaddingMargin official comments:
Compatibility hack. Old versions of the platform would not take margins and padding into account when generating the height measure spec for children during the horizontal measure pass.
On versions no lower than Android4.3, the padding and margin are calculated when RelativeLayout generates a height MeasureSpec for the child view during the measurement phase.
LayoutParams
Static inner class LayoutParams inherited from MarginLayoutParams is defined within RelativeLayout. There are some important member variables:
// Set the relative position rule property value of child (capacity 22)
private int[] mRules = new int[VERB_COUNT];
private int[] mInitialRules = new int[VERB_COUNT];
// Constraints on the upper left, lower right and lower left boundaries of the child
private int mLeft, mTop, mRight, mBottom;
// layout_alignWithParentIfMissing property value
public boolean alignWithParent;
Copy the code
- MRules description layout_toLeftOf, layout_alignLeft and other properties are called rules, mRules array is used to store the values of relative position rule properties set by sub-views, different subscript indexes store the corresponding properties, there are a total of 22 position rule properties. Look at the property initialization section of the LayoutParams constructor:
public LayoutParams(Context c, AttributeSet attrs) {
// omit part
/ /...
final int[] rules = mRules;
//noinspection MismatchedReadAndWriteOfArray
final int[] initialRules = mInitialRules;
final int N = a.getIndexCount();
// Iterate through the TypedArray and parse out the property values in turn.
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
// Note that many cases are omitted from the switch block, and only some representative examples are listed.
switch (attr) {
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
alignWithParent = a.getBoolean(attr, false);
break;
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
// The view ID of the layout_toLeftOf attribute is stored in index 0 of the Rules array.
rules[LEFT_OF] = a.getResourceId(attr, 0);
break;
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
// The value obtained here is the Boolean corresponding to the layout_alignParentLeft attribute, press true=>-1, false=>0,
// The conversion to an integer is stored at index 9 of the Rules array.
rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false)? TRUE :0;
break;
}
}
mRulesChanged = true;
System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT);
a.recycle();
}
Copy the code
As can be seen, if the attribute value is the VIEW ID, it is saved in the array index position corresponding to the attribute. If the attribute value is a Boolean value, it is converted to -1 or 0 before saving. An index value of 0 for mRules after all properties are parsed indicates that the property value is not set or is set to false for a child view.
The diagram above shows all the rule properties, in order, corresponding to the 22 elements of the mRules array.
- The values of l, T, r, and B in the mLeft, mTop, mRight, mBottom, and layout phases are similar to those of l, T, R, and B in the mLeft, mTop, mRight, mBottom, and layout phases. The default value is VALUE_NOT_SET, and the value of VALUE_NOT_SET constant is integ.min_value. These four member variable values are determined in the Measure phase and used directly in the Layout phase.
So let’s say we have view A with the layout_below property set to the ID of View B. Then view A’s layoutparams. mTop will be changed to View B’s layoutparams. mBottom.
- AlignWithParent layout_alignWithParentIfMissing property, when true, the parent is used as the anchor if the anchor does not exist or if the anchor’s visibility is GONE.
Given A layout_alignRight B, when B is set to GONE, the right side of A is aligned against the right side of the parent layout RelativeLayout.
Relative position dependency diagram
RelativeLayout generates dependency diagrams during the measurement phase based on the mRules of each child’s LayoutParams to represent the position dependencies of each child, facilitating calculation of size and position constraints.
Layout as shown in the figure:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/A"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@android:color/holo_red_light"/>
<View
android:id="@+id/B"
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_toRightOf="@+id/A"
android:background="@android:color/holo_blue_dark"/>
<View
android:id="@+id/C"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_below="@+id/A"
android:background="@android:color/holo_purple"/>
<View
android:id="@+id/D"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_below="@+id/B"
android:layout_alignRight="@+id/B"
android:background="@android:color/holo_blue_bright"/>
</RelativeLayout>
Copy the code
Expressed in the dependency graph:
The position of D depends on B, the position of B and C depends on A, and A has no dependence. Views that depend on the current node are called adjoint nodes, views that are dependent on the current node are called anchor nodes, and anchor nodes that do not depend on other views are called root anchor nodes.
In this legend, A is the root anchor point and the anchor point of B and C, and B and C are accompanied by A. B is the anchor of D, and D is the companion of B.
Given the concept of a dependency graph, you need to represent it in code as a data structure:
Node Node
A view is represented by a Node.
static class Node {
/** * The view representing this node in the layout. */
View view;
/** * The list of dependents for this node; a dependent is a node * that needs this node to be processed first. */
final ArrayMap<Node, DependencyGraph> dependents =
new ArrayMap<Node, DependencyGraph>();
/** * The list of dependencies for this node. */
final SparseArray<Node> dependencies = new SparseArray<Node>();
/ /...
}
Copy the code
- View: Holds a view reference
- Dependents: Holds a view that is dependent on me
- Dependencies: Stores a view that I depend on
Dependency graph DependencyGraph
A RelativeLayout generates a DependencyGraph.
private static class DependencyGraph {
/** * List of all views in the graph. */
private ArrayList<Node> mNodes = new ArrayList<Node>();
/** * List of nodes in the graph. Each node is identified by its * view id (see View#getId()). */
private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
/** * Temporary data structure used to build the list of roots * for this graph. */
private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
/ /...
}
Copy the code
- MNodes: A collection of all child nodes within a RelativeLayout
- MKeyNodes: A collection of all children within a RelativeLayout that have a view ID as the key
- MRoots: Collection of all root anchors within a RelativeLayout
Description of important methods in DependencyGraph:
- FindRoots get all root anchor points
// The rulesFilter parameter represents a set of rule indexes corresponding to those in the Layoutparams.mrules array.
// For example, when toLeftOf rules are passed in, the index value is 0, corresponding to the position of mRules[0].
private ArrayDeque<Node> findRoots(int[] rulesFilter) {
final SparseArray<Node> keyNodes = mKeyNodes;
final ArrayList<Node> nodes = mNodes;
final int count = nodes.size();
// Find roots can be invoked several times, so make sure to clear
// all dependents and dependencies before running the algorithm
// First walk through the node to empty the old data.
for (int i = 0; i < count; i++) {
final Node node = nodes.get(i);
node.dependents.clear();
node.dependencies.clear();
}
// Builds up the dependents and dependencies for each node of the graph
// Walk through nodes to build dependencies.
for (int i = 0; i < count; i++) {
final Node node = nodes.get(i);
// Select mRules from LayoutParams of view corresponding to node (mRules store values of rule attributes)
final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
final int[] rules = layoutParams.mRules;
final int rulesCount = rulesFilter.length;
// Look only the the rules passed in parameter, this way we build only the
// dependencies for a specific set of rules
// Iterates through the regular index array of input parameters, initializes the anchor and adjoint sets for each node.
for (int j = 0; j < rulesCount; j++) {
// Element values in rulesFilter are indexes of mRules arrays, and corresponding element values are retrieved from rules according to the indexes.
final int rule = rules[rulesFilter[j]];
if (rule > 0) {
// If rule is greater than 0, child has set the corresponding rule attribute. For example, if child sets the layout_toLeftOf attribute, the rule value is the view ID set.
// The node this node depends on
// Get the node according to the view ID, which is the anchor point of the current child.
final Node dependency = keyNodes.get(rule);
// Skip unknowns and self dependencies
if (dependency == null || dependency == node) {
continue;
}
// Add the current node as a dependent
// The anchor node records the accompanying node
dependency.dependents.put(node, this);
// Add a dependency to the current node
// Record anchor points with nodesnode.dependencies.put(rule, dependency); }}}// Empty the old data of the root anchor collection
final ArrayDeque<Node> roots = mRoots;
roots.clear();
// Finds all the roots in the graph: all nodes with no dependencies
// Initializes the set of root anchors by traversing all nodes.
for (int i = 0; i < count; i++) {
final Node node = nodes.get(i);
// If the node does not depend on any other node, add it to the root anchor set.
if (node.dependencies.size() == 0) roots.addLast(node);
}
// return the collection of root anchors
return roots;
}
Copy the code
In this method, the old data is first emptied. The node is then iterated over to populate its dependents and Dependencies collection. Finally, the nodes are traversed again, and the nodes that are not dependent on any other nodes are added to the root anchor collection. You can see that in this method, you have completed the construction of the RelativeLayout child node dependencies.
- GetSortedViews sorts all nodes according to the specified relative position relation rule
// Sorted is used to store sorted results. Before it is passed, an array is created based on the number of nodes.
// The rulesFilter parameter represents a set of rule indexes corresponding to those in the Layoutparams.mrules array.
// For example, when toLeftOf rules are passed in, the index value is 0, corresponding to the position of mRules[0].
void getSortedViews(View[] sorted, int. rules) {
// Get all the root anchor points for traversal starting from the root anchor points.
final ArrayDeque<Node> roots = findRoots(rules);
// Used to record the number of nodes traversed and the array index, used at the end to determine whether there is a ring in the graph.
int index = 0;
Node node;
// Retrieve the last element of the root anchor set.
while((node = roots.pollLast()) ! =null) {
final View view = node.view;
final int key = view.getId();
// View reference of the storage node.
sorted[index++] = view;
// Retrieve the adjoint set of this node
final ArrayMap<Node, DependencyGraph> dependents = node.dependents;
final int count = dependents.size();
// Iterate over the adjoint node
for (int i = 0; i < count; i++) {
final Node dependent = dependents.keyAt(i);
final SparseArray<Node> dependencies = dependent.dependencies;
// Remove the current root anchor itself from the accompanying node's set of anchors
dependencies.remove(key);
if (dependencies.size() == 0) {
// If the accompanying node does not depend on other nodes after removing the root anchor, it will be added to the root anchor set as a new root anchor.roots.add(dependent); }}}// After traversing the root anchor set above, all nodes are stored in sorted array in dependent order.
RelativeLayout does not allow cyclic dependencies between children.
if (index < sorted.length) {
throw new IllegalStateException("Circular dependencies cannot exist"
+ " in RelativeLayout"); }}Copy the code
The legend illustrates the sorting process:
OnMeasure measurement
The measurements within a RelativeLayout can be roughly divided into the following stages:
- Build the dependency diagram
- Initialize auxiliary calculation variable parameters
- Calculate the horizontal measurement specification
- Calculate the vertical measurement specification
- Adjust width and height in wrap_content case
- Adjust the position based on the Gravity property
- Sets the width of the RelativeLayout itself
Build the dependency diagram
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Check whether it has been built. MDirtyHierarchy is set to True again when the requestLayout method is called.
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
// Sort children by a specific rule.
sortChildren();
}
/ /...
}
Copy the code
The key logic is in sortChildren method:
private void sortChildren(a) {
/ / initialize mSortedVerticalChildren and mSortedHorizontalChildren array, used to hold child after sorting.
final int count = getChildCount();
if (mSortedVerticalChildren == null|| mSortedVerticalChildren.length ! = count) { mSortedVerticalChildren =new View[count];
}
if (mSortedHorizontalChildren == null|| mSortedHorizontalChildren.length ! = count) { mSortedHorizontalChildren =new View[count];
}
/ / empty DependencyGraph
final DependencyGraph graph = mGraph;
graph.clear();
// Add child to the DependencyGraph to initialize the DependencyGraph data.
for (int i = 0; i < count; i++) {
graph.add(getChildAt(i));
}
// Sort the children according to a specific rule.
graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
}
Copy the code
This method will initialize the dependency graph DependencyGraph, then prioritize, child to save the result in mSortedVerticalChildren and mSortedHorizontalChildren array.
In the DependencyGraph. Add method, save all child nodes using the add method:
void add(View view) {
final int id = view.getId();
// Fetch an instance of Node from the object cache pool. Node will hold the view reference.
final Node node = Node.acquire(view);
// If the view has a set ID, save it to mKeyNodes according to the ID.
if(id ! = View.NO_ID) { mKeyNodes.put(id, node); }// mNodes saves all nodes.
mNodes.add(node);
}
Copy the code
The graph.getSortedViews method, described earlier, sorts children according to the specified relative position rule. Here two sets of rules are passed: RULES_VERTICAL and RULES_HORIZONTAL:
// the vertical relative position-dependent rules correspond to the index of the layoutparams. mRules array [2,3,4,6,8].
private static final int[] RULES_VERTICAL = {
ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM
};
// the horizontal relative position-dependent rules correspond to the indexes of the layoutparams.mrules array [0,1,5,7,16,17,18,19].
private static final int[] RULES_HORIZONTAL = {
LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END
};
Copy the code
Here the child according to the vertical and horizontal relative location of two sets of rules were sorted, the results are saved in mSortedVerticalChildren and mSortedHorizontalChildren array.
Preparation of auxiliary variable parameters
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/ /...
// Temporary dimensions for RelativeLayout during measurement
int myWidth = -1;
int myHeight = -1;
// RelativeLayout for final sizing
int width = 0;
int height = 0;
// Parse out the pattern and dimensions from the measurement specifications within the RelativeLayout
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Record our dimensions if they are known;
// myWidth, myHeight record EXACTLY or AT_MOST semi-exact size
if(widthMode ! = MeasureSpec.UNSPECIFIED) { myWidth = widthSize; }if(heightMode ! = MeasureSpec.UNSPECIFIED) { myHeight = heightSize; }// width, height Record the specific size
if (widthMode == MeasureSpec.EXACTLY) {
width = myWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = myHeight;
}
View ignore = null;
// Pull the horizontal axis alignment bit from mGravity
int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
// Flag whether the horizontal axis alignment is set (START is the default alignment, if set can also be ignored, so exclude START)
final booleanhorizontalGravity = gravity ! = Gravity.START && gravity ! =0;
// Retrieve the bits aligned with the vertical stationary correlation
gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
// Flag whether vertical axis alignment is set (TOP is the default alignment and can be ignored if set, so TOP is excluded)
final booleanverticalGravity = gravity ! = Gravity.TOP && gravity ! =0;
// Record the loosest left, top, right, bottom four constraints. During measurement, the smallest upper left margin and the largest lower right margin are compared, so the default values are set to MAX_VALUE and MIN_VALUE.
int left = Integer.MAX_VALUE;
int top = Integer.MAX_VALUE;
int right = Integer.MIN_VALUE;
int bottom = Integer.MIN_VALUE;
// Flag whether a child has the layout_alignParentEnd property set
boolean offsetHorizontalAxis = false;
// Flags whether a child has the layout_alignParentBottom attribute set
boolean offsetVerticalAxis = false;
if((horizontalGravity || verticalGravity) && mIgnoreGravity ! = View.NO_ID) {// Get a child that ignores the relativelayout. gravity constraint
ignore = findViewById(mIgnoreGravity);
}
// Flag the RelativeLayout dimensions to be unambiguous
final booleanisWrapContentWidth = widthMode ! = MeasureSpec.EXACTLY;final booleanisWrapContentHeight = heightMode ! = MeasureSpec.EXACTLY;// We need to know our size for doing the correct computation of children positioning in RTL
// mode but there is no practical way to get it instead of running the code below.
// So, instead of running the code twice, we just set the width to a "default display width"
// before the computation and then, as a last pass, we will update their real position with
// an offset equals to "DEFAULT_WIDTH - width".
// Get the layout direction, RTL or LTR
final int layoutDirection = getLayoutDirection();
if (isLayoutRtl() && myWidth == -1) {
// If the layout direction is from right to left and the width is unknown, set a default width.
myWidth = DEFAULT_WIDTH;
}
/ /...
}
Copy the code
Horizontal dependent constraint measurement
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/ /...
// Get an array of children sorted by horizontal rules.
View[] views = mSortedHorizontalChildren;
int count = views.length;
for (int i = 0; i < count; i++) {
View child = views[i];
if(child.getVisibility() ! = GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams();// Get layoutparams. mRules, used to get all rule property values set by child.
// The attribute value is also adjusted according to the layout direction, and the view ID of toStartOf, toEndOf, alignStart, alignEnd, alignParentStart, alignParentEnd is changed to
// Corresponding toLeftOf, toRightOf, alignLeft, alignRight, alignParentLeft, alignParentRight.
//
int[] rules = params.getRules(layoutDirection);
// Adjust the left and right boundary constraints of Child's LayoutParams according to the rules.
applyHorizontalSizeRules(params, myWidth, rules);
// Distribute the child measurement
measureChildHorizontal(child, params, myWidth, myHeight);
// Get the measured width of the child after measuring with the child, adjust the left and right boundaries of the child again, and return whether the layout_alignParentEnd property is set.
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true; }}}/ /...
}
Copy the code
This phase first adjusts the left and right boundary constraints of the child and makes a child distribution measurement. Note that this measurement only guarantees that the width measurement is accurate.
Enter the applyHorizontalSizeRules method and see how a RelativeLayout bounds a child:
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
RelativeLayout.LayoutParams anchorParams;
// VALUE_NOT_SET indicates a "soft requirement" in that direction. For example:
// left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it
// wants to the right
// left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it
// wants to the left
// left=10, right=20 means the left and right ends are both fixed
childParams.mLeft = VALUE_NOT_SET;
childParams.mRight = VALUE_NOT_SET;
// Get the anchor view's LayoutParams (if any) of the child's toLeftOf.
The getRelatedViewParams method will be found from the DependencyGraph, based on LEFT_OF from rules pairs
This DependencyGraph contains the view ID and the node mapping set
// Can be easily found. If the anchor point found is GONE, then find the anchor point of the anchor point.
anchorParams = getRelatedViewParams(rules, LEFT_OF);
if(anchorParams ! =null) {
// Restrict the right margin of child to the left margin of the anchor point, and subtract margin.
childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
childParams.rightMargin);
} else if(childParams.alignWithParent && rules[LEFT_OF] ! =0) {
// It is possible that the anchors are all GONE, or the child does not set toLeftOf rule property.
// If toLeftOf and alignWithParentIfMissing are set for child.
// The right boundary constraint is applied if the width of the RelativeLayout is UNSPECIFIED. Otherwise, it is required
// Wait until the child measurement is complete, then constrain.
if (myWidth >= 0) {
// Constrain the right margin of the Child to be the right margin of the RelativeLayout, minus the padding and margin.childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; }}// Get child's toRightOf anchor LayoutParams
anchorParams = getRelatedViewParams(rules, RIGHT_OF);
if(anchorParams ! =null) {
// Constrain the left margin of child to be the right margin of the anchor point, plus margin.
childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
childParams.leftMargin);
} else if(childParams.alignWithParent && rules[RIGHT_OF] ! =0) {
// Constrain the left margin of the Child to the left margin of the RelativeLayout, plus the padding and margin.
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
// Get the anchor point LayoutParams of child's alignLeft
anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
if(anchorParams ! =null) {
// The constraint left boundary is the anchor point left boundary
childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
} else if(childParams.alignWithParent && rules[ALIGN_LEFT] ! =0) {
// Constrain the left boundary to the left boundary of the RelativeLayout
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
// Get the anchor point LayoutParams of alignRight
anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
if(anchorParams ! =null) {
// The constraint right boundary is the anchor right boundary
childParams.mRight = anchorParams.mRight - childParams.rightMargin;
} else if(childParams.alignWithParent && rules[ALIGN_RIGHT] ! =0) {
if (myWidth >= 0) {
// Constrain the right boundary to a RelativeLayout right boundarychildParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; }}if (0! = rules[ALIGN_PARENT_LEFT]) {// If the alignParentLeft rule attribute is set, constrain the left boundary to the RelativeLayout left boundary.
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
if (0! = rules[ALIGN_PARENT_RIGHT]) {if (myWidth >= 0) {
// If alignParentRight is set, constrain the right boundary to the right boundary of the RelativeLayout.childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin; }}}Copy the code
Then enter the measureChildHorizontal method and look at the measurechild measurement:
private void measureChildHorizontal(
View child, LayoutParams params, int myWidth, int myHeight) {
// Generate width gauge for child.
final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight,
params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight,
myWidth);
final int childHeightMeasureSpec;
if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
// If the height measurement mode is UNSPECIFIED (older versions are not considered)
if (params.height >= 0) {
// If child's layoutparams. height is set to an explicit pixel value, an exact height measurement is generated for the child.
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
params.height, MeasureSpec.EXACTLY);
} else {
// Negative values in a mySize/myWidth/myWidth value in
// RelativeLayout measurement is code for, "we got an
// unspecified mode in the RelativeLayout's measure spec."
// Carry it forward.
// The height measurement specification of UNSPECIFIED is also generated for the Child.
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); }}else {
final int maxHeight;
/ / high version mMeasureVerticalWithPaddingMargin to true, low version can be neglected.
if (mMeasureVerticalWithPaddingMargin) {
// Calculate the height of the child and ensure that it is not less than 0.
maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom
- params.topMargin - params.bottomMargin);
} else {
maxHeight = Math.max(0, myHeight);
}
final int heightMode;
if (params.height == LayoutParams.MATCH_PARENT) {
heightMode = MeasureSpec.EXACTLY;
} else {
heightMode = MeasureSpec.AT_MOST;
}
// Generate a height gauge for the child (the height gauge may not be accurate at this point).
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code
In this method, the width and height measurement specifications are generated for the Child, and then the child measurement is called. The getChildMeasureSpec method is called to generate the width measurement specification.
/ * * *@param childStart The left or top field of the child's layout params
* @param childEnd The right or bottom field of the child's layout params
* @param childSize The child's desired size (the width or height field of
* the child's layout params)
* @param startMargin The left or top margin
* @param endMargin The right or bottom margin
* @param startPadding mPaddingLeft or mPaddingTop
* @param endPadding mPaddingRight or mPaddingBottom
* @param mySize The width or height of this view (the RelativeLayout)
* @return MeasureSpec for the child
*/
private int getChildMeasureSpec(int childStart, int childEnd,
int childSize, int startMargin, int endMargin, int startPadding,
int endPadding, int mySize) {
int childSpecMode = 0;
int childSpecSize = 0;
// Negative values in a mySize value in RelativeLayout
// measurement is code for, "we got an unspecified mode in the
// RelativeLayout's measure spec."
// When the measurement specification mode of the RelativeLayout is UNSPECIFIED, the mySize value is -1.
final boolean isUnspecified = mySize < 0;
// mAllowBrokenMeasureSpecs is false in higher versions and ignored in lower versions.
if(isUnspecified && ! mAllowBrokenMeasureSpecs) {if(childStart ! = VALUE_NOT_SET && childEnd ! = VALUE_NOT_SET) {// If left/up and right/down constraints are set, then the interval distance is calculated as the size, and the measurement specification is the exact value.
// Constraints fixed both edges, so child has an exact size.
childSpecSize = Math.max(0, childEnd - childStart);
childSpecMode = MeasureSpec.EXACTLY;
} else if (childSize >= 0) {
/ / if the child LayoutParams. The width/height set up precise pixel values, the measuring accuracy specifications and values.
// The child specified an exact size.
childSpecSize = childSize;
childSpecMode = MeasureSpec.EXACTLY;
} else {
// The measurement specification schema generated for Child also remains UNSPECIFIED.
// Allow the child to be whatever size it wants.
childSpecSize = 0;
childSpecMode = MeasureSpec.UNSPECIFIED;
}
// Generate measurement specifications and return.
return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
}
// Figure out start and end bounds.
int tempStart = childStart;
int tempEnd = childEnd;
// If the view did not express a layout constraint for an edge, use
// view's margins and our padding
// If child has no left/top constraint, use the left margin of the RelativeLayout with the padding and margin as the left/top margin.
if (tempStart == VALUE_NOT_SET) {
tempStart = startPadding + startMargin;
}
// If child does not set the right/bottom bounds, use the right edge of the RelativeLayout minus the padding and margin as the right/bottom bounds.
if (tempEnd == VALUE_NOT_SET) {
tempEnd = mySize - endPadding - endMargin;
}
// Figure out maximum size available to this view
// Calculate the interval distance as the available size under the constraint.
final int maxAvailable = tempEnd - tempStart;
if(childStart ! = VALUE_NOT_SET && childEnd ! = VALUE_NOT_SET) {// With constraints set left/up and right/down, the dimensions can be determined (in older versions, isUnspecified is False).
// Constraints fixed both edges, so child must be an exact size.
childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;
childSpecSize = Math.max(0, maxAvailable);
} else {
if (childSize >= 0) {
// Child's LayoutParams sets the exact pixel value.
// Child wanted an exact size. Give as much as possible.
childSpecMode = MeasureSpec.EXACTLY;
if (maxAvailable >= 0) {
// Make sure that the size of child does not exceed the constraint size.
// We have a maximum size in this dimension.
childSpecSize = Math.min(maxAvailable, childSize);
} else {
// We can grow in this dimension.childSpecSize = childSize; }}else if (childSize == LayoutParams.MATCH_PARENT) {
// Child's LayoutParams fills the parent layout with the constraint size as the size of the child.
// Child wanted to be as big as possible. Give all available
// space.
childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;
childSpecSize = Math.max(0, maxAvailable);
} else if (childSize == LayoutParams.WRAP_CONTENT) {
// Child's LayoutParams is just the wrapping content.
// Child wants to wrap content. Use AT_MOST to communicate
// available space if we know our max size.
if (maxAvailable >= 0) {
// We have a maximum size in this dimension.
childSpecMode = MeasureSpec.AT_MOST;
// Use the constraint size as the maximum limit size.
childSpecSize = maxAvailable;
} else {
// We can grow in this dimension. Child can be as big as it
// wants.
childSpecMode = MeasureSpec.UNSPECIFIED;
childSpecSize = 0; }}}// Generate measurement specifications and return.
return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
}
Copy the code
In this method, according to the boundary constraints of Child’s LayoutParams, the width/height values of LayoutParams, the measurement specifications and spacing values of the RelativeLayout, and the layout characteristics of the RelativeLayout, the measurement specifications are generated.
Back in the onMeasure method, execute the positionChildHorizontal method immediately after executing the measureChildHorizontal method:
private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
boolean wrapContent) {
final int layoutDirection = getLayoutDirection();
// Get converted mRules
int[] rules = params.getRules(layoutDirection);
if(params.mLeft == VALUE_NOT_SET && params.mRight ! = VALUE_NOT_SET) {// Child right boundary is set, left boundary is not set, set here.
// Right is fixed, but left varies
// The width of the child can be determined by subtracting the width of the child from the right.
params.mLeft = params.mRight - child.getMeasuredWidth();
} else if(params.mLeft ! = VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {// Left is fixed, but right varies
// Find the right edge by adding the left edge to the child width.
params.mRight = params.mLeft + child.getMeasuredWidth();
} else if (params.mLeft == VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
// The left and right boundaries are not set
// Both left and right vary
if(rules[CENTER_IN_PARENT] ! =0|| rules[CENTER_HORIZONTAL] ! =0) {
// This child sets the centerInParent or centerHorizontal property, which is centered horizontally.
// Determine if the measurement mode within the RelativeLayout is EXACTLY.
if(! wrapContent) {// The measurement mode is EXACTLY, and the width of the RelativeLayout is an explicit pixel value.
The centerHorizontal method uses the child width and the RelativeLayout width to set the left and right constraints of the child.
centerHorizontal(child, params, myWidth);
} else {
// The positionAtEdge method sets the left and right constraints of the Child using the RelativeLayout left and right boundaries and the child width.
positionAtEdge(child, params, myWidth);
}
return true;
} else {
// This is the default case. For RTL we start from the right and for LTR we start
// from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.positionAtEdge(child, params, myWidth); }}// Rules has been converted, so the START and END attributes are 0 and must return false.
returnrules[ALIGN_PARENT_END] ! =0;
}
Copy the code
The positionChildHorizontal method complements setting the left and right boundary constraints of a child. In the previous process, the left and right constraints of the child are set according to their setting attributes, and there is a boundary without setting constraints. Therefore, after the child width is accurately measured, the left and right constraints are set using the child width.
Vertical direction dependent constraint measurement
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/ /...
// Get an array of children sorted by vertical rules
views = mSortedVerticalChildren;
count = views.length;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
for (int i = 0; i < count; i++) {
final View child = views[i];
if(child.getVisibility() ! = GONE) {final LayoutParams params = (LayoutParams) child.getLayoutParams();
// Similar to the applyHorizontalSizeRules method, upper and lower bounds are set according to the relative position property set by child.
applyVerticalSizeRules(params, myHeight, child.getBaseline());
// Distribute the Child measurement (this measurement determines the exact height of the child).
measureChild(child, params, myWidth, myHeight);
PositionChildVertical is similar to positionChildHorizontal, in
// After child is measured, set the upper and lower boundaries with the child height supplement. This method always returns false.
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
if (isWrapContentWidth) {
// RelativeLayout width specification mode is not EXACTLY.
// Record the minimum maximum width required by a RelativeLayout.
if (isLayoutRtl()) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, myWidth - params.mLeft);
} else{ width = Math.max(width, myWidth - params.mLeft + params.leftMargin); }}else {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, params.mRight);
} else{ width = Math.max(width, params.mRight + params.rightMargin); }}}if (isWrapContentHeight) {
// RelativeLayout width specification mode is not EXACTLY.
// Record the minimum maximum height required by a RelativeLayout.
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
height = Math.max(height, params.mBottom);
} else{ height = Math.max(height, params.mBottom + params.bottomMargin); }}if(child ! = ignore || verticalGravity) {If the vertical axis is aligned and the child is not ignored, record the leftmost margin.
left = Math.min(left, params.mLeft - params.leftMargin);
// Record the uppermost upper boundary.
top = Math.min(top, params.mTop - params.topMargin);
}
if(child ! = ignore || horizontalGravity) {// Record the right margin most to the right.
right = Math.max(right, params.mRight + params.rightMargin);
// Record the lowest lower boundary.bottom = Math.max(bottom, params.mBottom + params.bottomMargin); }}}/ /...
}
Copy the code
The measurement logic of the vertical dependent constraint is similar to that of the horizontal direction. It traverses the child, sets the upper and lower boundary constraints according to the rule attributes set by the child, generates the width and height measurement specifications for the child, calls the Child measurement, and finally uses the height of the child measurement to supplement the setting of the upper and lower boundaries that have not been set before.
This phase also calculates the minimum maximum width and height required by the RelativeLayout, as well as the upper left, lower right and lower bounds.
We’ve traversed the Child distribution measurement twice to this RelativeLayout.
Adjust width and height in WrapContent case
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/ /...
// omit the find mBaselineView section. Compare the upper-left child as mBaselineView by traversing the ungone child.
/ /...
if (isWrapContentWidth) {
// RelativeLayout width specification mode is not EXACTLY
// Width already has left padding in it since it was calculated by looking at
// the right of each child view
width += mPaddingRight;
if(mLayoutParams ! =null && mLayoutParams.width >= 0) {
// Compare the maximum layoutparams.width with its own
width = Math.max(width, mLayoutParams.width);
}
// Make sure it is not less than the minimum width
width = Math.max(width, getSuggestedMinimumWidth());
// Get the adjusted size (adjust the desired size according to the mode and size limits of the measurement specification and set the status bit)
width = resolveSize(width, widthMeasureSpec);
// offsetHorizontalAxis is fixed to false and can be ignored below
if (offsetHorizontalAxis) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if(child.getVisibility() ! = GONE) {final LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
if(rules[CENTER_IN_PARENT] ! =0|| rules[CENTER_HORIZONTAL] ! =0) {
centerHorizontal(child, params, width);
} else if(rules[ALIGN_PARENT_RIGHT] ! =0) {
final int childWidth = child.getMeasuredWidth();
params.mLeft = width - mPaddingRight - childWidth;
params.mRight = params.mLeft + childWidth;
}
}
}
}
}
if (isWrapContentHeight) {
// RelativeLayout's height specification mode is not EXACTLY
// Height already has top padding in it since it was calculated by looking at
// the bottom of each child view
height += mPaddingBottom;
if(mLayoutParams ! =null && mLayoutParams.height >= 0) {
// Compare the maximum value with its own layoutparams.height
height = Math.max(height, mLayoutParams.height);
}
// Make sure it is not less than the minimum height
height = Math.max(height, getSuggestedMinimumHeight());
// Get the adjusted size (adjust the desired size according to the mode and size limits of the measurement specification and set the status bit)
height = resolveSize(height, heightMeasureSpec);
// offsetVerticalAxis is fixed to false and can be ignored below
if (offsetVerticalAxis) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if(child.getVisibility() ! = GONE) {final LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
if(rules[CENTER_IN_PARENT] ! =0|| rules[CENTER_VERTICAL] ! =0) {
centerVertical(child, params, height);
} else if(rules[ALIGN_PARENT_BOTTOM] ! =0) {
final int childHeight = child.getMeasuredHeight();
params.mTop = height - mPaddingBottom - childHeight;
params.mBottom = params.mTop + childHeight;
}
}
}
}
}
}
Copy the code
Adjust the position based on the Gravity property
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/ /...
RelativeLayout specifies whether the gravity property is set
if (horizontalGravity || verticalGravity) {
final Rect selfBounds = mSelfBounds;
// Set the area within the RelativeLayout
selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
height - mPaddingBottom);
final Rect contentBounds = mContentBounds;
// This method will calculate the region of position the contentBounds should be in the selfBounds based on mGravity.
/ / assume that RelativeLayout set right | bottom alignment, selfBounds = the Rect (0,0,680,1032), right - left = 600,
// Bottom-top =400; contentBounds=Rect(80,632,680,1032);
Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
layoutDirection);
// Calculate the horizontal and vertical offsets
final int horizontalOffset = contentBounds.left - left;
final int verticalOffset = contentBounds.top - top;
if(horizontalOffset ! =0|| verticalOffset ! =0) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if(child.getVisibility() ! = GONE && child ! = ignore) {// Skip the view set with the GONE and ignoreGravity properties
// Add an offset to each child's four-bound constraint
final LayoutParams params = (LayoutParams) child.getLayoutParams();
if (horizontalGravity) {
params.mLeft += horizontalOffset;
params.mRight += horizontalOffset;
}
if (verticalGravity) {
params.mTop += verticalOffset;
params.mBottom += verticalOffset;
}
}
}
}
}
}
Copy the code
Sets the dimensions of the RelativeLayout itself
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/ /...
// Determine whether to layout from right to left
if (isLayoutRtl()) {
final int offsetWidth = myWidth - width;
for (int i = 0; i < count; i++) {
final View child = views[i];
if(child.getVisibility() ! = GONE) {finalLayoutParams params = (LayoutParams) child.getLayoutParams(); params.mLeft -= offsetWidth; params.mRight -= offsetWidth; }}}// Set the width and height
setMeasuredDimension(width, height);
}
Copy the code
This completes the measurement process with a RelativeLayout.
OnLayout layout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
/ / iterate through the child
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if(child.getVisibility() ! = GONE) { RelativeLayout.LayoutParams st = (RelativeLayout.LayoutParams) child.getLayoutParams();// Layout the child using the upper, lower, left, and right constraints of the child's LayoutParams as boundaries.child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom); }}}Copy the code
The layout process with a RelativeLayout is simple, using the four-bound constraints of each child identified during the measurement phase.
Ontouch draw
RelativeLayout does not override this method, requiring no special drawing.
conclusion
RelativeLayout the more complex logic is all in the onMeasure phase. During the measurement process, two child distribution measurements will be made, first accurately measuring the width of the child, and then accurately measuring the height of the child. During the measurement phase, a RelativeLayout relies on the dependency graph to set four boundaries for each child, and then uses the four boundaries to determine the size of the child. In the onLayout stage, the four boundary constraints of each child are directly used for layout.