Объявление пользовательского элемента пользовательского интерфейса Android с использованием XML

473

Как объявить элемент пользовательского интерфейса Android с помощью XML?

Casebash
источник
15
Если кто-то ищет список поддерживаемых встроенных форматов атрибутов, его можно найти, например, здесь .
Марцин Орловский
1
Хороший учебник, с которого можно начать -> Создание составных видов на Android
Gayan Weerakutti

Ответы:

840

В Руководстве для разработчиков Android есть раздел под названием « Создание пользовательских компонентов» . К несчастью, обсуждение атрибутов XML охватывает только объявление элемента управления в файле макета, а не обработку значений внутри инициализации класса. Шаги следующие:

1. Объявите атрибуты в values\attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyCustomView">
        <attr name="android:text"/>
        <attr name="android:textColor"/>            
        <attr name="extraInformation" format="string" />
    </declare-styleable>
</resources>

Обратите внимание на использование неквалифицированного имени в declare-styleableтеге. Нестандартные атрибуты Android, такие какextraInformation должны иметь объявленный тип. Теги, объявленные в суперклассе, будут доступны в подклассах без необходимости повторного объявления.

2. Создайте конструкторы

Поскольку есть два конструктора, которые используют AttributeSetдля инициализации, удобно создать отдельный метод инициализации для вызова конструкторов.

private void init(AttributeSet attrs) { 
    TypedArray a=getContext().obtainStyledAttributes(
         attrs,
         R.styleable.MyCustomView);

    //Use a
    Log.i("test",a.getString(
         R.styleable.MyCustomView_android_text));
    Log.i("test",""+a.getColor(
         R.styleable.MyCustomView_android_textColor, Color.BLACK));
    Log.i("test",a.getString(
         R.styleable.MyCustomView_extraInformation));

    //Don't forget this
    a.recycle();
}

R.styleable.MyCustomViewэто автоматически сгенерированный int[]ресурс, где каждый элемент является идентификатором атрибута. Атрибуты генерируются для каждого свойства в XML путем добавления имени атрибута к имени элемента. Например, R.styleable.MyCustomView_android_textсодержит android_textатрибут для MyCustomView. Атрибуты могут быть затем получены с TypedArrayиспользованием различных getфункций. Если атрибут не определен в определенном в XML, nullвозвращается. За исключением, конечно, если возвращаемый тип является примитивом, то в этом случае возвращается второй аргумент.

Если вы не хотите получать все атрибуты, вы можете создать этот массив вручную. Идентификатор для стандартных атрибутов Android включен android.R.attr, а атрибуты для этого проекта - R.attr.

int attrsWanted[]=new int[]{android.R.attr.text, R.attr.textColor};

Обратите внимание, что вы не должны использовать что-либо в android.R.styleableсоответствии с этой темой это может измениться в будущем. Это все еще в документации, так как полезно просматривать все эти константы в одном месте.

3. Используйте его в файлах макета, таких как layout\main.xml

Включите объявление пространства имен xmlns:app="http://schemas.android.com/apk/res-auto"в элемент верхнего уровня xml. Пространства имен предоставляют метод, позволяющий избежать конфликтов, которые иногда возникают, когда разные схемы используют одинаковые имена элементов (дополнительную информацию см. В этой статье ). URL - это просто способ уникальной идентификации схем - на этом URL ничего не нужно размещать . Если кажется, что это ничего не делает, это потому, что вам на самом деле не нужно добавлять префикс пространства имен, если вам не нужно разрешить конфликт.

<com.mycompany.projectname.MyCustomView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:text="Test text"
    android:textColor="#FFFFFF"
    app:extraInformation="My extra information"
/> 

Ссылка на пользовательский вид, используя полное имя.

Образец Android LabelView

Если вы хотите полный пример, посмотрите на образец представления метки Android.

LabelView.java

 TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.LabelView);
 CharSequences=a.getString(R.styleable.LabelView_text);

attrs.xml

<declare-styleable name="LabelView">
    <attr name="text"format="string"/>
    <attr name="textColor"format="color"/>
    <attr name="textSize"format="dimension"/>
</declare-styleable>

custom_view_1.xml

<com.example.android.apis.view.LabelView
    android:background="@drawable/blue"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    app:text="Blue" app:textSize="20dp"/>

Это содержится в LinearLayoutс атрибутом пространства имен:xmlns:app="http://schemas.android.com/apk/res-auto"

связи

