Перейдя в Java и Android из мира C# .NET так и хочется притащить в чужой монастырь свой устав. В данном случае речь пойдет о декларативных состояниях интерфейса (Visual States). Начнем сразу с простой проблемки: у нас есть вьюшка, на которой отображается профиль какого-то пользователя, но, в зависимости от роли текущего пользователя, эта вьюшка может чуть-чуть отличаться. Допустим, у нас есть три роли: guest, member и moderator, и состояния этой вьюшки будут выглядеть следующим образом:
Эти состояния похожи, поэтому будет нелогично делать три разных лэйаута (представим, что реальная вьюшка намного сложнее, но между состояниями больше сходств, чем различий). Как вариант – описать один XML со всеми кнопками и потом кодом менять видимость нужных кнопок для нужных состояний. Вот здесь и приходят на помощь вижуал стейты, которые позволят нам описать эти самые состояния прямо в XML нашей вьюшки без кода. Фактически, код будет делать только goToState(stateName). Так это делают в мире C# + XAML, причем, в специальном редакторе (Blend) можно создавать эти состояния, визуально менять интерфейс (он запомнит изменения для текущего стейта), настраивать переходы между стейтами, добавлять анимации, создавать параллельные стейт машины. Я лишь попытался в стиле proof of concept перенести это в Android на уровне минимального функционала. Вот как будет выглядеть разметка и состояния для нашего примера:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:vs="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:gravity="center_horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginTop="20dp">
<TextView android:id="@+id/status"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="You entered as guest" android:textSize="16sp"
android:layout_marginBottom="5dp"/>
<ImageView android:id="@+id/photo"
android:layout_width="250dp" android:layout_height="250dp"
android:layout_gravity="center_horizontal" android:src="@drawable/avatar"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Vasya Pupkin" android:textSize="20sp" android:textStyle="bold"
android:layout_marginBottom="20dp"/>
<Button android:id="@+id/addToFriendsButton"
android:layout_width="250dp" android:visibility="gone"
android:layout_height="wrap_content"
android:text="Add to friends"/>
<Button android:id="@+id/banButton" android:visibility="gone"
android:layout_width="250dp" android:layout_height="wrap_content"
android:text="Ban"/>
<ImageButton android:id="@+id/facebookButton" android:visibility="visible"
android:layout_width="250dp" android:layout_height="60dp"
android:background="@null" android:src="@drawable/fb" android:scaleType="fitXY" />
<com.eb.vsm.VisualStateManager android:id="@+id/visualStateManager" style="@style/vsm"
vs:defaultStateName="Guest">
<!--GUEST state-->
<com.eb.vsm.VisualState style="@style/vsm" vs:stateName="Guest">
<com.eb.vsm.SetText style="@style/vsm"
vs:targetId="@id/status"
vs:text="You entered as guest"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/addToFriendsButton"
vs:visibility="gone"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/banButton"
vs:visibility="gone"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/facebookButton"
vs:visibility="visible"/>
<com.eb.vsm.SetSrc style="@style/vsm"
vs:targetId="@id/photo"
vs:src="@drawable/unknown_avatar"/>
</com.eb.vsm.VisualState>
<!--MEMBER state-->
<com.eb.vsm.VisualState style="@style/vsm" vs:stateName="Member">
<com.eb.vsm.SetText style="@style/vsm"
vs:targetId="@id/status"
vs:text="You entered as member"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/addToFriendsButton"
vs:visibility="visible"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/banButton"
vs:visibility="gone"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/facebookButton"
vs:visibility="gone"/>
<com.eb.vsm.SetSrc style="@style/vsm"
vs:targetId="@id/photo"
vs:src="@drawable/avatar"/>
</com.eb.vsm.VisualState>
<!--MODERATOR state-->
<com.eb.vsm.VisualState style="@style/vsm" vs:stateName="Moderator">
<com.eb.vsm.SetText style="@style/vsm"
vs:targetId="@id/status"
vs:text="You entered as moderator"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/addToFriendsButton"
vs:visibility="visible"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/banButton"
vs:visibility="visible"/>
<com.eb.vsm.SetVisibility style="@style/vsm"
vs:targetId="@id/facebookButton"
vs:visibility="gone"/>
<com.eb.vsm.SetSrc style="@style/vsm"
vs:targetId="@id/photo"
vs:src="@drawable/avatar"/>
</com.eb.vsm.VisualState>
</com.eb.vsm.VisualStateManager>
</LinearLayout>
Собственно, VisualStateManager – это фэйковый виджет (ViewGroup), который является контейнером именованных стейтов (VisualState), которые в свою очередь являются контейнерами сеттеров. Сеттер — абстрактный класс, с одним методом apply и свойством target. Я описал пяток стандартных сеттеров, но ни что не мешает добавить свои :-).
А когда мы переходим в какое-нибудь состояние – выполнятся все его сеттеры.
Таким образом, в коде чистота и только одинокий вызов stateManage.goToState(“Moderator”) вместо кучи лапши, управляющей свойствами виджетов (хотя по сути, лапша перебралась в XML).
public class MainActivity extends Activity {
private VisualStateManager visualStateManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
visualStateManager = (VisualStateManager)findViewById(R.id.visualStateManager);
//init view with "guest" state (it can be done via XML as well)
visualStateManager.goToState("Guest");
//go to "moderator" state on FB button click
((ImageButton)findViewById(R.id.facebookButton)).setOnClickListener((view) -> {
visualStateManager.goToState("Moderator");
});
}
}
В качестве упреждающего удара комментариям:
Повторюсь, это то, чем в мире XAML очень часто пользуются (в основном благодаря хорошей поддержке редактором — Blend), возможно покажется интересной идеей и Android разработчикам.
Ссылка на репозиторий на GitHub с рабочим кодом концепта: тыц.
Автор: Nagg