Рано или поздно любого начинающего андроид-разработчика перестает удовлетворять стандартный набор элементов управления. При этом имеется в виду как внешний вид, так и функциональность. И если с внешним видом все более или менее понятно, все достаточно легко кастомизируется, то функционала часто не хватает.
Когда (уже достаточно давно) в одном из своих проектов я столкнулся с необходимостью выбора в настройках одного из трех параметров, решение было очевидно — 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(), что позволит программно динамически ограничивать количество доступных состояний. Полный простор для фантазии.
На этом собственно все. Спасибо за внимание.
ссылка на оригинал статьи http://habrahabr.ru/post/254527/
Добавить комментарий