XML类型的drawable图片的解析处理过程

drawable state系列文章
XML类型的drawable图片的解析处理过程

StateListDrawable使用详解

详解refreshDrawableList()的执行流程

Checkable Views

Android中自定义drawable states

===============================================

首先我们还是举这个简单的例子

1、在res/drawable文件下创建selector.xml,示例代码如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item  android:state_pressed="false" android:drawable="@drawable/title_button_back">
    </item>
    <item  android:state_pressed="true" android:drawable="@drawable/title_button_back_h">
    </item>
    <item  android:state_window_focused="false" android:drawable="@drawable/title_button_back">
    </item>
</selector>

2、编写布局文件,为布局文件中的ImageButton设置selector,示例代码如下:

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="fill_parent">  
    <Button android:id="@+id/title_IB" android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/selector" android:layout_marginRight="4dp" android:layout_centerVertical="true">
    </Button>  
</RelativeLayout>

细心的人可能会有疑问,我们明明定义的是一个XML的文件,却把它当做一个drawable图片使用,并且这个图片还能根据状态发生改变,这个是怎么做到的?

至于为什么这个图片这个根据状态发生改变,我们在前面的详解refreshDrawableList()的执行流程已经讲过,并且把整个流程进行了分析。

下面我们要将的是为什么可以把这个XML文件当做一个drawable图片使用,其实很简单,我们一般定义的XML里面的内容其实都会被解析,并且相应的被实例化,我们的布局文件其实也是这样的。

下面我们就来看看它的解析和创建过程。

直接进入View.java文件,看看它的构造函数:

public View(Context context,AttributeSet attrs,int defStyleAttr) {
    this(context);

    TypedArray a = context.obtainStyledAttributes(attrs,com.android.internal.R.styleable.View,defStyleAttr,0);

    Drawable background = null;

    final int N = a.getIndexCount();
    for (int i = 0; i < N; i++) {
        int attr = a.getIndex(i);
        switch (attr) {
            case com.android.internal.R.styleable.View_background:
                background = a.getDrawable(attr);
                break;

            ......    

        }
   }             
}

我们可以看到通过a.getDrawable(attr)来得到背景图片,从上面可以看到a是TypedArray对象,我们直接进入它的getDrawable函数。

public Drawable getDrawable(int index) {
    final TypedValue value = mValue;
    //通过对应的index获取对应的TypeValue,并将它存放在value中
    if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES,value)) {
        if (false) {
            System.out.println("******************************************************************");
            System.out.println("Got drawable resource: type="
                               + value.type
                               + " str=" + value.string
                               + " int=0x" + Integer.toHexString(value.data)
                               + " cookie=" + value.assetCookie);
            System.out.println("******************************************************************");
        }
        //获取到对应的图片资源
        return mResources.loadDrawable(value,value.resourceId);
    }
    return null;
}

我们进入Resources来查看它是如何获取到这个Drawable对象的:

/*package*/ Drawable loadDrawable(TypedValue value,int id)
        throws NotFoundException {
    // 1、检查是否有这个图片缓存的版本
    // 2、检查是否预载了该图片
    ......
    if (cs != null) {
        dr = cs.newDrawable(this);
    } else {
        if (isColorDrawable) {
            dr = new ColorDrawable(value.data);
        }

        if (dr == null) {
            if (value.string == null) {
                throw new NotFoundException(
                        "Resource is not a Drawable (color or path): " + value);
            }

            String file = value.string.toString();

            if (TRACE_FOR_MISS_PRELOAD) {
                // Log only framework resources
                if ((id >>> 24) == 0x1) {
                    final String name = getResourceName(id);
                    if (name != null) android.util.Log.d(TAG,"Loading framework drawable #"
                            + Integer.toHexString(id) + ": " + name
                            + " at " + file);
                }
            }

            if (DEBUG_LOAD) Log.v(TAG,"Loading drawable for cookie "
                    + value.assetCookie + ": " + file);
            //前面的代码都在判断ColorDrawable和看是否有缓存的Draeable
            //我们重点看下面的代码
            if (file.endsWith(".xml")) {
                Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,file);
                try {
                    XmlResourceParser rp = loadXmlResourceParser(
                            file,id,value.assetCookie,"drawable");
                    dr = Drawable.createFromXml(this,rp);
                    rp.close();
                } catch (Exception e) {
                    Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
                    NotFoundException rnf = new NotFoundException(
                        "File " + file + " from drawable resource ID #0x"
                        + Integer.toHexString(id));
                    rnf.initCause(e);
                    throw rnf;
                }
                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

            }

        }
    }
     //最后,如果我们获取到了这个图片,就会把它缓存起来
     ......

    //最终返回
    return dr;
}

