使用XML声明自定义android UI元素


Answers:


840

《 Android开发人员指南》有一个名为构建自定义组件的部分。不幸的是,对XML属性的讨论仅涉及在布局文件中声明控件,而实际上并未处理类初始化中的值。步骤如下:

1.在中声明属性 values\attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyCustomView">
        <attr name="android:text"/>
        <attr name="android:textColor"/>            
        <attr name="extraInformation" format="string" />
    </declare-styleable>
</resources>

请注意,declare-styleable标记中使用了非限定名称。像非标准android属性extraInformation需要声明其类型。在超类中声明的标签将在子类中可用,而无需重新声明。

2.创建构造函数

由于有两个构造函数使用AttributeSet进行初始化,因此为构造函数调用创建单独的初始化方法很方便。

private void init(AttributeSet attrs) { 
    TypedArray a=getContext().obtainStyledAttributes(
         attrs,
         R.styleable.MyCustomView);

    //Use a
    Log.i("test",a.getString(
         R.styleable.MyCustomView_android_text));
    Log.i("test",""+a.getColor(
         R.styleable.MyCustomView_android_textColor, Color.BLACK));
    Log.i("test",a.getString(
         R.styleable.MyCustomView_extraInformation));

    //Don't forget this
    a.recycle();
}

R.styleable.MyCustomView是自动生成的int[]资源,其中每个元素都是属性的ID。通过将属性名称附加到元素名称,可以为XML中的每个属性生成属性。例如,R.styleable.MyCustomView_android_text包含的android_text属性MyCustomView。然后可以TypedArray使用各种get功能从中检索属性。如果该属性未在XML的定义中定义,则null返回。当然,如果返回类型是原始类型,则返回第二个参数。

如果您不想检索所有属性,则可以手动创建此数组。标准android属性的ID包含在中android.R.attr,而此项目的属性包含在中R.attr

int attrsWanted[]=new int[]{android.R.attr.text, R.attr.textColor};

请注意,您不应在中使用任何内容android.R.styleable,因为此线程将来可能会更改。仍然在文档中,因为在一处查看所有这些常量很有用。

3.在布局文件中使用它,例如 layout\main.xml

xmlns:app="http://schemas.android.com/apk/res-auto"在顶级xml元素中包括名称空间声明。命名空间提供了一种避免在不同模式使用相同元素名称时有时发生冲突的方法(有关更多信息,请参见本文)。URL只是唯一标识架构的一种方式- 在该URL上实际上不需要托管任何内容。如果这似乎没有做任何事情,那是因为您实际上不需要添加名称空间前缀,除非您需要解决冲突。

<com.mycompany.projectname.MyCustomView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:text="Test text"
    android:textColor="#FFFFFF"
    app:extraInformation="My extra information"
/> 

使用标准名称引用自定义视图。

Android LabelView示例

如果您需要完整的示例,请查看android标签视图示例。

LabelView.java

 TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.LabelView);
 CharSequences=a.getString(R.styleable.LabelView_text);

attrs.xml

<declare-styleable name="LabelView">
    <attr name="text"format="string"/>
    <attr name="textColor"format="color"/>
    <attr name="textSize"format="dimension"/>
</declare-styleable>

custom_view_1.xml

<com.example.android.apis.view.LabelView
    android:background="@drawable/blue"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    app:text="Blue" app:textSize="20dp"/>

包含在LinearLayout带有名称空间属性的中:xmlns:app="http://schemas.android.com/apk/res-auto"

链接


14
我想补充一点,如果您的根元素需要自定义名称空间,则必须同时添加标准android名称空间和您自己的自定义名称空间,否则您可能会遇到构建错误。
大通

11
这个答案是Internet上我能找到的最清晰的自定义XML参数资源。谢谢你,Casebash。
Artem Russakovskii 2011年

2
由于某种原因,可视化编辑器拒绝为android:text使用书面文本值,但设备使用它就好了。怎么来的 ?
Android开发人员

2
@androiddeveloper似乎Eclipse编辑器拒绝为所有android:属性使用值。我想知道它是否是功能或错误
deej 2013年

4
xmlns:app名称空间和res-auto的目的是什么?
IgorGanapolsky 2014年

91

很好的参考。谢谢!补充:

如果碰巧包含一个已为自定义视图声明了自定义属性的库项目,则必须声明项目名称空间,而不是库的名称空间。例如:

假定该库具有软件包“ com.example.library.customview”,而工作项目具有软件包“ com.example.customview”,则:

将不起作用(显示错误“错误:包'com.example.library.customview'“中找不到属性'newAttr'的资源标识符):

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.library.customview"
        android:id="@+id/myView"
        app:newAttr="value" />

将工作:

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.customview"
        android:id="@+id/myView"
        app:newAttr="value" />

47
此问题已在ADT 17预览中修复。要从库中使用应用程序的名称空间,xmlns:app="http://schemas.android.com/apk/res-auto"请声明参见code.google.com/p/android/issues/detail?id=9656
nmr

2
现在包括您的自定义名称空间将返回错误Suspicious namespace: Did you mean http://schemas.android.com/apk/res-auto
Ben Wilkinson

自定义名称空间以res-auto结尾,因为我们使用的是Android Studio和Gradle。否则(例如,某些Eclipse版本)通常以lib / [您的包名称]结尾
Universe

自定义名称空间的结尾是res-auto因为我们使用的是Android Studio和Gradle。否则(例如某些Eclipse版本),通常以结尾lib/[your package name]。即http://schemas.android.com/apk/lib/[your package name]
宇宙

27

