Android 中 View,Drawable,Animation 从 XML 中解析生成的过程

1,910 阅读9分钟
原文链接: www.kutear.com

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;
...

好了,到这里就算分析完了,里面有很多代码现在还是不太清楚具体的逻辑。但是我们流程还是算清楚的了。