【问题标题】:Defining custom attrs定义自定义属性
【发布时间】:2011-03-27 08:45:27
【问题描述】:

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

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

【问题讨论】:

标签: android android-resources android-attributes


【解决方案1】:

目前最好的文档是源。你可以看看here (attrs.xml)

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

<attr> 元素有两个 xml 属性 nameformatname 让你可以给它命名,这就是你最终在代码中引用它的方式,例如,R.attr.my_attributeformat 属性可以有不同的值,具体取决于您想要的属性的“类型”。

  • reference - 如果它引用另一个资源 ID(例如,“@color/my_color”、“@layout/my_layout”)
  • 颜色
  • 布尔值
  • 维度
  • 浮动
  • 整数
  • 字符串
  • 分数
  • 枚举 - 通常是隐式定义的
  • 标志 - 通常是隐式定义的

您可以使用| 将格式设置为多种类型,例如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>

除了属性之外,还有&lt;declare-styleable&gt; 元素。这允许您定义自定义视图可以使用的属性。您可以通过指定 &lt;attr&gt; 元素来执行此操作,如果之前已定义,则无需指定 format。如果您希望重用一个 android attr,例如 android:gravity,那么您可以在 name 中执行此操作,如下所示。

自定义视图示例&lt;declare-styleable&gt;

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

结束。 :)

【讨论】:

  • 这是一个示例项目,演示了用于自定义 View 的自定义属性:github.com/commonsguy/cw-advandroid/tree/master/Views/…
  • 如果您正在使用库项目中的自定义 attrs:请参阅此问题:stackoverflow.com/questions/5819369/… - 如果您使用 xmlns:my="http://schemas.android.com/apk/lib/my.namespace",它似乎可以工作 - 请勿复制 attrs.xml。注意命名空间 URI 路径必须是 /apk/*lib* 而不是 /apk/res。
  • @ThomNichols apk/lib 技巧不适用于库项目中具有参考格式的自定义属性。正如下面的stackoverflow.com/a/13420366/22904stackoverflow.com/a/10217752 中所建议的那样, 起作用的是使用apk/res-auto
  • 引用@Qberticus:“标志属性是相似的,只是需要定义值,以便它们可以一起进行位或运算”。在我看来,这有点低估了enumflag 之间的主要区别:前者让我们选择一个且只有一个值,后者让我们组合几个。我在类似的问题here 中写了一个更长的答案,现在找到了这个问题,我想我会链接到那个。
  • a.recycle()在这里释放内存很重要
【解决方案2】:

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

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

与:

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

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

【讨论】:

  • 这是最近才添加的……我想是在几周前。当然,它是在 Qberticus 写下他的答案很久之后添加的。
  • 我认为它比这更老,但它肯定是在 Qberticus 写下他的答案很久之后添加的。完全没有指责他,只是添加了一个有用的细节。
  • 我已经更新了 Qbericus 的答案以使用 apk/res-auto 来避免混淆。
【解决方案3】:

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

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

其次,它不包括如何处理 plaurals 资源(数量字符串)。这些无法使用 TypedArray 处理。这是我的 SeekBarPreference 中的一个代码 sn-p,它根据首选项的值设置首选项的摘要,格式化其值。如果首选项的 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);
}

  • 这只是作为示例,但是,如果您想在首选项屏幕上设置摘要,则需要在首选项的 onDialogClosed 方法中调用 notifyChanged()

【讨论】:

    【解决方案4】:

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

    第 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 类。

    第四步:在自定义视图的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 存储库以获取更多信息和示例。

    【讨论】:

    • 您可以使用 Google 数据绑定来实现相同的效果 - 如果特定属性没有属性绑定,GDB 会尝试查找 set* 方法并使用它。在这种情况下,您必须写信,比如android:title="@{&amp;quot;Hello, world!&amp;quot;}"
    【解决方案5】:

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

    • 来自attrs.xml 的示例。
    • Android Studio 了解该类是从 XML 引用的
        • Refactor &gt; Rename 工作
        • Find Usages 工作
        • 等等……

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

    <?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()
                }
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-26
      • 1970-01-01
      • 2011-07-19
      • 1970-01-01
      • 2011-01-24
      相关资源
      最近更新 更多