-
27-09-2019 - |
题
我需要实现我自己的属性,例如 com.android.R.attr
在官方文档中找不到任何内容,因此我需要有关如何定义这些属性以及如何从我的代码中使用它们的信息。
解决方案
目前最好的文档是源代码。你可以看一下 这里(attrs.xml).
您可以在顶部定义属性 <resources>
元素或a的内部 <declare-styleable>
元素。如果我要在多个地方使用 attr,我会将其放在根元素中。请注意,所有属性共享相同的全局命名空间。这意味着即使您在 <declare-styleable>
元素它可以在其外部使用,并且您不能创建具有不同类型的相同名称的另一个属性。
一个 <attr>
元素有两个 xml 属性 name
和 format
. name
让你可以给它起个名字,这就是你最终在代码中引用它的方式,例如, R.attr.my_attribute
. 。这 format
属性可以具有不同的值,具体取决于您想要的属性的“类型”。
- 参考 - 如果它引用另一个资源 ID(例如,“@color/my_color”、“@layout/my_layout”)
- 颜色
- 布尔值
- 方面
- 漂浮
- 整数
- 细绳
- 分数
- 枚举 - 通常是隐式定义的
- 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();
}
结束。:)
其他提示
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 设置为 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
方法。
传统的方法是充分的样板代码和笨拙资源处理。这就是为什么我做了望远镜框架。为了演示它是如何工作的,这里是展示如何制作一个自定义视图,其中显示字符串标题的例子。
步骤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
方法的属性值告诉望远镜框架路由到该方法中当视图被充气
@HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
titleView.setText(title);
}
现在您的类有一个望远镜注解,该望远镜框架将在编译时检测到它并自动生成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实例化类,该望远镜的同伴解释的属性,使所需的方法调用。例如,如果我们膨胀以下布局然后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库的更多信息和实施例。
如果你省略 format
属性来自于 attr
元素,您可以使用它来引用 XML 布局中的类。
- 示例来自 属性.xml.
- Android Studio 知道该类是从 XML 引用的
- IE。
Refactor > Rename
作品Find Usages
作品- 等等...
- IE。
不指定 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>
在某些布局文件中使用它 .../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()
}
}
}