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

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

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


person Alexander Oleynikov    schedule 09.08.2010    source источник
comment
Эти документы могут быть новее, чем ваше сообщение, но для того, чтобы поддерживать их в актуальном состоянии, вы можете найти хорошую официальную документацию по атрибутам здесь: developer.android.com/training/custom-views/.   -  person OYRM    schedule 02.04.2013
comment
может оказаться полезным небольшой рабочий пример: github.com/yujiaao/MergeLayout1   -  person Yu Jiaao    schedule 02.11.2016


Ответы (5)


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

Конец. :)

person Rich Schuler    schedule 09.08.2010
comment
Вот пример проекта, демонстрирующего настраиваемые атрибуты для использования с настраиваемым View: github.com/commonsguy/cw-advandroid/tree/master/Views/ - person CommonsWare; 09.08.2010
comment
На этом этапе нельзя определить макет в пакете библиотеки и использовать его в различных основных проектах, если он использует настраиваемые атрибуты. Это связано с тем, как пространство имен XML должно быть объявлено при использовании настраиваемых атрибутов. См. это обсуждение в списке проблем Android. - person Ted Hopp; 16.05.2011
comment
Если вы используете настраиваемые атрибуты из проекта библиотеки: см. Этот вопрос: stackoverflow.com/questions/5819369/ - Кажется, это работает, если вы используете xmlns:my="http://schemas.android.com/apk/lib/my.namespace" - нет копирования attrs.xml . Обратите внимание, что путь URI пространства имен должен быть / apk / * lib *, а не / apk / res. - person thom_nic; 01.03.2012
comment
Как я могу решить проблему, если один и тот же атрибут существует в двух пакетах? Предположим, что атрибут <attr name="title" format="string"/> существует в пакете com.pack1module1), а также тот же атрибут существует в com.pack2module2), и оба модуля указаны в module3 (исполняемый файл). Я получил ошибку Attribute "title" has already been defined. - person anonim; 28.08.2012
comment
Извините за некро, но если вы поместите ‹attr› прямо в корень ‹resources›, как вы тогда будете ссылаться на атрибуты в классе представления? - person obie; 07.09.2012
comment
Верно ли, что допустимые значения для format и их значения нигде не описаны в официальных документах? Я хорошо посмотрел и не могу их найти. Кажется странным для такой важной особенности архитектуры. - person JulianSymes; 01.12.2012
comment
@ThomNichols, трюк apk/lib не сработал для меня с настраиваемыми атрибутами со справочным форматом из проекта библиотеки. Что сделал, так это использование apk/res-auto, как предложено в stackoverflow.com/a/13420366/22904 чуть ниже, а также в stackoverflow.com/a/10217752 - person Giulio Piancastelli; 14.01.2013
comment
Я обновил ответ, чтобы использовать apk / res-auto, так как теперь это лучший способ сделать это и гораздо менее запутанный. - person Intrications; 07.02.2013
comment
Такой способ определения настраиваемых атрибутов будет работать с любым типом ресурса? Скажите, например, в drawable или values xml файлах? - person taylor; 24.04.2013
comment
Цитирование @Qberticus: атрибуты флага аналогичны, за исключением того, что значения должны быть определены, чтобы их можно было объединить вместе. На мой взгляд, это как бы преуменьшение основного различия между enum и flag: первое позволяет нам выбрать одно и только одно значение, второе позволяет нам комбинировать несколько. Я написал более подробный ответ на аналогичный вопрос здесь, и теперь нашел этот вопрос Я решил, что на это свяжусь. - person Rad Haring; 13.03.2014
comment
Я использую xmlns:xxxx="http://schemas.android.com/apk/res/my_package_name" и он работает - person suitianshi; 22.07.2014
comment
Можно ли сделать атрибут обязательным? Что-то вроде этого, к сожалению, не работает: ‹attr name = android: gravity required = true /› - person Couitchy; 23.09.2014
comment
a.recycle() здесь очень важен для освобождения памяти - person Tash Pemhiwa; 21.01.2016
comment
Привет, а как насчет десятичных значений? Мне нужно принять значение, например, 20,5 или 0,51 и т. Д. - person Vincent_Paing; 13.09.2016
comment
для меня MyCustomView(Context context, AttributeSet attrs, int defStyle) не вызывался, поэтому изменение на MyCustomView(Context context, AttributeSet attrs) сработало, и я использовал context.obtainStyledAttributes(attrs,R.styleable.MyCustomView), чтобы получить TypedArray. - person Zeeshan Ghazanfar; 27.09.2016
comment
Это действительно должно быть выделено жирным шрифтом: Обратите внимание, что все атрибуты используют одно и то же глобальное пространство имен. Это означает, что даже если вы создадите новый атрибут внутри элемента ‹declare-styleable›, его можно будет использовать вне его, и вы не сможете создать другой атрибут с тем же именем другого типа. Это так нелогично. и может легко стать причиной, по которой вы не можете создавать ресурсы и понятия не имеете, почему. - person Varvara Kalinina; 28.05.2017
comment
Как тогда width и height работают со значениями типа 16dp и match_parent? Они dimension|enum? - person Michel Feinstein; 03.09.2018

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

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

с участием:

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

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

person Neil Miller    schedule 16.11.2012
comment
Это было добавлено совсем недавно ... Думаю, несколько недель назад. Конечно, он был добавлен спустя много времени после того, как Кбертикус написал свой ответ. - person ArtOfWarfare; 20.11.2012
comment
Я думаю, что он старше, но он определенно был добавлен спустя много времени после того, как Qberticus написал свой ответ. Ни в коем случае не обвиняя его, просто добавив полезную деталь. - person Neil Miller; 27.11.2012
comment
Я обновил ответ Qbericus, чтобы использовать apk / res-auto, чтобы избежать путаницы. - person Intrications; 07.02.2013

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

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

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

person Helios    schedule 20.11.2017
comment
Вы можете добиться того же с помощью привязки данных Google - если нет привязки атрибута для определенного атрибута, GDB пытается найти метод set * и использует его вместо этого. В этом случае вам нужно будет написать, скажем, android:title="@{&quot;Hello, world!&quot;}". - person Spook; 04.02.2019

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

  • пример из attr. xml.
  • Android Studio understands that the class is being referenced from XML
    • i.e.
      • Refactor > Rename works
      • 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()
            }
        }
    }
person Eric    schedule 20.09.2019