Android 定义自定义属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3441396/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 10:12:53  来源:igfitidea点击:

Defining custom attrs

androidandroid-resourcesandroid-attributes

提问by Alexander Oleynikov

I need to implement my own attributes like in com.android.R.attr

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

Found nothing in official documentation so I need information about how to define these attrs and how to use them from my code.

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

回答by Rich Schuler

Currently the best documentation is the source. You can take a look at it here (attrs.xml).

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

You can define attributes in the top <resources>element or inside of a <declare-styleable>element. If I'm going to use an attr in more than one place I put it in the root element. Note, all attributes share the same global namespace. That means that even if you create a new attribute inside of a <declare-styleable>element it can be used outside of it and you cannot create another attribute with the same name of a different type.

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

An <attr>element has two xml attributes nameand format. namelets you call it something and this is how you end up referring to it in code, e.g., R.attr.my_attribute. The formatattribute can have different values depending on the 'type' of attribute you want.

一个<attr>元素有两个 xml 属性nameformat. name允许您将其称为某物,这就是您最终在代码中引用它的方式,例如,R.attr.my_attribute. 根据所需的format属性“类型”,该属性可以具有不同的值。

  • reference - if it references another resource id (e.g, "@color/my_color", "@layout/my_layout")
  • color
  • boolean
  • dimension
  • float
  • integer
  • string
  • fraction
  • enum - normally implicitly defined
  • flag - normally implicitly defined
  • 引用 - 如果它引用了另一个资源 ID(例如,“@color/my_color”、“@layout/my_layout”)
  • 颜色
  • 布尔值
  • 尺寸
  • 漂浮
  • 整数
  • 细绳
  • 分数
  • 枚举 - 通常隐式定义
  • 标志 - 通常隐式定义

You can set the format to multiple types by using |, e.g., format="reference|color".

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

enumattributes can be defined as follows:

enum属性可以定义如下:

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

flagattributes are similar except the values need to be defined so they can be bit ored together:

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

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

In addition to attributes there is the <declare-styleable>element. This allows you to define attributes a custom view can use. You do this by specifying an <attr>element, if it was previously defined you do not specify the format. If you wish to reuse an android attr, for example, android:gravity, then you can do that in the name, as follows.

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

An example of a custom view <declare-styleable>:

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

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

When defining your custom attributes in XML on your custom view you need to do a few things. First you must declare a namespace to find your attributes. You do this on the root layout element. Normally there is only xmlns:android="http://schemas.android.com/apk/res/android". You must now also add xmlns:whatever="http://schemas.android.com/apk/res-auto".

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

Example:

例子:

<?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>

Finally, to access that custom attribute you normally do so in the constructor of your custom view as follows.

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

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

The end. :)

结束。:)

回答by Neil Miller

Qberticus's answer is good, but one useful detail is missing. If you are implementing these in a library replace:

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

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

with:

和:

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

Otherwise the application that uses the library will have runtime errors.

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

回答by Steve Waring

The answer above covers everything in great detail, apart from a couple of things.

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

First, if there are no styles, then the (Context context, AttributeSet attrs)method signature will be used to instantiate the preference. In this case just use context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)to get the TypedArray.

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

Secondly it does not cover how to deal with plaurals resources (quantity strings). These cannot be dealt with using TypedArray. Here is a code snippet from my SeekBarPreference that sets the summary of the preference formatting its value according to the value of the preference. If the xml for the preference sets android:summary to a text string or a string resouce the value of the preference is formatted into the string (it should have %d in it, to pick up the value). If android:summary is set to a plaurals resource, then that is used to format the result.

其次,它不包括如何处理 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);
}


  • This is just given as an example, however, if you want are tempted to set the summary on the preference screen, then you need to call notifyChanged()in the preference's onDialogClosedmethod.
  • 这仅作为示例给出,但是,如果您想在首选项屏幕上设置摘要,则需要调用notifyChanged()首选项的onDialogClosed方法。

回答by Helios

The traditional approach is full of boilerplate code and clumsy resource handling. That's why I made the Spyglass framework. To demonstrate how it works, here's an example showing how to make a custom view that displays a String title.

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

Step 1: Create a custom view class.

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

Step 2: Define a string attribute in the values/attrs.xmlresource file:

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

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

Step 3: Apply the @StringHandlerannotation to the setTitlemethod to tell the Spyglass framework to route the attribute value to this method when the view is inflated.

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

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

Now that your class has a Spyglass annotation, the Spyglass framework will detect it at compile-time and automatically generate the CustomView_SpyglassCompanionclass.

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

Step 4: Use the generated class in the custom view's initmethod:

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

That's it. Now when you instantiate the class from XML, the Spyglass companion interprets the attributes and makes the required method call. For example, if we inflate the following layout then setTitlewill be called with "Hello, World!"as the argument.

就是这样。现在,当您从 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>

The framework isn't limited to string resources has lots of different annotations for handling other resource types. It also has annotations for defining default values and for passing in placeholder values if your methods have multiple parameters.

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

Have a look at the Github repo for more information and examples.

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

回答by Eric

if you omit the formatattribute from the attrelement, you can use it to reference a class from XML layouts.

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

  • example from attrs.xml.
  • Android Studio understands that the class is being referenced from XML
    • i.e.
      • Refactor > Renameworks
      • Find Usagesworks
      • and so on...
  • 来自attrs.xml 的示例。
  • Android Studio 知道该类是从 XML 引用的
    • IE
      • Refactor > Rename作品
      • Find Usages作品
      • 等等...

don't specify a formatattribute in .../src/main/res/values/attrs.xml

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

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

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

</resources>

use it in some layout file .../src/main/res/layout/activity__main_menu.xml

在一些布局文件中使用它.../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>

parse the class in your view initialization code .../src/main/java/.../MyCustomView.kt

解析视图初始化代码中的类.../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()
            }
        }
    }