首先会判断是否以.xml结尾,然后得到XmlResourceParser对象,最后通过这个XmlResourceParser对象来创建Drawable对象。

我们直接进入Drawable.createFromXml(this,rp)函数。

public static Drawable createFromXml(Resources r,XmlPullParser parser)
        throws XmlPullParserException,IOException {
    AttributeSet attrs = Xml.asAttributeSet(parser);

    int type;
    while ((type=parser.next()) != XmlPullParser.START_TAG &&
            type != XmlPullParser.END_DOCUMENT) {
        // Empty loop
    }

    if (type != XmlPullParser.START_TAG) {
        throw new XmlPullParserException("No start tag found");
    }

    Drawable drawable = createFromXmlInner(r,parser,attrs);

    if (drawable == null) {
        throw new RuntimeException("Unknown initial tag: " + parser.getName());
    }

    return drawable;
}

从这个函数的参数列表,我们可以看到XmlResourceParser就是一个Pull解析器,它实现了XmlPullParser接口。

这里我们来稍微打断一下,我们一般在Android中使用Pull解析器来解析一个XML文件,我们一般是怎么做的?这里来简单的回忆一下,因为后面的过程就是对drawable这个xml文件进行解析。

1、写一个XML文件

<?xml version="1.0" encoding="UTF-8"?>
<persons>
  <person id="23">
    <name>李明</name>
    <age>30</age>
  </person>
  <person id="20">
    <name>李向梅</name>
    <age>25</age>
  </person>
</persons>

2、使用Pull解析器进行解析

public static List<Person> readXML(InputStream inStream) {

    XmlPullParser parser = Xml.newPullParser();

    try {
        parser.setInput(inStream,"UTF-8");
        int eventType = parser.getEventType();

        Person currentPerson = null;
        List<Person> persons = null;

        while (eventType != XmlPullParser.END_DOCUMENT) {
                    switch (eventType) {
                    case XmlPullParser.START_DOCUMENT://文档开始事件,可以进行数据初始化处理
                                persons = new ArrayList<Person>();
                                break;

                     case XmlPullParser.START_TAG://开始元素事件
                                String name = parser.getName();
                                if (name.equalsIgnoreCase("person")) {
                                            currentPerson = new Person();
                                            currentPerson.setId(new Integer(parser.getAttributeValue(null,"id")));
                                } else if (currentPerson != null) {
                                            if (name.equalsIgnoreCase("name")) {
                                                        currentPerson.setName(parser.nextText());
                                            } else if (name.equalsIgnoreCase("age")) {
                                                        currentPerson.setAge(new Short(parser.nextText()));
                                            }
                                }
                                break;

                    case XmlPullParser.END_TAG://结束元素事件
                                if (parser.getName().equalsIgnoreCase("person") && currentPerson != null) {
                                            persons.add(currentPerson);
                                            currentPerson = null;
                                }

                                break;
                    }

                     eventType = parser.next();
        }

    inStream.close();
    return persons;
    } catch (Exception e) {
                e.printStackTrace();
    }

    return null;
}

现在我们回过头去看上面的代码,while空循环是为了找到START_TAG,即selector标签,然后进入createFromXmlInner函数:

