LinearLayout的简单分析
本篇简单分析
LinearLayout
的实现机制来理解该怎么进行自定义组合View
我们可能这样在xml
中来使用LinearLayout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView3" />
</LinearLayout>
在MainActivity
的onCreate
中
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
我们直接
setContentView
,将LinearLayout
布局通过LayoutInflater
转换成View
加入DecorView
中,这里我就不详细分析,在6.0代码中,可以查看support/v7/appcompat/src/AppCompatDelegateImpIV7.java
:
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
在LayoutInflater
中的inflate
中可以看出将采用pull
解析将xml
的节点解析成view
,加入DecorView
中的
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) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
我们查看LinearLayout
源码的三个构造函数,发现这三个函数都会调用这个构造方法
public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initLinearLayout();
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);
int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
if (index >= 0) {
setOrientation(index);
}
index = a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1);
if (index >= 0) {
setGravity(index);
}
boolean baselineAligned = a.getBoolean(R.styleable.LinearLayout_baselineAligned, true);
if (!baselineAligned) {
setBaselineAligned(baselineAligned);
}
mWeightSum = a.getFloat(R.styleable.LinearLayout_weightSum, -1.0f);
mBaselineAlignedChildIndex =
a.getInt(com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1);
mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_measureWithLargestChild, false);
setDividerDrawable(a.getDrawable(R.styleable.LinearLayout_divider));
mShowDividers = a.getInt(R.styleable.LinearLayout_showDividers, SHOW_DIVIDER_NONE);
mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayout_dividerPadding, 0);
a.recycle();
}
这个方法是读取自定义属性,在
TypeArray
中获取自定义属性的值,这个TypeArray
封装了自定义属性的值,它的原理实现也是通过pull
解析atts.xml
中的declare-styleable
的属性列表,封装成TypeArray
对象
public TypedArray obtainStyledAttributes(AttributeSet set,
@StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(Resources.this, len);
// XXX note that for now we only work with compiled XML files.
// To support generic XML files we will need to manually parse
// out the attributes from the XML file (applying type information
// contained in the resources and such).
final XmlBlock.Parser parser = (XmlBlock.Parser)set;
AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);
array.mTheme = this;
array.mXml = parser;
if (false) {
int[] data = array.mData;
System.out.println("Attributes:");
String s = " Attrs:";
int i;
for (i=0; i<set.getAttributeCount(); i++) {
s = s + " " + set.getAttributeName(i);
int id = set.getAttributeNameResource(i);
if (id != 0) {
s = s + "(0x" + Integer.toHexString(id) + ")";
}
s = s + "=" + set.getAttributeValue(i);
}
System.out.println(s);
s = " Found:";
TypedValue value = new TypedValue();
for (i=0; i<attrs.length; i++) {
int d = i*AssetManager.STYLE_NUM_ENTRIES;
value.type = data[d+AssetManager.STYLE_TYPE];
value.data = data[d+AssetManager.STYLE_DATA];
value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE];
value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID];
s = s + " 0x" + Integer.toHexString(attrs[i])
+ "=" + value;
}
System.out.println(s);
}
return array;
}
我们查看LinearLayout
自定义属性所在的位置frameworks/base/core/res/res/attrs.xml
<declare-styleable name="LinearLayout">
<!-- Should the layout be a column or a row? Use "horizontal"
for a row, "vertical" for a column. The default is
horizontal. -->
<attr name="orientation" />
<attr name="gravity" />
<!-- When set to false, prevents the layout from aligning its children's
baselines. This attribute is particularly useful when the children
use different values for gravity. The default value is true. -->
<attr name="baselineAligned" format="boolean" />
<!-- When a linear layout is part of another layout that is baseline
aligned, it can specify which of its children to baseline align to
(that is, which child TextView).-->
<attr name="baselineAlignedChildIndex" format="integer" min="0"/>
<!-- Defines the maximum weight sum. If unspecified, the sum is computed
by adding the layout_weight of all of the children. This can be
used for instance to give a single child 50% of the total available
space by giving it a layout_weight of 0.5 and setting the weightSum
to 1.0. -->
<attr name="weightSum" format="float" />
<!-- When set to true, all children with a weight will be considered having
the minimum size of the largest child. If false, all children are
measured normally. -->
<attr name="measureWithLargestChild" format="boolean" />
<!-- Drawable to use as a vertical divider between buttons. -->
<attr name="divider" />
<!-- Setting for which dividers to show. -->
<attr name="showDividers">
<flag name="none" value="0" />
<flag name="beginning" value="1" />
<flag name="middle" value="2" />
<flag name="end" value="4" />
</attr>
<!-- Size of padding on either end of a divider. -->
<attr name="dividerPadding" format="dimension" />
</declare-styleable>
<declare-styleable name="LinearLayout_Layout">
<attr name="layout_width" />
<attr name="layout_height" />
<attr name="layout_weight" format="float" />
<attr name="layout_gravity" />
</declare-styleable>
这里定义了LinearLayout
的一些属性,如我们常用的orientation
,grayvity
等,然后我们会在onDraw
中根据所定义的orientation
来进行绘制
@Override
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
return;
}
if (mOrientation == VERTICAL) {
drawDividersVertical(canvas);
} else {
drawDividersHorizontal(canvas);
}
}
经过上面LinearLayout
的简单分析,我们能够得到一个自定义view
的思路,我们可以通过继承现有控件,自定义declare-styleable
属性,重写构造函数获取自定义属性来得到我们的组合自定义view
的实现。