Библиотека 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
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
