507

我需要实现自己的属性,例如com.android.R.attr

在官方文档中找不到任何内容,因此我需要有关如何定义这些属性以及如何从我的代码中使用它们的信息。

4

5 回答 5

1033

目前最好的文档是源。您可以在此处查看它 (attrs.xml)

您可以在顶部<resources>元素或元素内部定义属性<declare-styleable>。如果我要在多个地方使用 attr,我将它放在根元素中。请注意,所有属性共享相同的全局命名空间。这意味着即使您在<declare-styleable>元素内部创建了一个新属性,它也可以在元素外部使用,并且您不能创建具有不同类型的相同名称的另一个属性。

一个<attr>元素有两个 xml 属性nameformat. name让您称其为某事,这就是您最终在代码中引用它的方式,例如R.attr.my_attribute. 该format属性可以具有不同的值,具体取决于您想要的属性的“类型”。

  • reference - 如果它引用另一个资源 id(例如,“@color/my_color”、“@layout/my_layout”)
  • 颜色
  • 布尔值
  • 方面
  • 漂浮
  • 整数
  • 细绳
  • 分数
  • enum - 通常是隐式定义的
  • flag - 通常是隐式定义的

您可以使用 将格式设置为多种类型|,例如format="reference|color".

enum属性可以定义如下:

<attr name="my_enum_attr">
  <enum name="value1" value="1" />
  <enum name="value2" value="2" />
</attr>

flag属性是相似的,除了需要定义值以便它们可以位或在一起:

<attr name="my_flag_attr">
  <flag name="fuzzy" value="0x01" />
  <flag name="cold" value="0x02" />
</attr>

除了属性,还有<declare-styleable>元素。这允许您定义自定义视图可以使用的属性。你可以通过指定一个<attr>元素来做到这一点,如果它之前定义过,你不指定format. 如果您希望重用一个 android attr,例如 android:gravity,那么您可以在 中执行此操作name,如下所示。

自定义视图的示例<declare-styleable>

<declare-styleable name="MyCustomView">
  <attr name="my_custom_attribute" />
  <attr name="android:gravity" />
</declare-styleable>

在自定义视图上以 XML 定义自定义属性时,您需要做一些事情。首先,您必须声明一个命名空间来查找您的属性。您在根布局元素上执行此操作。通常只有xmlns:android="http://schemas.android.com/apk/res/android". 您现在还必须添加xmlns:whatever="http://schemas.android.com/apk/res-auto".

例子:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:whatever="http://schemas.android.com/apk/res-auto"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">

    <org.example.mypackage.MyCustomView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:gravity="center"
      whatever:my_custom_attribute="Hello, world!" />
</LinearLayout>

最后,要访问该自定义属性,您通常在自定义视图的构造函数中执行以下操作。

public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);

  TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);

  String str = a.getString(R.styleable.MyCustomView_my_custom_attribute);

  //do something with str

  a.recycle();
}

结束。:)

于 2010-08-09T16:11:24.477 回答
89

Qberticus 的回答很好,但缺少一个有用的细节。如果您在库中实现这些替换:

xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"

和:

xmlns:whatever="http://schemas.android.com/apk/res-auto"

否则使用该库的应用程序将出现运行时错误。

于 2012-11-16T16:18:08.303 回答
16

上面的答案非常详细地涵盖了所有内容,除了几件事。

首先,如果没有样式,则(Context context, AttributeSet attrs)方法签名将用于实例化首选项。在这种情况下,只需使用context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)获取 TypedArray。

其次,它不包括如何处理 plaurals 资源(数量字符串)。这些无法使用 TypedArray 处理。这是我的 SeekBarPreference 中的一个代码片段,它根据首选项的值设置首选项的摘要,格式化其值。如果首选项的 xml 将 android:summary 设置为文本字符串或字符串资源,则首选项的值将格式化为字符串(其中应该包含 %d 以获取值)。如果 android:summary 设置为 plaurals 资源,则用于格式化结果。

// Use your own name space if not using an android resource.
final static private String ANDROID_NS = 
    "http://schemas.android.com/apk/res/android";
private int pluralResource;
private Resources resources;
private String summary;

public SeekBarPreference(Context context, AttributeSet attrs) {
    // ...
    TypedArray attributes = context.obtainStyledAttributes(
        attrs, R.styleable.SeekBarPreference);
    pluralResource =  attrs.getAttributeResourceValue(ANDROID_NS, "summary", 0);
    if (pluralResource !=  0) {
        if (! resources.getResourceTypeName(pluralResource).equals("plurals")) {
            pluralResource = 0;
        }
    }
    if (pluralResource ==  0) {
        summary = attributes.getString(
            R.styleable.SeekBarPreference_android_summary);
    }
    attributes.recycle();
}

