Pull to refresh

Андроид — кастомные View или ToggleButton 4x4

Reading time 5 min
Views 16K
Рано или поздно любого начинающего андроид-разработчика перестает удовлетворять стандартный набор элементов управления. При этом имеется в виду как внешний вид, так и функциональность. И если с внешним видом все более или менее понятно, все достаточно легко кастомизируется, то функционала часто не хватает.

Когда (уже достаточно давно) в одном из своих проектов я столкнулся с необходимостью выбора в настройках одного из трех параметров, решение было очевидно — 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];

	private OnViewChangeListener	listener;
	
	public interface OnViewChangeListener {
		public void onChangeState(int i);
	}
	
	public void setOnViewChangeListener(OnViewChangeListener l) {
		this.listener = l;
	}

	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();
                listener.onChangeState(state);
	}

	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(), что позволит программно динамически ограничивать количество доступных состояний. Полный простор для фантазии.

На этом собственно все. Спасибо за внимание.

P.S: Добавил в код интерфейс OnViewChangeListener.
Tags:
Hubs:
+11
Comments 6
Comments Comments 6

Articles