public static Drawable createFromXmlInner(Resources r,XmlPullParser parser,AttributeSet attrs)
throws XmlPullParserException,IOException {
    Drawable drawable;

    final String name = parser.getName();

    if (name.equals("selector")) {
        drawable = new StateListDrawable();
    } else if (name.equals("level-list")) {
        drawable = new LevelListDrawable();
    } else if (name.equals("layer-list")) {
        drawable = new LayerDrawable();
    } else if (name.equals("transition")) {
        drawable = new TransitionDrawable();
    } else if (name.equals("color")) {
        drawable = new ColorDrawable();
    } else if (name.equals("shape")) {
        drawable = new GradientDrawable();
    } else if (name.equals("scale")) {
        drawable = new ScaleDrawable();
    } else if (name.equals("clip")) {
        drawable = new ClipDrawable();
    } else if (name.equals("rotate")) {
        drawable = new RotateDrawable();
    } else if (name.equals("animated-rotate")) {
        drawable = new AnimatedRotateDrawable();            
    } else if (name.equals("animation-list")) {
        drawable = new AnimationDrawable();
    } else if (name.equals("inset")) {
        drawable = new InsetDrawable();
    } else if (name.equals("bitmap")) {
        //noinspection deprecation
        drawable = new BitmapDrawable(r);
        if (r != null) {
           ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
        }
    } else if (name.equals("nine-patch")) {
        drawable = new NinePatchDrawable();
        if (r != null) {
            ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
         }
    } else {
        throw new XmlPullParserException(parser.getPositionDescription() +
                ": invalid drawable tag " + name);
    }

    drawable.inflate(r,attrs);
    return drawable;
}

从这里我们可以看到为什么要找到START_TAG标签,可以知道我们找到的是selector,这样就执行了drawable = new StateListDrawable(),创建了一个StateListDrawable对象。

在这里你应该恍然大悟,原来这个XML文件对应的就是这个StateListDrawable对象,知道我们在详解refreshDrawableList()的执行流程一文中为什么进入到了StateListDrawable这个实例的onStateChange函数吧!后面你将会更加清楚了!

我们继续来看代码,当创建完了StateListDrawable实例后,就会执行它的inflate方法,我们直接进入这个函数。

public void inflate(Resources r,AttributeSet attrs)
        throws XmlPullParserException,IOException {

    TypedArray a = r.obtainAttributes(attrs,com.android.internal.R.styleable.StateListDrawable);

    super.inflateWithAttributes(r,a,com.android.internal.R.styleable.StateListDrawable_visible);

    mStateListState.setVariablePadding(a.getBoolean(
            com.android.internal.R.styleable.StateListDrawable_variablePadding,false));
    mStateListState.setConstantSize(a.getBoolean(
            com.android.internal.R.styleable.StateListDrawable_constantSize,false));
    mStateListState.setEnterFadeDuration(a.getInt(
            com.android.internal.R.styleable.StateListDrawable_enterFadeDuration,0));
    mStateListState.setExitFadeDuration(a.getInt(
            com.android.internal.R.styleable.StateListDrawable_exitFadeDuration,0));

    setDither(a.getBoolean(com.android.internal.R.styleable.StateListDrawable_dither,DEFAULT_DITHER));

    setAutoMirrored(a.getBoolean(
            com.android.internal.R.styleable.StateListDrawable_autoMirrored,false));

    a.recycle();

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

        int drawableRes = 0;

        int i;
        int j = 0;
        final int numAttrs = attrs.getAttributeCount();
        int[] states = new int[numAttrs];
        for (i = 0; i < numAttrs; i++) {
            final int stateResId = attrs.getAttributeNameResource(i);
            if (stateResId == 0) break;
            if (stateResId == com.android.internal.R.attr.drawable) {
                drawableRes = attrs.getAttributeResourceValue(i,0);
            } else {
                states[j++] = attrs.getAttributeBooleanValue(i,false)
                        ? stateResId
                        : -stateResId;
            }
        }
        states = StateSet.trimStateSet(states,j);

        Drawable dr;
        if (drawableRes != 0) {
            dr = r.getDrawable(drawableRes);
        } else {
            while ((type = parser.next()) == XmlPullParser.TEXT) {
            }
            if (type != XmlPullParser.START_TAG) {
                throw new XmlPullParserException(
                        parser.getPositionDescription()
                                + ": <item> tag requires a 'drawable' attribute or "
                                + "child tag defining a drawable");
            }
            dr = Drawable.createFromXmlInner(r,attrs);
        }

        mStateListState.addStateSet(states,dr);
    }

    onStateChange(getState());
}

进入frameworks/base/core/res/res/values/attrs.xml文件,我们可以看到进入StateListDrawable定义的属性:

<declare-styleable name="StateListDrawable">
    <!-- Indicates whether the drawable should be initially visible. -->
    <attr name="visible" />
    <!-- If true,allows the drawable's padding to change based on the current state that is selected. If false,the padding will stay the same (based on the maximum padding of all the states). Enabling this feature requires that the owner of the drawable deal with performing layout when the state changes,which is often not supported. -->
    <attr name="variablePadding" format="boolean" />
    <!-- If true,the drawable's reported internal size will remain constant as the state changes; the size is the maximum of all of the states. If false,the size will vary based on the current state. -->
    <attr name="constantSize" format="boolean" />
    <!-- Enables or disables dithering of the bitmap if the bitmap does not have the same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with an RGB 565 screen). -->
    <attr name="dither" format="boolean" />
    <!-- Amount of time (in milliseconds) to fade in a new state drawable. -->
    <attr name="enterFadeDuration" format="integer" />
    <!-- Amount of time (in milliseconds) to fade out an old state drawable. -->
    <attr name="exitFadeDuration" format="integer" />
    <!-- Indicates if the drawable needs to be mirrored when its layout direction is RTL (right-to-left). -->
    <attr name="autoMirrored"/>
</declare-styleable>

在上面的代码中,我们看到它会得到这些属性并将其进行设置。接着,就是进入一个while循环,然后得到各个item对应的状态数组states和对应图片资源dr,最终通过mStateListState.addStateSet(states,dr)把它们添加进去。

mStateListState就是一个StateListState对象,StateListState是StateListDrawable的静态内部类,我们来看看具体的实现:

int addStateSet(int[] stateSet,Drawable drawable) {
    final int pos = addChild(drawable);
    mStateSets[pos] = stateSet;
    return pos;
}

它会执行addChild函数,StateListState没有实现这个函数,它直接继承自它的父类DrawableContainerState,DrawableContainerState是DrawableContainer类的静态内部类:

public final int addChild(Drawable dr) {
    final int pos = mNumChildren;

    if (pos >= mDrawables.length) {
        growArray(pos,pos+10);
    }

    dr.setVisible(false,true);
    dr.setCallback(mOwner);

    mDrawables[pos] = dr;
    mNumChildren++;
    mChildrenChangingConfigurations |= dr.getChangingConfigurations();
    mCheckedStateful = false;
    mCheckedOpacity = false;

    mConstantPadding = null;
    mPaddingChecked = false;
    mComputedConstantSize = false;

    return pos;
}

它内部有一个Drawable数组mDrawables用来存放所有的图片资源,然后它会把这个图片资源的下标返回去,item里面的图片资源就是通过下标跟状态数据关联起来的。

回到addStateSet,我们可以看到状态数组也是放在一个数组中,所以存放状态数组的数组是个二维数组,它的下标就是图片资源数组的下标。所以我们知道,我们上面所说的那个XML文件的图片资源其实被进行了解析并且对应一个StateListDrawable对象,在StateListDrawable对象中,有一个存放图片的数组和存放状态集合的数组,它们对应的就是XML文件中的item项。

现在回想一下上篇文章详解refreshDrawableList()的执行流程,当我们控件的状态发生改变的时候,会调用refreshDrawableState进行刷新,它会首先获取当前的状态数组stateSet,然后在上面存放所有状态的二维数组mStateSets中查找与之相匹配的状态数组,然后返回它的下标,然后通过这个下标,在图片资源数组mDrawables中找到对应的图片资源,接着将这个图片进行绘制,所以我们就看到了背景的改变。

最后,需要把Drawable,DrawableContainer和StateListDrawable之间的关系整理一下,下面直接上一张图,Drawable是DrawableContainer的父类,DrawableContainer是StateListDrawable的父类,并且它们每个类中都包含有相应的内部类,也有继承关系。对应这个类图,再看看上面的过程,应该会更加清晰。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


php输出xml格式字符串
J2ME Mobile 3D入门教程系列文章之一
XML轻松学习手册
XML入门的常见问题(一)
XML入门的常见问题(三)
XML轻松学习手册(2)XML概念
xml文件介绍及使用
xml编程(一)-xml语法
XML文件结构和基本语法
第2章 包装类
XML入门的常见问题(二)
Java对象的强、软、弱和虚引用
JS解析XML文件和XML字符串详解
java中枚举的详细使用介绍
了解Xml格式
XML入门的常见问题(四)
深入SQLite多线程的使用总结详解
PlayFramework完整实现一个APP(一)
XML和YAML的使用方法
XML轻松学习总节篇