android如何在xml中引用内部类的View
来源:互联网 发布:office 2016 mac 云盘 编辑:程序博客网 时间:2024/05/30 05:17
上周,有个同事在xml中引用内部类的View时候出错,问我在xml中能不能用内部类的View,我以前项目曾经这样做过,因此当时很肯定地告诉他可以。看了下他的代码,xml中的class属性引用的内部类写法错了,把“$”写成“.”,我让他改下就可以。他试完之后告诉我还是不行,我瞬间懵逼了。当时因时间关系,没时间去查错,让他先改为外部类处理。今天早上有空查看下系统源码,终于把这个问题搞清楚了。进入今天的正题:
- xml布局引用内部类View的正确写法
- 系统是如何根据class来创建View
xml布局引用内部类View的正确写法
解决问题从源码入手。首先从Activity的setContentView开始
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
调用PhoneWindow的setContentView:
@Override public void setContentView(int layoutResID) { ... if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } ... }
调用LayoutInflater的inflate方法调用顺序如下(已删除大部分无关代码):
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { ... final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { ... final View temp = createViewFromTag(root, name, inflaterContext, attrs); ...}View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } ...
当看到createViewFromTag方法的name.equals(“view”)时候,我瞬间明白了,原来我同事把xml中tag写成大写View了,于是赶脚写个Demo测试一下:
内部类MyView:
package com.baidusoso.innerclassview;...public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public static class MyView extends TextView { public MyView(Context context, AttributeSet attrs) { this(context, attrs,R.attr.CustomizeStyle); Log.d(TAG, "MyView"); } }}
布局文件activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/transparent" android:orientation="vertical" > <view android:layout_width="wrap_content" android:layout_height="wrap_content" class="com.baidusoso.innerclassview.MainActivity$MyView" android:text="Hello world!!!" /></LinearLayout>
Duang,程序果然运行起来了。
现在总结一下xml布局引用内部类View的正确写法:
1. xml布局文件中tag的view必须是:小写、小写、小写,重要的事情说3遍;
2. 内部类的View必须是静态的,因为普通内部类必须通过对象来引用,这在xml中是不可能的(如果看不明白这点,赶紧去学习下java内部类相关知识)
3. 引用类属性直接是class,没有如android:这样的名字空间;外部类和静态内部类是用$(而不是“.”)连起来的,如:
class=”com.baidusoso.innerclassview.MainActivity$MyView”
4. 静态内部类必须有带Context、AttributeSet这2个参数的构造函数,如
public MyView(Context context, AttributeSet attrs)
我将在下一节对第四点做出解释。
系统是如何根据class来创建View
那么写好class之后,系统是如何进行校验这个class是否存在?怎么构建其View对象呢?
带着这2个问题,我们接着往下看源码,还是在LayoutInflater的createViewFromTag
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } ... if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; ... }
这里会根据class是否包含”.”调用2个不同的函数:onCreateView和createView
我们先来看onCreateView
protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException { return onCreateView(name, attrs); }protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); }
从代码中,我们得知onCreateView最后也是调用到createView,只是第二个参数是”android.view.”,而不是null。也就是说,如果class的值没带”.”,那默认就会到android.view这个包名下去找相应的类,如:
<view android:layout_width="match_parent" android:layout_height="1dp" class="View" android:background="#000000" />
以上代码就是构建一个android.view.View对象,内容就是一根长度充满父节点的黑线。
接着我们再看看createView方法:
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 { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); 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); ... constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { .... } Object[] args = mConstructorArgs; args[1] = attrs; final View view = constructor.newInstance(args); ... return view;
其中mContext.getClassLoader().loadClass方法就是加载class属性值,得到相应的类实例。如果我们把class值写错了,这里就会报ClassNotFoundException.这里我们解决本节开头提到的第一个问题:系统是如何进行校验这个class是否存在
创建class的类实例后,通过反射clazz.getConstructor(找到构造函数,其参数mConstructorSignature对应的值是:
static final Class<?>[] mConstructorSignature = new Class[] { Context.class, AttributeSet.class};
现在,你明白了上节结论第四点提到对构造函数的要求:静态内部类必须有带Context、AttributeSet这2个参数的构造函数
如果我们定义的view中没有这个构造函数,那么就会抛出NoSuchMethodException。
接着通过final View view = constructor.newInstance(args);创建了View的实例。这也回答本节开头提到的第二个问题:怎么构建其View对象
最后再说一点,我们平常写的布局:
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"
也可以写出这样:
<view android:layout_width="wrap_content" android:layout_height="wrap_content" class="android.widget.LinearLayout"
只是第一种写法显得很简单,简单就是美!
本文Demo下载:Demo下载
- android如何在xml中引用内部类的View
- android如何在xml中引用内部类View
- android 自定义view,在xml中引用内部类View
- android如何在xml中引用内部类?
- Android XML中引用自定义内部类view的四个why
- 在xml文件中引用内部类笔记
- Android中自定义View的研究 -- 在XML中引用自定义View
- Android中View内部类MeasureSpec研究
- android 在xml文件中引用自定义View
- android 在xml文件中引用自定义View
- android 在xml文件中引用自定义View
- 如何在web.xml中引用其它的xml文件
- 如何在android的XML和java代码中引用字符串常量
- Android非匿名内部类持有外部类的引用
- Android中随处可见的匿名内部类
- Android源代码中使用的内部类
- 在xml中如何引用自己定义的schema文件?
- 如何在HTML中引用XML数据
- 博客开始篇
- 类模板和模板函数连接出错处理
- 用redis的sadd和spop做后台抽奖
- 首次组长遇到的团队问题
- VTK入门(二)--颜色映射
- android如何在xml中引用内部类的View
- Eclipse 运行C/C++
- error LNK2038: 检测到“_MSC_VER”的不匹配项: 值“1800”不匹配值“1600”
- poj之旅——2385
- Linux软RAID的技术概要及实现
- Installing on Raspberry PI/Raspbian from source
- VC隐藏console
- 4-2 跳马
- mjpg-streamer移植