Мне нужно реализовать свои собственные атрибуты, например, в com.android.R.attr
Ничего не найдено в официальной документации, поэтому мне нужна информация о том, как определить эти атрибуты и как их использовать из моего кода.
Мне нужно реализовать свои собственные атрибуты, например, в com.android.R.attr
Ничего не найдено в официальной документации, поэтому мне нужна информация о том, как определить эти атрибуты и как их использовать из моего кода.
В настоящее время источником является лучшая документация. Вы можете посмотреть здесь здесь (attrs.xml).
Вы можете определить атрибуты в верхнем элементе <resources>
или внутри элемента <declare-styleable>
. Если я собираюсь использовать attr в более чем одном месте, я помещаю его в корневой элемент. Обратите внимание: все атрибуты имеют одно и то же глобальное пространство имен. Это означает, что даже если вы создаете новый атрибут внутри элемента <declare-styleable>
, его можно использовать вне его, и вы не можете создать другой атрибут с тем же именем другого типа.
Элемент <attr>
имеет два атрибута xml name
и format
. name
позволяет вам называть его чем-то, и вот как вы в конечном итоге ссылаетесь на него в коде, например, R.attr.my_attribute
. Атрибут format
может иметь разные значения в зависимости от типа вашего атрибута.
Вы можете установить формат для нескольких типов с помощью |
, например, 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
. Если вы хотите повторно использовать андроид 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.
Во-вторых, он не охватывает, как обращаться с ресурсами plaurals (количество строк). Их нельзя использовать с помощью TypedArray. Вот фрагмент кода из моего SeekBarPreference, который устанавливает сводку предпочтения, форматируя его значение в соответствии со значением предпочтения. Если xml для предпочтения устанавливает android: сводка в текстовую строку или строку resouce, значение предпочтения отформатируется в строку (в ней должно быть% d, чтобы получить значение). Если андроид: сводка задана ресурсом 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
.Традиционный подход наполнен шаблоном кода и неуклюжей обработкой ресурсов. Вот почему я создал инфраструктуру 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. Определите атрибут string в файле ресурсов 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 для получения дополнительной информации и примеров.
если вы опустите атрибут format
в элементе attr
, вы можете использовать его для ссылки на класс из макетов XML.
Refactor > Rename
работаетFind Usages
работаетне указывайте атрибут 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()
}
}
}