Описание состояний интерфейса в XML вместо кода для Android

в 10:08, , рубрики: android, java, mobile development, visual states, XAML, XML, Разработка под android, со своим уставом в чужой моностырь

Перейдя в Java и Android из мира C# .NET так и хочется притащить в чужой монастырь свой устав. В данном случае речь пойдет о декларативных состояниях интерфейса (Visual States). Начнем сразу с простой проблемки: у нас есть вьюшка, на которой отображается профиль какого-то пользователя, но, в зависимости от роли текущего пользователя, эта вьюшка может чуть-чуть отличаться. Допустим, у нас есть три роли: guest, member и moderator, и состояния этой вьюшки будут выглядеть следующим образом:

Описание состояний интерфейса в XML вместо кода для Android - 1

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

В качестве упреждающего удара комментариям:

Описание состояний интерфейса в XML вместо кода для Android - 2

Повторюсь, это то, чем в мире XAML очень часто пользуются (в основном благодаря хорошей поддержке редактором — Blend), возможно покажется интересной идеей и Android разработчикам.
Ссылка на репозиторий на GitHub с рабочим кодом концепта: тыц.

Автор: Nagg

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js