除了投票最多的答案。

getStyledAttributes()

当我们使用android:xxx prdefined属性创建自定义视图时,我想添加一些有关gainStyledAttributes()用法的词。特别是当我们使用TextAppearance时。
如“ 2.创建构造函数”中所述,自定义视图在其创建时获取AttributeSet。我们可以在TextView源代码(API 16)中看到主要用法。

final Resources.Theme theme = context.getTheme();

// TextAppearance is inspected first, but let observe it later

TypedArray a = theme.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.TextView, defStyle, 0);

int n = a.getIndexCount();
for (int i = 0; i < n; i++) 
{
    int attr = a.getIndex(i);
    // huge switch with pattern value=a.getXXX(attr) <=> a.getXXX(a.getIndex(i))
}
a.recycle();

我们在这里可以看到什么?
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
根据文档,主题按主题处理属性集。属性值是逐步编译的。首先从主题填充属性,然后用样式的值替换值,最后用特殊视图实例的XML的精确值替换其他值。
所请求属性的数组- com.android.internal.R.styleable.TextView
这是常量的普通数组。如果我们需要标准属性,则可以手动构建此数组。

文档中未提及的内容-结果TypedArray元素的顺序。
在attrs.xml中声明自定义视图时,将生成属性索引的特殊常量。我们可以提取值是这样的:a.getString(R.styleable.MyCustomView_android_text)。但是对于手动int[],没有常量。我想,getXXXValue(arrayIndex)可以正常工作。

另一个问题是:“我们如何替换内部常量,并请求标准属性?” 我们可以使用android.R.attr。*值。

因此,如果我们想在自定义视图中使用标准TextAppearance属性并在构造函数中读取其值,则可以通过以下方式修改TextView中的代码:

ColorStateList textColorApp = null;
int textSize = 15;
int typefaceIndex = -1;
int styleIndex = -1;

Resources.Theme theme = context.getTheme();

TypedArray a = theme.obtainStyledAttributes(attrs, R.styleable.CustomLabel, defStyle, 0);
TypedArray appearance = null;
int apResourceId = a.getResourceId(R.styleable.CustomLabel_android_textAppearance, -1);
a.recycle();
if (apResourceId != -1)
{
    appearance = 
        theme.obtainStyledAttributes(apResourceId, new int[] { android.R.attr.textColor, android.R.attr.textSize, 
            android.R.attr.typeface, android.R.attr.textStyle });
}
if (appearance != null)
{
    textColorApp = appearance.getColorStateList(0);
    textSize = appearance.getDimensionPixelSize(1, textSize);
    typefaceIndex = appearance.getInt(2, -1);
    styleIndex = appearance.getInt(3, -1);

    appearance.recycle();
}

定义CustomLabel的位置:

<declare-styleable name="CustomLabel">
    <!-- Label text. -->
    <attr name="android:text" />
    <!-- Label text color. -->
    <attr name="android:textColor" />
    <!-- Combined text appearance properties. -->
    <attr name="android:textAppearance" />
</declare-styleable>

也许,我在某种程度上误解了,但是关于getStyledAttributes()的Android文档非常差。

扩展标准UI组件

同时,我们可以使用其所有声明的属性来扩展标准UI组件。这种方法不是很好,因为例如TextView声明了很多属性。而且将不可能在被重写的onMeasure()和onDraw()中实现全部功能。

但是我们可以牺牲自定义组件在理论上的广泛使用。说“我确切知道我将使用什么功能”,并且不要与任何人共享代码。

然后我们可以实现构造函数CustomComponent(Context, AttributeSet, defStyle)。调用之后,super(...)我们将解析所有属性,并可以通过getter方法获得这些属性。


android:xxx预定义属性在Eclipse GUI设计器中工作吗?
deej 2013年

此类属性由属性编辑器中的Eclipse ADT插件识别。如果未定义某些值,则可以从样式中看到默认值。并且不要忘记在类中添加@RemoteView批注。
yuriy.weiss

无法使其工作。Eclipse会继续为getText加载null,并为getResourceId抛出android.content.res.Resources $ NotFoundException,尽管该应用程序在设备上运行良好。
deej 2013年

对不起,我不能帮你。我仅创建了演示项目来测试可能性,而未遇到此类错误。
yuriy.weiss 2013年

这比将自定义视图的自定义属性映射到其中包含的内置视图的内置属性要好得多。
samis 2014年

13

Google似乎已经更新了其开发人员页面并在那里添加了各种培训。

其中之一处理自定义视图的创建,可以在这里找到


5

非常感谢您的第一个答案。

对于我来说,我只有一个问题。使视图膨胀时,我遇到一个错误: java.lang.NoSuchMethodException:MyView(Context,Attributes)

我通过创建一个新的构造函数来解决它:

public MyView(Context context, AttributeSet attrs) {
     super(context, attrs);
     // some code
}

希望这会有所帮助!


0

您可以将任何布局文件包括在其他布局文件中,例如-

             <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="30dp" >

                <include
                    android:id="@+id/frnd_img_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_imagefile"/>

                <include
                    android:id="@+id/frnd_video_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_video_lay" />

                <ImageView
                    android:id="@+id/downloadbtn"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_centerInParent="true"
                    android:src="@drawable/plus"/>
            </RelativeLayout>

这里include标记中的布局文件是同一res文件夹中的其他.xml布局文件。


我已经尝试过了,但我遇到的问题是所包含的布局无法“适应”,无法创建泛型。例如,当我以类似方式包括按钮时,如果我尝试在xml中设置文本,则它确实起作用。
cfl
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.