Определение пользовательских атрибутов

Мне нужно реализовать свои собственные атрибуты, например, в com.android.R.attr

Ничего не найдено в официальной документации, поэтому мне нужна информация о том, как определить эти атрибуты и как их использовать из моего кода.

Ответ 1

В настоящее время источником является лучшая документация. Вы можете посмотреть здесь здесь (attrs.xml).

Вы можете определить атрибуты в верхнем элементе <resources> или внутри элемента <declare-styleable>. Если я собираюсь использовать attr в более чем одном месте, я помещаю его в корневой элемент. Обратите внимание: все атрибуты имеют одно и то же глобальное пространство имен. Это означает, что даже если вы создаете новый атрибут внутри элемента <declare-styleable>, его можно использовать вне его, и вы не можете создать другой атрибут с тем же именем другого типа.

Элемент <attr> имеет два атрибута xml name и format. name позволяет вам называть его чем-то, и вот как вы в конечном итоге ссылаетесь на него в коде, например, R.attr.my_attribute. Атрибут format может иметь разные значения в зависимости от типа вашего атрибута.

  • ссылка - если он ссылается на другой идентификатор ресурса (например, "@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. Если вы хотите повторно использовать андроид 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();
}

Конец.:)

Ответ 2

Ответ Qberticus хорош, но одна полезная деталь отсутствует. Если вы реализуете их в библиотеке, замените:

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

с:

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

В противном случае приложение, использующее библиотеку, будет иметь ошибки времени выполнения.

Ответ 3

Ответ выше подробно описывает все, кроме нескольких вещей.

Во-первых, если нет стилей, для подтверждения предпочтения будет использоваться подпись метода (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.

Ответ 4

Традиционный подход наполнен шаблоном кода и неуклюжей обработкой ресурсов. Вот почему я создал инфраструктуру 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 для получения дополнительной информации и примеров.

Ответ 5

если вы опустите атрибут format в элементе attr, вы можете использовать его для ссылки на класс из макетов XML.

  • пример из attrs.xml.
  • Android Studio понимает, что на класс ссылаются из 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()
            }
        }
    }