Суть проблемы

Повторное использование кода – это хорошо. Если оно не сопряжено с копи-пастингом – это еще лучше. Поэтому в данной серии статей мы рассмотрим создание собственного класса-наследника View, с возможностью задания его свойств через xml-атрибуты в layout-файле контейнера. Это позволит использовать его в проекте повторно, не создавая тонны xml-копи-пасты. В одной из следующий статей я расскажу как создать собственную библиотеку контролов, которую можно будет использовать в нескольких проектах.

Цель и алгоритм еще достижения

Нашей конечной целью будет так называемый composite control — объединение нескольких контролов в один, при этом мы создадим специфичные только для него xml-атрибуты, используя которые деволопер сможет задавать некоторые начальные параметры контрола в layout-файле того контейнера, куда вы этот контрол “положите”. Примером такого контрола является CheckedTextView – дитя священного союза ToggleButton и TextView.

Мы же создадим класс IdevCustomView, содержаций изображение и подпись к нему. Для этого конрола мы создадим xml-атрибуты, для задания текста подписи, цвета фона и картинки.



Для этого нам нужно выполнить 4 шага:

1. Описать разметку контрола в xml (файл res/layout/titled_image_view.xml)

2. Описать в xml его дополнительные атрибуты (файл res/values/attrs.xml)

3. Описать процедуру извлечения атрибутов, конструкторы и дополнительную функциональность на Java ( файл by.idev.android.customview.TitledImageView.java)

4. Использовать его в приложении (файл by.idev.android.cuctomview.IdevCustomViewActivity, файл res/layout/main.xml)

Шаг 1. Layout. Или View терминами XML.

Как было сказано выше, начинка контрола это: изображение, текст и фон. Для нас это ImageView, TextView и RelativeLayout, в который мы “запихнем” первые два. Файл res/layout/titled_image_view.xml будет иметь вид:

<?xml version="1.0" encoding="utf-8"?>

<merge

xmlns:android="http://schemas.android.com/apk/res/android" >

<relativeLayout

android:id="@+id/layout"

android:layout_height="wrap_content"

android:layout_width="fill_parent"

android:minHeight="60dp"

android:layout_margin="5dp"

android:background="#FFFFFFFF"></p>
<p> <imageView android:id="@+id/image"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_margin="5dp"/></p>
<p> <textView android:id="@+id/title"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:layout_alignParentRight="true"

android:layout_alignTop="@id/image"

android:layout_toRightOf="@id/image"

android:layout_margin="5dp"

android:textColor="#FF000000"/>

</relativeLayout>

</merge>



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

Но на самом деле любому xml-файлу нужен корневой элемент, роль которого и играет . При разметке же он просто игнорируется.

Шаг 2. Атрибуты настоящего контрола

Все мы знаем, как (удобно) пользоваться стандартными атрибутами типа android:src у ImageView. А как создать новые атрибуты? Для начала нужно описать в ресурсе какие атрибуты и кому мы ходим “подарить”. Сделаем это в файле res/values/attrs.xml:

<?xml version="1.0" encoding="utf-8"?>

<resources>

<declare-styleable name="TitledImageView">

<attr name="title" format="string"/>

<attr name="backColor" format="color"/>

<attr name="image" format="reference"/>

</declare-styleable>

</resources>



Разберем что тут к чему: атрибут “name” элемента указывает на то, какой виджет имеет доступ к описанным ниже атрибутам. Смысл элемента понятен из контекста, атрибут format описывает каков тип данных для данного атрибута и может принимать следующие значения:

  • boolean
  • color
  • dimension
  • float
  • fraction
  • integer
  • reference (то есть ссылки на ресурсы, как например на Drawable, который мы используем в этом примере ниже)
  • string

Шаг 3. От разметки к логике. Описание класса TitledImageView

