View
对于View我们常见的使用就是两种,一种是setContent(int),另一种为LayoutInflater.inflate(),其本质都是一样的。皆为LayoutInflater.inflate(),我们分析一下这里的具体实现。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource); //根据xml返回一个解析器
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
接着就是函数inflate().
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final String name = parser.getName();
if (TAG_MERGE.equals(name)) { //如果是标签
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//内部也是通过函数createViewFromTag()生成View,并调用rInflateChildren()递归解析
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//根据当前标签tag创建View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//获取xml中的属性,如宽高,待为view设置layoutparams
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);
}
}
// Inflate all children under temp against its context.
//把当前解析的view作为parent,继续递归解析她的child View
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//最后把当前解析的view添加到他的parent中
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;
}
}
return result;
}
}
上面的函数非常容易理解,我们现在要看的函数就是createViewFromTag()
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
//原来xml还可以写成
//的形式
//标签 实现闪烁的如 那么AA会闪烁
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
...
//这里有一些Factory创建方式,但是他的逻辑和下面一样,不多说
...
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs); //不含. 一定是系统的View,如TextView
} else {
view = createView(name, null, attrs); //非系统VIew 如支持库的View,自定义的View
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
...
}
}
通过源码,我们发现onCreateView(..)其实是调用createView(name, "android.view.", attrs),只是人为的加上View的前缀。
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor extends View> constructor = sConstructorMap.get(name); //构造函数缓存
Class extends View> clazz = null;
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
...
// static final Class[] mConstructorSignature = new Class[] {
// Context.class, AttributeSet.class};
constructor = clazz.getConstructor(mConstructorSignature);//这里我们就可以看到为什么View在xml实例时调用两个参数的构造函数
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object[] args = mConstructorArgs;
args[1] = attrs;
final View view = constructor.newInstance(args); //实例化View对象
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
//对于ViewStub,现在还没解析其内部结构,待调用函数inflate()时才解析替换为内部的View。
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} catch (NoSuchMethodException e) {
...
}
}
到此View的解析就算完了。
Drawable
这里以Background为例说明,在View的构造哈数中存在这样一段代码
//View.java
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
case ..
}
//TypedArray.java
@Nullable
public Drawable getDrawable(int index) {
...
return mResources.loadDrawable(value, value.resourceId, mTheme);
}
在loadDrawable()当中,做了一些判断,比如当前要的Drawable是否已经加载过了,即判断是否已经在缓存中,如果在就不需要从xml中加载,反之就需要加载,并保存在缓存中。而加载就是调用函数loadDrawableForCookie(),下面看看具体的实现。
private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
if (value.string == null) {
throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
}
final String file = value.string.toString(); //这里是文件的路径 如res/drawable/xxx.png
final Drawable dr;
try {
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(this, rp, theme);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(this, value, is, file, null);
is.close();
}
} catch (Exception e) {
}
return dr;
}
很容易看到,这里以xml结尾的文件和其他如png结尾的文件做了分别处理,先看看简单的普通的图片格式,也就是else中的代码。可以看见是很简单的实现,就是把图片转换为数据流,在通过数据流构建Drawable对象。重点看看xml类型Drawable的生成吧。跟入进去,我们发现有个特别的函数createFromXmlInner(),为什么说特别?因为它就是创建具体Drawable的函数。
public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final Drawable drawable;
final String name = parser.getName();
switch (name) {
case "selector":
drawable = new StateListDrawable();
break;
case "animated-selector":
drawable = new AnimatedStateListDrawable();
break;
case "level-list":
drawable = new LevelListDrawable();
break;
case "layer-list":
drawable = new LayerDrawable();
break;
case "transition":
drawable = new TransitionDrawable();
break;
case "ripple":
drawable = new RippleDrawable();
break;
case "color":
drawable = new ColorDrawable();
break;
case "shape":
drawable = new GradientDrawable();
break;
case "vector":
drawable = new VectorDrawable();
break;
case "animated-vector":
drawable = new AnimatedVectorDrawable();
break;
case "scale":
drawable = new ScaleDrawable();
break;
case "clip":
drawable = new ClipDrawable();
break;
case "rotate":
drawable = new RotateDrawable();
break;
case "animated-rotate":
drawable = new AnimatedRotateDrawable();
break;
case "animation-list":
drawable = new AnimationDrawable();
break;
case "inset":
drawable = new InsetDrawable();
break;
case "bitmap":
drawable = new BitmapDrawable();
break;
case "nine-patch":
drawable = new NinePatchDrawable();
break;
default:
throw new XmlPullParserException(parser.getPositionDescription() +
": invalid drawable tag " + name);
}
drawable.inflate(r, parser, attrs, theme);
return drawable;
}
前面的switch就是简单的根据标签tag创建具体的对象,最后调用drawable.inflate()来配置具体的参数。这里我们一帧动画AnimationDrawable来说明。
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable);
super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible);
updateStateFromTypedArray(a);
a.recycle();
inflateChildElements(r, parser, attrs, theme);
}
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
int type;
final int innerDepth = parser.getDepth()+1;
int depth;
//循环获取每一帧动画信息
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
//取得每个名为item的tag结点
final TypedArray a = obtainAttributes(r, theme, attrs,
R.styleable.AnimationDrawableItem);
final int duration = a.getInt(R.styleable.AnimationDrawableItem_duration, -1);
if (duration < 0) {
throw new XmlPullParserException(parser.getPositionDescription()
+ ": tag requires a 'duration' attribute");
}
Drawable dr = a.getDrawable(R.styleable.AnimationDrawableItem_drawable); //按照解析png的方式解析
a.recycle();
if (dr == null) {
while ((type=parser.next()) == XmlPullParser.TEXT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException(parser.getPositionDescription()
+ ": tag requires a 'drawable' attribute or child tag"
+ " defining a drawable");
}
dr = Drawable.createFromXmlInner(r, parser, attrs, theme); //item内部再嵌xml类型的Drawable
}
mAnimationState.addFrame(dr, duration); //添加到存储每一帧的容器,带播放时使用
if (dr != null) {
dr.setCallback(this);
}
}
}
对于Drawable的解析大体就这些。
Animation
我们知道,Android动画大体分为三类,帧动画,View 动画,和属性动画。帧动画在上面Drawable已经说过了。而View动画和属性动画基本一样的解析。下面以属性动画为例看一下。我们选择从函数loadAnimator(Context context, @AnimatorRes int id)入手。
public static Animator loadAnimator(Resources resources, Theme theme, int id,
float pathErrorScale) throws NotFoundException {
final ConfigurationBoundResourceCache animatorCache = resources
.getAnimatorCache();
Animator animator = animatorCache.getInstance(id, theme); //优先缓存读取
if (animator != null) {
return animator;
} else if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
}
//缓存中不存在,需要解析
XmlResourceParser parser = null;
try {
parser = resources.getAnimation(id);
animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);//动画的创建
if (animator != null) {
animator.appendChangingConfigurations(getChangingConfigs(resources, id));
final ConstantState constantState = animator.createConstantState();
if (constantState != null) {
if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
}
animatorCache.put(id, theme, constantState);
// create a new animator so that cached version is never used by the user
// 这里的注释说明每次从缓存中读取的cache其实也是通过这种方式创建的。
animator = constantState.newInstance(resources, theme);
}
}
return animator;
} catch (XmlPullParserException ex) {
...
} finally {
if (parser != null) parser.close();
}
}
毕竟这不是我们这里需要主要关注的,下面我们看看创建的函数createAnimatorFromXml().
private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)
throws XmlPullParserException, IOException {
Animator anim = null;
ArrayList childAnims = null;
// Make sure we are on a start tag.
int type;
int depth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
boolean gotValues = false;
if (name.equals("objectAnimator")) { //生成objectAnimator
anim = loadObjectAnimator(res, theme, attrs, pixelSize);
} else if (name.equals("animator")) { //ValueAnimator
anim = loadAnimator(res, theme, attrs, null, pixelSize); //递归过程
} else if (name.equals("set")) { //AnimatorSet
//我们知道,AnimatorSet是个容器,这里的逻辑就是把它作为parent,在递归解析子元素
anim = new AnimatorSet();
TypedArray a;
if (theme != null) {
a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
} else {
a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
}
anim.appendChangingConfigurations(a.getChangingConfigurations());
int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
pixelSize); //递归调用
a.recycle();
} else if (name.equals("propertyValuesHolder")) { //这里面还会解析keyframe标签
PropertyValuesHolder[] values = loadValues(res, theme, parser,
Xml.asAttributeSet(parser));
if (values != null && anim != null && (anim instanceof ValueAnimator)) {
((ValueAnimator) anim).setValues(values);
}
gotValues = true;
} else {
throw new RuntimeException("Unknown animator name: " + parser.getName());
}
if (parent != null && !gotValues) {
if (childAnims == null) {
childAnims = new ArrayList();
}
childAnims.add(anim);
}
}
//生成完毕,考虑添加到Parent
if (parent != null && childAnims != null) {
Animator[] animsArray = new Animator[childAnims.size()];
int index = 0;
for (Animator a : childAnims) {
animsArray[index++] = a;
}
if (sequenceOrdering == TOGETHER) {
parent.playTogether(animsArray);
} else {
parent.playSequentially(animsArray);
}
}
return anim;
}
有点抽象,下面我们先写一个小例子看看动画的所有结构。
android:ordering="sequentially">
android:duration="1000"
android:repeatCount="1"
android:repeatMode="reverse">
android:fraction="0" android:value="1"/>
android:fraction=".2" android:value=".4"/>
android:fraction="1" android:value="0"/>
android:duration="500"
android:valueTo="1f">
android:propertyName="x" >
android:fraction="0" android:value="800" />
android:fraction=".2"
android:interpolator="@android:anim/accelerate_interpolator"
android:value="1000" />
android:fraction="1"
android:interpolator="@android:anim/accelerate_interpolator"
android:value="400" />
android:propertyName="y" >
android:fraction=".2"
android:interpolator="@android:anim/accelerate_interpolator"
android:value="300"/>
android:interpolator="@android:anim/accelerate_interpolator"
android:value="1000" />
我们接下来以的解析为例来说明。
private static PropertyValuesHolder[] loadValues(Resources res, Theme theme,
XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
ArrayList values = null;
int type;
while ((type = parser.getEventType()) != XmlPullParser.END_TAG &&
type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
parser.next();
continue;
}
String name = parser.getName();
if (name.equals("propertyValuesHolder")) {
TypedArray a;
if (theme != null) {
a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyValuesHolder, 0, 0);
} else {
a = res.obtainAttributes(attrs, R.styleable.PropertyValuesHolder);
}
String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
VALUE_TYPE_UNDEFINED);
PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
if (pvh == null) {
pvh = getPVH(a, valueType,
R.styleable.PropertyValuesHolder_valueFrom,
R.styleable.PropertyValuesHolder_valueTo, propertyName);
}
if (pvh != null) {
if (values == null) {
values = new ArrayList();
}
values.add(pvh);
}
a.recycle();
}
parser.next();
}
PropertyValuesHolder[] valuesArray = null;
if (values != null) {
int count = values.size();
valuesArray = new PropertyValuesHolder[count];
for (int i = 0; i < count; ++i) {
valuesArray[i] = values.get(i);
}
}
return valuesArray;
}
上面代码没什么可说的,唯一要看的就是PropertyValuesHolder的创建函数loadPvh()。
private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
String propertyName, int valueType)
throws XmlPullParserException, IOException {
PropertyValuesHolder value = null;
ArrayList keyframes = null;
int type;
while ((type = parser.next()) != XmlPullParser.END_TAG &&
type != XmlPullParser.END_DOCUMENT) {
String name = parser.getName();
if (name.equals("keyframe")) {
if (valueType == VALUE_TYPE_UNDEFINED) {
valueType = inferValueTypeOfKeyframe(res, theme, Xml.asAttributeSet(parser));
}
//解析keyframe
Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType);
if (keyframe != null) {
if (keyframes == null) {
keyframes = new ArrayList();
}
keyframes.add(keyframe);
}
parser.next();
}
}
int count;
if (keyframes != null && (count = keyframes.size()) > 0) {
...对keyframes的调整
value = PropertyValuesHolder.ofKeyframe(propertyName, keyframeArray); //设置keyframe
if (valueType == VALUE_TYPE_COLOR) {
value.setEvaluator(ArgbEvaluator.getInstance());//设置估值器
}
}
return value;
}
卧槽,一层一层何时能到头。
private static Keyframe loadKeyframe(Resources res, Theme theme, AttributeSet attrs,
int valueType)
throws XmlPullParserException, IOException {
TypedArray a;
if (theme != null) {
a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
} else {
a = res.obtainAttributes(attrs, R.styleable.Keyframe);
}
Keyframe keyframe = null;
float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
boolean hasValue = (keyframeValue != null);
if (valueType == VALUE_TYPE_UNDEFINED) {
// When no value type is provided, check whether it's a color type first.
// If not, fall back to default value type (i.e. float type).
if (hasValue && isColorType(keyframeValue.type)) {
valueType = VALUE_TYPE_COLOR;
} else {
valueType = VALUE_TYPE_FLOAT;
}
}
if (hasValue) {
switch (valueType) {
case VALUE_TYPE_FLOAT:
float value = a.getFloat(R.styleable.Keyframe_value, 0);
keyframe = Keyframe.ofFloat(fraction, value);
break;
case VALUE_TYPE_COLOR:
case VALUE_TYPE_INT:
int intValue = a.getInt(R.styleable.Keyframe_value, 0);
keyframe = Keyframe.ofInt(fraction, intValue);
break;
}
} else {
keyframe = (valueType == VALUE_TYPE_FLOAT) ? Keyframe.ofFloat(fraction) :
Keyframe.ofInt(fraction);
}
final int resID = a.getResourceId(R.styleable.Keyframe_interpolator, 0);
if (resID > 0) {
final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID); //插值器,和View动画一致
keyframe.setInterpolator(interpolator);
}
a.recycle();
return keyframe;
}
上面函数很简单,就是根据不同的type生成不同的keyframe,而type我们从上面知道是从TypedValue.type来判断的。TypedValue中持有很多的type声明
...
public static final int TYPE_DIMENSION = 0x05;
/** The data field holds a complex number encoding a fraction
* of a container. */
public static final int TYPE_FRACTION = 0x06;
...
好了,到这里就算分析完了,里面有很多代码现在还是不太清楚具体的逻辑。但是我们流程还是算清楚的了。