Рано или поздно любого начинающего андроид-разработчика перестает удовлетворять стандартный набор элементов управления. При этом имеется в виду как внешний вид, так и функциональность. И если с внешним видом все более или менее понятно, все достаточно легко кастомизируется, то функционала часто не хватает.
Когда (уже достаточно давно) в одном из своих проектов я столкнулся с необходимостью выбора в настройках одного из трех параметров, решение было очевидно — RadioButton. Но по ряду причин, типа экономии места на экране и некоторых других, возникло желание использовать нечто вроде ToggleButton. Поскольку стандартный Toggle имеет лишь два состояния, был использован костыль в виде программной обработки некоей циклично меняющейся переменной, в зависимости от которой менялись свойства стандартного элемента вроде обычной Button или ImageButton – уже даже не помню. Способ вполне работоспособный, однако не без греха. Первый и самый главный – нарушается Генеральная линия партии, призывающая к раздельному хранению ресурсов и программного кода. Ну и при большом количестве подобных элементов управления код теряет всю свою изящность и привлекательность. Инкапсуляция, опять же, жутко страдает. Посему было решено создать кастомный элемент.
Представленный пример не представляет из себя ничего сложного для опытного разработчика, однако я в свое время был бы весьма рад найти подобное в одном месте, а не собирать по кусочкам. Давайте попытаемся создать кастомный ToggleButton, имеющий произвольное количество (в данном случае четыре) циклически переключающихся состояний. В зависимости от состояния в приведенном примере меняется надпись на кнопке и значок состояния слева (DrawableLeft у кнопки). Однако все настолько просто и прозрачно, что легко применить изменение состояния к любым свойствам контрола.
Первое, что нам необходимо – описать нужные нам свойства в файле res/values/attrs.xml. Как мы уже решили, это будут четыре строки и четыре изображения для различных состояний элемента управления:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomToggle">
<attr name="customState" format="integer" />
<attr name="customText_0" format="string" />
<attr name="customText_1" format="string" />
<attr name="customText_2" format="string" />
<attr name="customText_3" format="string" />
<attr name="customImage_0" format="reference" />
<attr name="customImage_1" format="reference" />
<attr name="customImage_2" format="reference" />
<attr name="customImage_3" format="reference" />
</declare-styleable>
</resources>
Теперь можем перейти непосредственно к коду нового класса. Отнаследуем его от обычной Button.
public class CustomToggle extends Button implements OnClickListener {
private static final int STATE_COUNT = 4;
private int state;
private String[] txt = new String[3];
private Drawable[] img = new Drawable[3];
public CustomToggle(Context context) {
this(context, null);
}
public CustomToggle(Context context, AttributeSet attrs) {
this(context, attrs, 0);
handleAttributes(context, attrs);
}
public CustomToggle(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOnClickListener(this);
}
private void handleAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomToggle);
final int count = a.getIndexCount();
// Перебираем все имеющиеся атрибуты
// И присваиваем соответствующим переменным
for (int i = 0; i < count; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.CustomToggle_customText_0:
txt[0] = a.getString(attr);
break;
case R.styleable.CustomToggle_customText_1:
txt[1] = a.getString(attr);
break;
case R.styleable.CustomToggle_customText_2:
txt[2] = a.getString(attr);
break;
case R.styleable.CustomToggle_customText_3:
txt[3] = a.getString(attr);
break;
case R.styleable.CustomToggle_customImage_0:
img[0] = a.getDrawable(attr);
break;
case R.styleable.CustomToggle_customImage_1:
img[1] = a.getDrawable(attr);
break;
case R.styleable.CustomToggle_customImage_2:
img[2] = a.getDrawable(attr);
break;
case R.styleable.CustomToggle_customImage_3:
img[3] = a.getDrawable(attr);
break;
}
}
a.recycle();
setValues();
}
// Применяем полученные свойства к контролу
private void setValues() {
setText(txt[state]);
setCompoundDrawablesWithIntrinsicBounds(img[state], null, null, null);
}
public void setState(int i) {
this.state = i;
setValues();
}
private void changeState() {
if (state < STATE_COUNT) state++;
else state = 0;
setValues();
}
public int getState() {
return state;
}
public String getCurrentText() {
return txt[state];
}
@Override
public void onClick(View v) {
changeState();
}
}
Я опустил импорты для экономии, если что – Эклипс подскажет.
Теперь, когда новый класс создан, мы можем добавить его на разметку и канонично указать свойства в xml-файле, не забыв добавить свое пространство имен:
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:custom_toggle="http://schemas.android.com/apk/res/com.example"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.CustomToggle
android:id="@+id/custom_toggle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom_toggle:customImage_0="@drawable/img_0"
custom_toggle:customImage_1="@drawable/img_1"
custom_toggle:customImage_2="@drawable/img_2"
custom_toggle:customImage_3="@drawable/img_3"
custom_toggle:customText_0="@string/toggle_0"
custom_toggle:customText_1="@string/toggle_1"
custom_toggle:customText_2="@string/toggle_2"
custom_toggle:customText_3="@string/toggle_3"
/>
</RelativeLayout>
Как говорится, profit.
Мы получили скелет, с которым дальше можем извращаться как угодно. Как варианты: вместо константы STATE_COUNT добавить соответствующие атрибут и метод типа setStateCount(), что позволит программно динамически ограничивать количество доступных состояний. Полный простор для фантазии.
На этом собственно все. Спасибо за внимание.
Автор: OJV