定义自定义attrs

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

在官方文档中没有发现任何东西,所以我需要关于如何定义这些attrs以及如何从我的代码中使用它们的信息。

297355 次浏览

目前最好的文档是源代码。你可以看一下。

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

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

  • 引用——如果它引用了另一个资源id(例如,"@color/my_color", "@layout/my_layout")
  • 颜色
  • 布尔
  • 浮动
  • 整数
  • 字符串
  • 分数
  • Enum—通常隐式定义
  • 标志-通常隐式定义

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

最后。:)

Qberticus的回答很好,但是漏掉了一个有用的细节。如果你在库中实现这些替换:

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

:

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

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

上面的答案涵盖了所有的细节,除了一些事情。

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

其次,它没有涉及如何处理多个资源(数量字符串)。这些不能使用TypedArray处理。下面是来自我的SeekBarPreference的一个代码片段,它设置了偏好的摘要,根据偏好的值格式化其值。如果首选项的xml将android:summary设置为文本字符串或字符串资源,首选项的值将被格式化为字符串(它应该有%d,以获取该值)。如果android:summary被设置为一个多重资源,那么它将被用来格式化结果。

// 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()

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

步骤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);
}
}

步骤2:在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回购以获得更多信息和示例。

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

  • attrs.xml的例子。
  • Android Studio理解类是从XML引用的
      <李>。
      • Refactor > Rename作品
      • Find Usages作品
      • 等等……
      • 李< / ul > < / > 李< / ul > < / >

      不要在…/ src / main / res /价值/ 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 /布局/ 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()
      }
      }
      }
      
  • 在这里是创建自定义属性和视图的官方文档