@Override
public CharSequence getSummary() {
    int value = getPersistedInt(defaultValue);
    if (pluralResource != 0) {
        return resources.getQuantityString(pluralResource, value, value);
    }
    return (summary == null) ? null : String.format(summary, value);
}

  • 这只是作为示例,但是,如果您想在首选项屏幕上设置摘要,则需要调用notifyChanged()首选项的onDialogClosed方法。
于 2014-09-03T15:33:47.257 回答
5

传统方法充满了样板代码和笨拙的资源处理。这就是我制作Spyglass 框架的原因。为了演示它是如何工作的,这里有一个示例,展示了如何制作一个显示字符串标题的自定义视图。

第 1 步:创建自定义视图类。

public class CustomView extends FrameLayout {
    private TextView titleView;

    public CustomView(Context context) {
        super(context);
        init(null, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr, 0);
    }

    @RequiresApi(21)
    public CustomView(
            Context context, 
            AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes) {

        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    public void setTitle(String title) {
        titleView.setText(title);
    }

    private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        inflate(getContext(), R.layout.custom_view, this);

        titleView = findViewById(R.id.title_view);
    }
}

第二步:在values/attrs.xml资源文件中定义一个字符串属性:

<resources>
    <declare-styleable name="CustomView">
        <attr name="title" format="string"/>
    </declare-styleable>
</resources>

第 3 步:将@StringHandler注解应用到setTitle方法上,以告诉 Spyglass 框架在视图膨胀时将属性值路由到此方法。

@HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
    titleView.setText(title);
}

现在您的类有一个 Spyglass 注释,Spyglass 框架将在编译时检测它并自动生成CustomView_SpyglassCompanion该类。

第 4 步:在自定义视图的init方法中使用生成的类:

private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    inflate(getContext(), R.layout.custom_view, this);

    titleView = findViewById(R.id.title_view);

    CustomView_SpyglassCompanion
            .builder()
            .withTarget(this)
            .withContext(getContext())
            .withAttributeSet(attrs)
            .withDefaultStyleAttribute(defStyleAttr)
            .withDefaultStyleResource(defStyleRes)
            .build()
            .callTargetMethodsNow();
}

就是这样。现在,当您从 XML 实例化类时,Spyglass 伴侣会解释属性并进行所需的方法调用。例如,如果我们对下面的布局进行膨胀,那么setTitle"Hello, World!"作为参数调用。

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:width="match_parent"
    android:height="match_parent">

    <com.example.CustomView
        android:width="match_parent"
        android:height="match_parent"
        app:title="Hello, World!"/>
</FrameLayout>

该框架不仅限于字符串资源,还有许多不同的注释来处理其他资源类型。如果您的方法有多个参数,它还具有用于定义默认值和传入占位符值的注释。

查看 Github 存储库以获取更多信息和示例。

于 2017-11-20T12:30:36.333 回答
1

如果省略元素中的format属性,则attr可以使用它来引用 XML 布局中的类。

  • 来自attrs.xml的示例。
  • Android Studio 了解该类是从 XML 引用的
    • IE
      • Refactor > Rename作品
      • Find Usages作品
      • 等等...

不要在.../src/main/res/values/attrs.xmlformat中指定属性

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyCustomView">
        ....
        <attr name="give_me_a_class"/>
        ....
    </declare-styleable>

</resources>

在一些布局文件中使用它.../src/main/res/layout/activity__main_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<SomeLayout
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- make sure to use $ dollar signs for nested classes -->
    <MyCustomView
        app:give_me_a_class="class.type.name.Outer$Nested/>

    <MyCustomView
        app:give_me_a_class="class.type.name.AnotherClass/>

</SomeLayout>

解析视图初始化代码中的类.../src/main/java/.../MyCustomView.kt

class MyCustomView(
        context:Context,
        attrs:AttributeSet)
    :View(context,attrs)
{
    // parse XML attributes
    ....
    private val giveMeAClass:SomeCustomInterface
    init
    {
        context.theme.obtainStyledAttributes(attrs,R.styleable.ColorPreference,0,0).apply()
        {
            try
            {
                // very important to use the class loader from the passed-in context
                giveMeAClass = context::class.java.classLoader!!
                        .loadClass(getString(R.styleable.MyCustomView_give_me_a_class))
                        .newInstance() // instantiate using 0-args constructor
                        .let {it as SomeCustomInterface}
            }
            finally
            {
                recycle()
            }
        }
    }
于 2019-09-20T06:54:45.983 回答