Casebash
источник
14
Я хотел бы добавить, что если вашему корневому элементу требуется ваше собственное пространство имен, вам придется добавить как стандартное пространство имен Android, так и свое собственное, иначе вы можете столкнуться с ошибками сборки.
Погоня
11
Этот ответ - самый понятный ресурс в Интернете по пользовательским параметрам XML, который мне удалось найти. Спасибо, Касебаш.
Артем Руссаковский
2
по какой-то причине визуальный редактор отказывается использовать записанное текстовое значение для android: text, однако устройство использует его просто отлично. Как так ?
разработчик Android
2
@androiddeveloper Редактор Eclipse отказывается использовать значения для всех атрибутов android :. Я хотел бы знать, если это особенность или ошибка
Дей
4
Какова цель xmlns: пространство имен приложений и res-auto?
Игорь Ганапольский
91

Отличная ссылка. Спасибо! Дополнение к нему:

Если у вас есть библиотечный проект, в котором объявлены настраиваемые атрибуты для настраиваемого представления, вы должны объявить пространство имен вашего проекта, а не библиотечное. Например:

Учитывая, что в библиотеке есть пакет «com.example.library.customview», а в рабочем проекте есть пакет «com.example.customview», то:

Не будет работать (показывает ошибку «ошибка: не найден идентификатор ресурса для атрибута« newAttr »в пакете« com.example.library.customview »»):

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.library.customview"
        android:id="@+id/myView"
        app:newAttr="value" />

Будет работать:

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.customview"
        android:id="@+id/myView"
        app:newAttr="value" />
Энди
источник
47
Это было исправлено в предварительном просмотре ADT 17. Чтобы использовать пространство имен приложения из библиотеки заявляет xmlns:app="http://schemas.android.com/apk/res-auto"Смотрите комментарий 57 в code.google.com/p/android/issues/detail?id=9656
мр
2
Включение вашего собственного пространства имен теперь возвращает ошибкуSuspicious namespace: Did you mean http://schemas.android.com/apk/res-auto
Бен Уилкинсон,
Пользовательское пространство имен заканчивается в res-auto, потому что мы используем Android Studio и Gradle. В противном случае (например, в некоторых версиях Eclipse) он обычно заканчивается на lib / [имя вашего пакета]
Universe
Пользовательское пространство имен заканчивается, res-autoпотому что мы используем Android Studio и Gradle. В противном случае (например, некоторые версии Eclipse) он обычно заканчивается на lib/[your package name]. то естьhttp://schemas.android.com/apk/lib/[your package name]
Вселенная
27

Дополнение к наиболее голосуемому ответу.

obtainStyledAttributes ()

Я хочу добавить несколько слов об использовании receiveStyledAttributes (), когда мы создаем пользовательское представление, используя атрибуты android: xxx prdefined. Особенно, когда мы используем TextAppearance.
Как было упомянуто в «2. Создание конструкторов», пользовательское представление получает AttributeSet при его создании. Основное использование мы видим в исходном коде TextView (API 16).

final Resources.Theme theme = context.getTheme();

// TextAppearance is inspected first, but let observe it later

TypedArray a = theme.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.TextView, defStyle, 0);

int n = a.getIndexCount();
for (int i = 0; i < n; i++) 
{
    int attr = a.getIndex(i);
    // huge switch with pattern value=a.getXXX(attr) <=> a.getXXX(a.getIndex(i))
}
a.recycle();

Что мы можем увидеть здесь?
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
Набор атрибутов обрабатывается по темам в соответствии с документацией. Значения атрибутов составляются шаг за шагом. Сначала атрибуты заполняются из темы, затем значения заменяются значениями из стиля, и, наконец, точные значения из XML для экземпляра специального представления заменяют другие.
Массив запрашиваемых атрибутов - com.android.internal.R.styleable.TextView
это обычный массив констант. Если мы запрашиваем стандартные атрибуты, мы можем построить этот массив вручную.

Что не упомянуто в документации - порядок следования элементов TypedArray.
Когда в attrs.xml объявляется пользовательское представление, генерируются специальные константы для индексов атрибутов. И мы можем извлечь значения таким образом: a.getString(R.styleable.MyCustomView_android_text). Но для руководстваint[] нет постоянных. Я полагаю, что getXXXValue (arrayIndex) будет работать нормально.

И другой вопрос: «Как мы можем заменить внутренние константы и запросить стандартные атрибуты?» Мы можем использовать значения android.R.attr. *.

