Библиотека Butter Knife позволит вам избежать большого количества однотипного кода, связанного с работой с View элементами в ваших Activity, фрагментах, холдерах и т.д. В этом материале я подробно рассмотрю возможности библиотеки, и на примерах покажу, как вы можете ее использовать.
Зачем нужен Butter Knife
Рассмотрим простой пример. Бывает необходимость показывать/скрывать сразу несколько View в Activity. В стандартном варианте реализации это может выглядеть так:
public class MainActivity extends AppCompatActivity { private List<View> viewList = new LinkedList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); viewList.add(findViewById(R.id.title)); viewList.add(findViewById(R.id.value)); viewList.add(findViewById(R.id.loading)); for (View view : viewList) { view.setVisibility(View.INVISIBLE); } } }
Объявление списка, поиск элементов по findViewById и добавление в список, перебор списка и установка видимости.
А вот реализация с использованием Butter Knife (и Retrolambda)
public class MainActivity extends AppCompatActivity { @BindViews({R.id.title, R.id.value, R.id.loading}) List<View> viewList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); ButterKnife.apply(viewList, (view, value, index) -> view.setVisibility(value), View.INVISIBLE); } }
Объявление списка, поиск элементов по findViewById и добавление в список - все это выполнила аннотация @BindViews, избавив нас от написания нескольких строк однотипного кода.
А комбинация Butter Knife + Retrolambda позволила одой строкой поменять видимость всем View в списке.
Кроме биндинга View элементов, библиотека умеет вешать на View различные обработчики, выполнять операции над коллекциями View и биндить значения из values. Давайте на примерах рассмотрим все эти возможности.
Подключение к проекту
Для подключения библиотеки к проекту необходимо добавить строки в dependencies в gradle файл модуля
compile 'com.jakewharton:butterknife:8.6.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
View Binding
BindView
Аннотация BindView избавит вас от необходимости писать строки с findViewById.
Рассмотрим пример. В layout файле описан TextView:
<TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" />
Используем BindView, чтобы получить доступ к этому TextView в Activity:
public class MainActivity extends AppCompatActivity { @BindView(R.id.title) TextView textViewTitle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); textViewTitle.setText("bla bla"); } }
При работе с BindView от нас требуется указать id TextView элемента и переменную, в которую необходимо этот TextView поместить.
А в onCreate необходимо вызвать метод ButterKnife.bind и передать туда Activity. Этот метод найдет все Butter Knife аннотации в Activity и выполнит необходимые действия. В нашем случае он увидит аннотацию BindView, возьмет R.id.title, найдет TextView и поместит в textViewTitle.
После вызова ButterKnife.bind мы можем работать с textViewTitle.
Вместо типа переменной TextView, вы можете указать View. BindView автоматически выполнит для вас преобразование типа.
public class MainActivity extends AppCompatActivity { @BindView(R.id.title) View viewTitle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); viewTitle.setVisibility(View.INVISIBLE); } }
Использование BindView для нескольких View элементов выглядит так:
@BindView(R.id.title) TextView textViewTitle; @BindView(R.id.value) EditText editTextValue; @BindView(R.id.loading) ProgressBar progressBarLoading;
После вызова ButterKnife.bind все эти переменные будут заполнены и готовы к работе.
Если вы используете Butter Knife в фрагментах, то необходимо использовать Unbinder из-за особенностей Fragment Lifecycle.
public class MyFragment extends Fragment { private Unbinder unbinder; @BindView(R.id.title) TextView textViewTitle; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_my, container, false); unbinder = ButterKnife.bind(this, view); textViewTitle.setText("bla bla"); return view; } @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); } }
В onCreateView вызываем bind и сохраняем полученный Unbinder объект в переменную unbinder. А в onDestroyView вызываем unbinder.unbind.
Также, вы можете использовать Butter Knife в холдерах при работе со списками:
class Holder { @BindView(R.id.title) TextView textViewTitle; public Holder(View view) { ButterKnife.bind(this, view); } }
Nullable
По умолчанию, если BindView не найдет View с указанным id, то он вызовет Exception. Чтобы избежать этого, используйте аннотацию Nullable.
@Nullable @BindView(R.id.title) TextView title;
BindViews
BindViews соберет указанные View в список или в массив.
Примеры:
Собираем три различных View (TextView, EditText, ProgressBar) в список с общим типом - View
@BindViews({R.id.title, R.id.value, R.id.loading}) List<View> viewList;
Или в массив
@BindViews({R.id.title, R.id.value, R.id.loading}) View[] viewList;
Собираем два View (TextView, EditText) в список с общим типом - TextView.
@BindViews({R.id.title, R.id.value}) List<TextView> textViewList;
Обработчики
ButterKnife позволяет удобно назначать для View наиболее распространенные обработчики
OnClick
Реализация обработчика OnClickListener.
Пометив метод аннотацией OnClick мы указываем, что этот метод будет являться обработчиком клика на View
@OnClick(R.id.save) void onSaveClick() { // ... }
Теперь, при клике на View с id = R.id.save будет вызван метод onSaveClick. И вам уже не надо писать ни findViewById, ни setOnClickListener. BindView в этом случае вам также не нужен.
Если необходимо передавать "кликнутый" View в этот метод, то просто добавьте View в параметры метода:
@OnClick(R.id.save) void onSaveClick(View view) { // ... }
Если вам надо получить конкретный тип View, например, Button, то укажите его:
@OnClick(R.id.save) void onSaveClick(Button view) { // ... }
Butter Knife позволяет назначить один обработчик нескольким View.
@OnClick({R.id.button1, R.id.button2, R.id.button3}) void onSaveClick(View view) { switch (view.getId()) { case R.id.button1: // ... break; case R.id.button2: // ... break; case R.id.button3: // ... break; } }
OnCheckedChanged
Реализация обработчика OnCheckedChangeListener
Можно использовать, например, для чекбоксов, чтобы получать его checked-состояние
@OnCheckedChanged(R.id.agree) void onAgreeChecked(boolean checked) { // ... }
При изменении значений чекбокса с id = R.id.agree, будет вызываться метод onAgreeChecked.
Параметры метода могут варьироваться. Например, можно получать непосредственно View, которое было “чекнуто”
@OnCheckedChanged(R.id.agree) void onAgreeChecked(CompoundButton view) { // ... }
А можно получать и View и checked-значение
@OnCheckedChanged(R.id.agree) void onAgreeChecked(CompoundButton view, boolean checked) { // ... }
OnTextChanged
Реализация TextWatcher
Интерфейс TextWatcher содержит следующие методы:
TextWatcher textWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { } };
По умолчанию, Butter Knife создает обработчик, соответствующий методу onTextChanged
@OnTextChanged(R.id.value) public void onTextChanged(CharSequence s, int start, int before, int count) { // ... }
Если вам нужны два других метода, это делается так:
@OnTextChanged(value = R.id.value, callback = BEFORE_TEXT_CHANGED) public void beforeTextChanged(CharSequence s, int start, int count, int after) { // ... } @OnTextChanged(value = R.id.value, callback = AFTER_TEXT_CHANGED) public void afterTextChanged(Editable s) { // ... }
В аннотации OnTextChanged вам необходимо с помощью параметра callback указать, какой именно метод вам необходим. А метод onTextChanged идет по умолчанию, поэтому для него можно не указывать callback.
Этот же прием с callback используется для других обработчиков, которые содержат два и более методов. Каждый из них содержит документацию, в которой все это описано.
Посмотреть документацию в Android Studio можно поставив курсор на нужную вам аннотацию и нажав CTRL+Q.
Прочие обработчики
Перечислю, какие еще аннотации вы можете использовать и каким обработчикам они соответствуют.
OnEditorAction - OnEditorActionListener
OnFocusChange - OnFocusChangeListener
OnItemClick - OnItemClickListener
OnItemLongClick - OnItemLongClickListener
OnItemSelected - OnItemSelectedListener
OnLongClick - OnLongClickListener
OnPageChange - OnPageChangeListener
OnTouch - OnTouchListener
Optional
По умолчанию, если аннотация обработчика не найдет View с указанным id, то она вызовет Exception. Чтобы избежать этого, используйте аннотацию Optional.
@Optional @OnClick(R.id.button1) void onClick() { // ... }
Операции
Butter Knife может выполнять определенные операции над одним View или целой группой View. Для этого используется метод ButterKnife.apply. Его можно вызывать для одного View, списка View и массива View.
Операции бывают трех типов
Action
Если применить Action к группе View, он будет выдавать вам View по одному и указывать его индекс, а вы уже решаете, что с этим View делать.
Например, есть задача, собирать текст со всех EditText в один StringBuilder. Это можно реализовать так:
ButterKnife.Action getTexts = new ButterKnife.Action<EditText>() { @Override public void apply(@NonNull EditText editText, int index) { stringBuilder.append(index).append(". ").append(editText.getText().toString()).append("\r\n") ; } }; ButterKnife.apply(viewList, getTexts);
При создании Action, указываем, что тип View будет EditText. Внутри Action, в методе apply, мы будем получать по одному все наши EditText и их позиции в списке. Для каждого из них мы добавляем в StringBuilder индекс и текст.
Остается вызывать метод ButterKnife.apply и передать ему список EditText-ов и Action.
В итоге, Action будет применен ко всем EditText из списка viewList. И StringBuilder будет содержать пронумерованные тексты из всех EditText.
Property
С помощью Property вы можете быстро установить какое-либо свойство целой группе View.
Например, прозрачность.
ButterKnife.apply(viewList, View.ALPHA, 0.5f);
Setter
Похож на Action, но позволяет дополнительно передавать значение внутрь метода операции.
Например, мы можем создать Setter, который будет устанавливать видимость View.
ButterKnife.Setter visibleSetter = new ButterKnife.Setter<View, Integer>() { @Override public void set(@NonNull View view, Integer value, int index) { view.setVisibility(value); } };
При создании, мы указываем тип View и тип значения которое будем передавать - Integer. В методе set мы получим View и его индекс (аналогично Action), а также значение Integer, которое будем использовать в методе setVisibility.
Использование этого сеттера выглядит так
ButterKnife.apply(viewList, visibleSetter, View.VISIBLE);
В метод apply передаем список View, сеттер и значeние View.VISIBLE. Сеттер прогонит через себя весь список View и для каждого выполнит метод setVisibility(View.VISIBLE).
А если хотим спрятать все View, то используем этот же сеттер, но с значением View.INVISIBLE
ButterKnife.apply(viewList, visibleSetter, View.INVISIBLE);
Value Binding
Кроме View вы можете биндить различные значения из папки values.
Например dimensions:
@BindDimen(R.dimen.activity_horizontal_margin) float marginFloat;
@BindDimen(R.dimen.activity_horizontal_margin) int marginInt;
Можно получить как float, так и int значение.
Существуют следующие аннотации для биндинга значений:
BindArray
BindBitmap
BindBool
BindColor
BindDimen
BindDrawable
BindFloat
BindInt
BindString
Я описал практически все возможности текущей версии Butter Knife. Если чего пропустил, или с выходом новой версии появилось что-то интересное, пишите на форуме - я дополню материал.
Plugin
Для Android Studio есть полезный плагин
https://github.com/avast/android-butterknife-zelezny
Он за вас сможет создать BindView и OnClick.
При использовании, убедитесь, что курсор стоит на layout файле.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня