Визначення спеціальних attrs


472

Мені потрібно реалізувати власні атрибути, як у com.android.R.attr

В офіційній документації нічого не знайдено, тому мені потрібна інформація про те, як визначити ці attrs та як їх використовувати з мого коду.


20
Ці документи можуть бути новішими, ніж ваша публікація, але для того, щоб зберегти цю актуальність, ви можете знайти хорошу офіційну документацію щодо атрибутів тут: developer.android.com/training/custom-views/…
OYRM

Я рекомендую приємну статтю з прикладом про власні атрибути: amcmobileware.org/android/blog/2016/09/11/custom-attributes
Arkadiusz Cieśliński

невеликий робочий приклад може бути корисним: github.com/yujiaao/MergeLayout1
Yu Jiaao

Відповіді:


971

Наразі найкраща документація - це джерело. Ви можете подивитися тут (attrs.xml) .

Ви можете визначити атрибути у верхньому <resources>або всередині <declare-styleable>елемента. Якщо я збираюся використовувати attr у більш ніж одному місці, я поміщую його в елемент root. Зауважте, всі атрибути мають однаковий глобальний простір імен. Це означає, що навіть якщо ви створюєте новий атрибут всередині <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, наприклад, 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();
}

Кінець. :)


14
Ось зразок проекту, що демонструє власні атрибути для використання із користувацьким View: github.com/commonsguy/cw-advandroid/tree/master/Views/…
CommonsWare

7
Якщо ви використовуєте користувацькі attrs з бібліотечного проекту: див. Це питання: stackoverflow.com/questions/5819369/… - Це, здається, працює, якщо ви використовуєте xmlns:my="http://schemas.android.com/apk/lib/my.namespace"- без копіювання attrs.xml. Зверніть увагу, шлях URI простору імен повинен бути / apk / * lib * not / apk / res.
thom_nic

2
@ThomNichols apk/libхитрість не працювала для мене на користувальницьких атрибутах із форматом довідки з бібліотечного проекту. Що ж робота полягала у використанні apk/res-auto, як це було запропоновано в stackoverflow.com/a/13420366/22904 трохи нижче , а також в stackoverflow.com/a/10217752
Джуліо Piancastelli

1
Цитуючи @Qberticus: "Атрибути прапорця схожі, за винятком значень, які потрібно визначити, щоб їх можна було трохи розбити". На мою думку, це є своєрідним заниженням основної різниці між enumі flag: перше дозволяє нам вибрати одне і лише одне значення, а друге дозволяє об'єднати декілька. Я написав довшу відповідь на подібне запитання тут , і, знайшовши це питання, я зрозумів, що буду посилатися на це.
Рад Харінг

5
a.recycle()Тут дуже важливо звільнити пам'ять
Tash Pemhiwa

87

Відповідь Qberticus хороша, але одна корисна деталь відсутня. Якщо ви реалізуєте їх у бібліотеці, замініть:

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

з:

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

Інакше програма, яка використовує бібліотеку, матиме помилки під час виконання.


3
Це було нещодавно додано ... Я думаю, що протягом декількох тижнів тому. Звичайно, це було додано ще задовго після того, як Qberticus написав свою відповідь.
ArtOfWarfare

12
Я думаю, що він старший за це, але це, безумовно, було додано задовго після того, як Qberticus написав свою відповідь. Не винувативши його зовсім, просто додавши корисну деталь.
Ніл Міллер

11
Я оновив відповідь Qbericus, щоб використовувати apk / res-auto для збереження плутанини.
інтригації

15

Відповідь вище висвітлює все докладно, окрім кількох речей.

По-перше, якщо стилів немає, тоді (Context context, AttributeSet attrs)підпис методу буде використовуватися для інстанціювання налаштування. У цьому випадку просто використовуйте context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)для отримання TypedArray.

По-друге, це не стосується способів поводження з ресурсами плауралів (рядкових кількостей). З ними не вдається вирішити використання TypedArray. Ось фрагмент коду з моєї SeekBarPreference, який встановлює резюме перевагу, форматуючи його значення відповідно до значення налаштування. Якщо xml для набору переваг має android: резюме до текстового рядка або ряду 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методі уподобань .

5

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


Це можна досягти і з прив'язкою даних Google - якщо немає прив’язки атрибутів для конкретного атрибута, GDB намагається знайти метод * і використовує його замість нього. У цьому випадку вам доведеться писати, скажімо android:title="@{&quot;Hello, world!&quot;}".
Відьмак

0

якщо ви опустите 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()
            }
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.