Поэтому, если мы хотим использовать стандартный атрибут TextAppearance в пользовательском представлении и читать его значения в конструкторе, мы можем изменить код из TextView следующим образом:

ColorStateList textColorApp = null;
int textSize = 15;
int typefaceIndex = -1;
int styleIndex = -1;

Resources.Theme theme = context.getTheme();

TypedArray a = theme.obtainStyledAttributes(attrs, R.styleable.CustomLabel, defStyle, 0);
TypedArray appearance = null;
int apResourceId = a.getResourceId(R.styleable.CustomLabel_android_textAppearance, -1);
a.recycle();
if (apResourceId != -1)
{
    appearance = 
        theme.obtainStyledAttributes(apResourceId, new int[] { android.R.attr.textColor, android.R.attr.textSize, 
            android.R.attr.typeface, android.R.attr.textStyle });
}
if (appearance != null)
{
    textColorApp = appearance.getColorStateList(0);
    textSize = appearance.getDimensionPixelSize(1, textSize);
    typefaceIndex = appearance.getInt(2, -1);
    styleIndex = appearance.getInt(3, -1);

    appearance.recycle();
}

Где CustomLabel определяется:

<declare-styleable name="CustomLabel">
    <!-- Label text. -->
    <attr name="android:text" />
    <!-- Label text color. -->
    <attr name="android:textColor" />
    <!-- Combined text appearance properties. -->
    <attr name="android:textAppearance" />
</declare-styleable>

Возможно, я как-то ошибаюсь, но документация Android для receiveStyledAttributes () очень плохая.

Расширение стандартного компонента пользовательского интерфейса

В то же время мы можем просто расширить стандартный компонент пользовательского интерфейса, используя все его объявленные атрибуты. Этот подход не очень хорош, потому что TextView, например, объявляет много свойств. И будет невозможно реализовать полную функциональность в переопределенных onMeasure () и onDraw ().

Но мы можем пожертвовать теоретическим широким использованием пользовательского компонента. Скажите «Я точно знаю, какие функции я буду использовать», и не делитесь ни с кем кодом.

Тогда мы можем реализовать конструктор CustomComponent(Context, AttributeSet, defStyle). После вызова super(...)все атрибуты будут проанализированы и доступны через методы получения.

yuriy.weiss
источник
ли android: предопределенные атрибуты xxx работают в дизайнере графического интерфейса Eclipse?
Диджей
Такие атрибуты распознаются плагином Eclipse ADT в редакторе свойств. Я могу видеть значения по умолчанию из моего стиля, если некоторые значения не определены. И не забудьте добавить аннотацию @RemoteView в ваш класс.
yuriy.weiss
Не могу заставить это работать. Eclipse продолжает загружать пустые значения для getText и выбрасывает android.content.res.Resources $ NotFoundException для getResourceId, хотя приложение хорошо работает на устройстве.
Диджей
Извините, я не могу вам помочь. Я создал только демонстрационный проект для тестирования возможностей, и не встречал таких ошибок.
yuriy.weiss
Это намного лучше, чем сопоставление пользовательских атрибутов пользовательского представления со встроенными атрибутами встроенных представлений, содержащихся в нем.
Самис
13

Похоже, что Google обновил свою страницу для разработчиков и добавил туда различные тренинги.

Один из них посвящен созданию пользовательских видов и может быть найден здесь

mitch000001
источник
5

Большое спасибо за первый ответ.

Что касается меня, у меня была только одна проблема с этим. Раздувая мой взгляд, у меня была ошибка: java.lang.NoSuchMethodException: MyView (Context, Attributes)

Я решил это, создав новый конструктор:

public MyView(Context context, AttributeSet attrs) {
     super(context, attrs);
     // some code
}

Надеюсь, это поможет!

user2346922
источник
0

Вы можете включить любой файл макета в другой файл макета, как

             <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="30dp" >

                <include
                    android:id="@+id/frnd_img_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_imagefile"/>

                <include
                    android:id="@+id/frnd_video_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_video_lay" />

                <ImageView
                    android:id="@+id/downloadbtn"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_centerInParent="true"
                    android:src="@drawable/plus"/>
            </RelativeLayout>

здесь файлы макета в теге include - это другие файлы макета .xml в той же папке res.

Акшай Паливал
источник
Я пробовал это, проблема, с которой я столкнулся, состоит в том, что включенный макет не может быть «адаптирован», не может создавать универсальные шаблоны. Например, когда я включаю кнопку аналогичным образом, если я пытаюсь установить текст в xml, он работает.
cfl