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

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

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

6 ответов

Решение

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

Во-вторых, он не охватывает, как обращаться с ресурсами plaurals (количественными строками). Это не может быть решено с помощью TypedArray. Вот фрагмент кода из моего SeekBarPreference, который устанавливает сводку предпочтения, форматируя его значение в соответствии со значением предпочтения. Если xml для предпочтения устанавливает android: summary в текстовую строку или в строку resouce, значение предпочтения форматируется в строку (в ней должно быть% 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 метод.

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

Если вы опустите 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()
            }
        }
    }
  • ЗДЕСЬ официальная документация по созданию пользовательских атрибутов и представлений.
Другие вопросы по тегам