Первым шагом будет объявление полей для нашего изображения, текста и поле типа RelativeLayout, которое мы используем для задания фонового цвета. Тут же необходимо создать 3 конструктора, 2 из которых в конечном итоге вызывает 3ий:


public class TitledImageView extends RelativeLayout {
private ImageView image;
private TextView title;
private RelativeLayout layout;
public TitledImageView(Context context) {
this(context,null);
}
public TitledImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TitledImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
((Activity)getContext())
.getLayoutInflater()
.inflate(R.layout.titled_image_view, this, true);
setUpViews();
setAttrs(attrs);
}
}

Основной интерес тут представляют метод setUpViews() – выполняет стандартные действия по нахождению контролов по id и создании ссылок на них из TitledImageView. И метод setUpAttrs(AttributeSet attrs) , который работает с атрибутами:


private void setUpViews() {
image = (ImageView)findViewById(R.id.image);
title = (TextView)findViewById(R.id.title);
layout = (RelativeLayout)findViewById(R.id.layout);
}
private void setAttrs(AttributeSet attrs) {
if (attrs != null) {
TypedArray a = getContext()
.obtainStyledAttributes(attrs, R.styleable.TitledImageView, 0,0);
setColor(a.getColor(R.styleable.TitledImageView_backColor,
0xFFFFFFFF));
setTitle(a.getString(R.styleable.TitledImageView_title));
setImage(a.getDrawable(R.styleable.TitledImageView_image));
a.recycle();
}
}
}

Заметьте, как формируются ссылки вида R.styleable.<имя класса>_<имя атрибута>. Вызывайте методы a.getString(…), a.getColor(…) согласно типам атрибутов, описанным в attrs.xml.

Стоит отметить, что вызов метода recycle()  желателен, так как помогает системе экономить ресурсы, позволяя другим контролам использовать тот же TypedArray вместо создания нового.

Для полноты кода покажем как (очень просто) работают сеттеры:


public void setImage(Drawable drawable) {
image.setImageDrawable(drawable);
}
public void setTitle(String text) {
title.setText(text);
}
public void setColor(int color) {
layout.setBackgroundColor(color);
}
}

Шаг 4. Радость контроло(ло)-творчества

Пришла пора взглянуть, ради чего мы это все дали. Описание IdevCustomViewActivity лаконично:


public class IdevCustomViewActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}

Гораздо интереснее для нас main.xml:

<?xml version="1.0" encoding="utf-8"?></p>
<linearLayout

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

xmlns:idev="http://schemas.android.com/apk/res/by.idev.android.customview"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent">
<p> <by.idev.android.customview.TitledImageView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

idev:title="Met, Custom Composite Control, sir!"

idev:image="@drawable/logo"

idev:backColor="#FF99FFFF"/></p>
<p> <by.idev.android.customview.TitledImageView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

idev:title="This is iDev!!!"

idev:image="@drawable/logo_red"

idev:backColor="#FF00DD00"/></p>
</linearLayout>


Созданные нами атрибуты будут доступны из указанного пространства имен “idev”, притом учтите, что оно должно оканчиваться названием пакета верхнего уровня вашего приложения, даже если ваш контрол лежит в каком-то подпакете.  Результат получится как на скриншоте в начале статьи.

Вместо заключения

На этом нашу работу нельзя считать законченной. Нашему контролу нужно еще научится сохранять состояние при помощи методов onSaveInstanceState() и onRestoreInstanceState(). Но об этом позже.

Happy coding!

PS: Исходный код, как всегда, можно скачать.

Похожие статьи

  • Вышла новая версия Android NDK r8e.

  • Вызов Java-методов из C/C++ кода при помощи JNI на Android.

  • Учимся возбуждать…. Java-исключений из нативного кода на C/C++ через JNI в Android.

  • SQL-скрипты, SimpleCursorAdapter и ViewBinder — полезные приемы работы с Sqlite в Android.

  • Java Gym: абстрактный класс и интерфейс. Или о чем могут спросить на собеседовании.