從概念設計到安卓實現, 第一部分

gz8113 7年前發布 | 6K 次閱讀 安卓開發 Android開發 移動開發

多虧了 Dribbble 和  MaterialUp 這樣的設計平臺,我們這些開發者才有機會接觸到大量的概念設計資源。盡管如此,有時候有些細節幾乎是不可能實現的,部分用戶體驗并沒有被考慮。

鑒于此,我覺得建立一個這樣的項目會比較有意思:選擇一些 Dribbble 或者  MaterialUp 上的設計資源,在安卓上實現它們,然后撰寫一些列的文章來講解實現的細節以及我認為比較重要的安卓界面實現技巧。

概念設計

這是我為第一部分選擇的概念設計,簡單但是足以涵蓋一些有趣的話題了,比如 ConstraintLayout &  chains ,  DataBinding ,UI層次結構性能的重要性以及 scenes(場景) 。

  • 該概念設計的作者是 Johnyvino ,發布在  MaterialUp 上。

  • 其實現可以在 GitHub的 這個倉庫 上找到。

讓我們開始吧!

支持庫中的Bottom sheets

在演示圖中, bottom sheet 被用來引導用戶完成購買過程。在安卓中你可以找到幾個第三方庫來實現這種視圖,比如 Umano的 AndroidSlidingUpPanel library 。

其實最近 bottoms sheets被引入了 design support library ,因此我決定在這里使用它,學習該如何使用。

Bottom sheets有兩種使用方式,一種是Bottom sheets作為main view 的一部分(在 CoordinatorLayout 中的一個viewgroup上使用 BottomSheetBehavior ),另一種是模態對話框似的Bottom sheets,使用 BottomSheetDialogFragment 來實現。

對于我們的例子我選擇 BottomSheetDialogFragment ,因為這里的信息是以模態的形式顯示的。這種Bottom sheets實現的代碼和 DialogFragment 的用法類似。

HomeActivity.java

OrderDialogFragment.newInstance(
  fakeProducts.get(position))
    .show(getSupportFragmentManager(),null);

OrderDialogFragment.java

  @Override
public View onCreateView(LayoutInflater inflater,
    ViewGroup container,Bundle savedInstanceState) {

    super.onCreateView(inflater,
        container, savedInstanceState);

    binding = FragmentOrderFormBinding.inflate(
        inflater, container, false);

    return binding.getRoot();
}

ConstraintLayout

Google I/O '16上發布的這個強大的布局控件最近迎來了它的穩定版本(1.0.2)。

這個控件的最大好處在于,它可以讓你以最扁平的層次,構造復雜的,自適應的布局。

通常建議安卓的布局中要避免使用層次很深的視圖結構,因為這樣會降低性能,而且會增加UI繪制到屏幕的時間。而如果使用 ConstraintLayout ,這樣的要求自動就滿足了。

基本地, ConstraintLayout 的工作原理類似 RelativeLayout ,即定義視圖與屏幕之間的關系。但是 ConstraintLayout 除了性能更好之外,在Android Studio的圖形編輯器中的表現也真的很好。

除此之外還有更多有趣的機制比如view之間的chain(不同的view相互約束),或者guidelines。

chain的使用

假設我們有兩個view,A和B,分別被約束在屏幕的右邊沿和左邊沿。如果A設定在B的左邊,而B設定在A的右邊,這樣這組view就會被特殊處理,在約束布局中這樣的情況被稱之為chain。

使用Android Studio的布局編輯器的上下文菜單很容易就可以創建一個chain。默認創建的是spread chain,即視圖均勻分布。

Chain 類型

在這個例子中,我們只使用spread 和 packed chain,但是你可以根據自己的需要選擇,最近谷歌對 ConstraintLayout 的文檔增加了對 chain 的說明,沒有理由不用。

被選中view的過渡動畫

這看起來比較簡單,每當用戶點擊一個產品參數的時候,被選擇的view就過渡到左下角的標簽旁邊。

為此我們在被點擊的的view的parent中添加一個新的view。

OrderDialogFragment.java

private void transitionSelectedView(View v) {
    final View selectionView = createSelectionView(v);

    binding.mainContainer.addView(selectionView);

    startCloneAnimation(selectionView, getTargetView(v));
}

然后使用 TransitionManager 的 beginDelayedTransition 來做過渡動畫。這個方法將檢測布局是否有變化,如果有,使用傳入的transition來執行動畫。

OrderDialogFragment.java

private void startCloneAnimation(View clonedView, View targetView) {
    clonedView.post(() -> {
        TransitionManager.beginDelayedTransition(
            (ViewGroup) binding.getRoot(), selectedViewTransition);

        // 觸發transition
        clonedView.setLayoutParams(SelectedParamsFactory
            .endParams(clonedView, targetView));
    });
}

ViewSwitcher

ViewSwitcher ,Android SDK一個并不十分流行的控件,它可以用進入和退出動畫來切換兩個view。這和我們購買過程中在代表兩個步驟的布局之間切換是完全符合的。

fragment_order_form.xml

<ViewSwitcher
    android:id="@+id/switcher"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inAnimation="@anim/slide_in_right"
    android:outAnimation="@anim/slide_out_left"
    >

    <include
        android:id="@+id/layout_step1"
        layout="@layout/layout_form_order_step1"
        />

    <include
        android:id="@+id/layout_step2"
        layout="@layout/layout_form_order_step2"
        />
</ViewSwitcher>

OrderDialogFragment.java

private void showDeliveryForm() {
    binding.switcher.setDisplayedChild(1);
    initOrderStepTwoView(binding.layoutStep2);
}

Databinding(數據綁定)

這個設計的實現我使用了 Databinding ,對我來說在一個對象中包含所有的布局結構是可以接受的,但是Databading還有其它一些機制值得一提。

節省click listener的書寫次數

因為這些布局有大量的listener需要處理,我決定為用 Databinding 綁定一個listener對象來處理onClick。在xml中,為每個view的onClick屬性設置方法名,然后事件就會定向到listener對象的相應方法中。

layout_form_order_step1.xml

<data>
    <variable
        name="listener"
        type="com.saulmm.cui.OrderDialogFragment.Step1Listener"
        />
</data>

<CircleImageView
    android:id="@+id/img_color_blue"
    style="@style/Widget.Color"
    android:onClick="@{listener::onColorSelected}"
    />

<CircleImageView
    android:id="@+id/img_color_blue"
    style="@style/Widget.Color"
    android:onClick="@{listener::onColorSelected}"
    />

OrderDialogFragment.java

layoutStep1Binding.setListener(new Step1Listener() {
    @Override
    public void onSizeSelected(View v) {
        // ...
    }

    @Override
    public void onColorSelected(View v) {
        // ...
    }
})

使用Spannables + databinding + ConstraintLayout來減少view

讓我們花點時間來思考如何實現下面這部分設計

一種辦法是每個item設置一個垂直的LinearLayout,然后3個item被包含在一個水平的LinearLayout中,然后再用一個LinearLayout包裹兩個水平LinearLayout以及兩個文本標簽控件。

平時別這樣做

這樣看起來并不好,對吧?前面我們談到了扁平結構的重要性,所以正確的做法是使用 ConstrainLayout 作為容器。

每一個item都是LinearLayout的話開銷很大的,其實我們只不過是要顯示字體大小不同的文本而已,也許還要加點邊框啥的,我們可以做到每個item只有一個TextView控件。

Spannables

Spannables 可以讓文本以不同的大小顯示,而邊框我們可以用drawable解決。這樣一來一個item我們就節省了3個view,遠比第一種方法要好。

使用 Spannables 的問題在于time item跟 date item的大號字符個數是不同的。

使用 Databinding 以及它的 BindingAdapters 機制,我們可以創建一個屬性來設置大號字符的個數。

layout_form_order_step2.xml

<TextView
    android:id="@+id/txt_time1"
    style="@style/Widget.DateTime"
    app:spanOffset="@{3}"
    />

<TextView
    android:id="@+id/txt_date1"
    style="@style/Widget.DateTime"
    app:spanOffset="@{2}"/>

OrderDialogFragment.java

@BindingAdapter("app:spanOffset")
public static void setItemSpan(View v, int spanOffset) {
    final String itemText = ((TextView) v).getText().toString();
    final SpannableString sString = new SpannableString(itemText);

    sString.setSpan(new RelativeSizeSpan(1.75f), itemText.
        length() - spanOffset, itemText.length(),
        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

    ((TextView) v).setText(sString);
}

Scenes(場景)

也許實現下面這個概念的本質是如何實現book按鈕和確認視圖之間的過渡效果。

如果我們利用好障眼法的話,這也并不難。如果我們不把按鈕視作按鈕,而是視作一個容器,我們就會意識到可以使用scenes來做到這個動畫效果。

整個表單為第一個scene,確認視圖則為第二個scene,按鈕則是兩個界面的共享元素。共享元素就是兩個scene之間所共同擁有的一個view,framework將使用一個transition為這個view做恰當的動畫。

一旦我們知道邏輯就是改變兩個scenes,問題就簡單了。

OrderDialogFragment.java

private void changeToConfirmScene() {
    final LayoutOrderConfirmationBinding confBinding =
        prepareConfirmationBinding();

    final Scene scene = new Scene(binding.content,
        ((ViewGroup) confBinding.getRoot()));

    scene.setEnterAction(onEnterConfirmScene(confBinding));

    final Transition transition = TransitionInflater
        .from(getContext()).inflateTransition(
              R.transition.transition_confirmation_view);

    TransitionManager.go(scene, transition);
}

效果

參考

 

來自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0317/7692.html

 

 本文由用戶 gz8113 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!