天天看點

Android之路 - 實作高斯模糊的菜單前言分析實作效果

前言

本章主要用原生的方式實作一個菜單頁面,主要用到的知識點為位移動畫,我們可以先看看效果。

Android之路 - 實作高斯模糊的菜單前言分析實作效果

高斯模糊的菜單效果圖.gif

分析

高斯模糊背景

我們的菜單背景是一個高斯模糊的背景,雖然看上去高大上,但是不要被吓到了,實作原理非常的簡單:截取目前螢幕轉換為bitmap,将bitmap進行高斯模糊,然後設定為菜單的背景。

當然,還有另外一種實作方式就是讓UI設計師切一張高斯模糊模糊的透明背景圖,看看UI設計師會不會打死你。

Android之路 - 實作高斯模糊的菜單前言分析實作效果

哒哒

菜單跳動

  • 這個效果看上去雖然是複雜,但是不要被吓到了。其實也就是先用一個RelativeLayout作為根布局,将裡面的每個menu逐個的布局,排列好。
  • 打開菜單的時候,使用translationY動畫逐個逐個的将menu從螢幕外移動到原來的位置,而從第0個menu開始,後面的每個menu根據下标延遲啟動動畫。
    • 第1個比第0個延遲開始動畫160毫秒*
    • 第2個比第1個延遲開始動畫260毫秒*
    • 第3個比第2個延遲開始動畫360毫秒*
  • 關閉菜單,從第0個進行translationY動畫将menu逐個移出到螢幕外。從下标遞增,不斷的延遲動畫的開始。
    • 第0個延遲開始動畫430毫秒,并且在動畫結束關閉彈出*
    • 第1個比第0個延遲開始動畫330毫秒*
    • 第2個比第1個延遲開始動畫230毫秒*
    • 第3個比第2個延遲開始動畫130毫秒*

底部圓形菜單

動畫

也就是這個東西

Android之路 - 實作高斯模糊的菜單前言分析實作效果

底部菜單

,菜單打開的時候使用rotation動畫從45度到180度旋轉,而關閉的時候則是使用rotation動畫從180度到45度旋轉。

如何實作突出

ViewGroup有個屬性是clipChildren,設定為false則代表目前ViewGroup對超出高度的子view不進行裁切

實作

底部Tab欄

首先是底部的tab欄實作,沒什麼好講的。根布局使用FrameLayout,然後又LineaLayout排列圖示。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:background="#00000000"
    android:clipChildren="false">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="48dip"
        android:layout_alignParentBottom="true"
        android:background="@color/colorPrimary"
        android:orientation="horizontal">

        <RelativeLayout
            android:id="@+id/rl_menu_home"
            style="@style/MainMenuLinearStyle">

            <ImageView
                android:id="@+id/iv_menu_home"
                style="@style/MainMenuImageStyle"
                android:src="@drawable/menu_home_select" />

            <TextView
                android:id="@+id/tv_menu_home"
                style="@style/MainMenuTextStyle"
                android:layout_below="@id/iv_menu_home"
                android:text="首頁" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/rl_menu_project"
            style="@style/MainMenuLinearStyle">

            <ImageView
                android:id="@+id/iv_menu_project"
                style="@style/MainMenuImageStyle"
                android:src="@drawable/menu_project_select" />

            <TextView
                android:id="@+id/tv_menu_project"
                style="@style/MainMenuTextStyle"
                android:layout_below="@id/iv_menu_project"
                android:text="項目" />
        </RelativeLayout>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab_all_menu"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_gravity="bottom"
            android:layout_marginBottom="-15dp"
            android:rotation="45"
            android:src="@drawable/ic_clear" />

        <RelativeLayout
            android:id="@+id/rl_menu_client"
            style="@style/MainMenuLinearStyle">

            <ImageView
                android:id="@+id/iv_menu_client"
                style="@style/MainMenuImageStyle"
                android:src="@drawable/menu_client_select" />

            <TextView
                android:id="@+id/tv_menu_client"
                style="@style/MainMenuTextStyle"
                android:layout_below="@id/iv_menu_client"
                android:text="招商" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/rl_menu_my"
            style="@style/MainMenuLinearStyle">

            <ImageView
                android:id="@+id/iv_menu_my"
                style="@style/MainMenuImageStyle"
                android:src="@drawable/menu_my_select" />

            <TextView
                android:id="@+id/tv_main_home"
                style="@style/MainMenuTextStyle"
                android:layout_below="@id/iv_menu_my"
                android:text="我的" />
        </RelativeLayout>
    </LinearLayout>
</FrameLayout>
           

看看效果吧:

Android之路 - 實作高斯模糊的菜單前言分析實作效果

底部tab欄

布局整個菜單

這一步也沒什麼大的難度,就是在布局每個menu的時候确定位置是比較繁瑣的,選擇RelativeLayout的原因是因為層級,所有的menu都處于同一個層級中,進行位移動畫的時候才不會被裁切。

另外需要的是在底部再增加一個和TAB欄一樣的圓形按鈕,用于占位和關閉菜單。

來看布局吧:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/rl_more_menu_root"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#90FFFFFF"
                android:clickable="true"
                android:focusable="true"
                android:gravity="center_horizontal"
                android:orientation="vertical">
    <!--背景占位-->
    <FrameLayout
        android:id="@+id/rl_bg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:alpha="0.3"
        android:background="@color/white"
        />
    <RelativeLayout
        android:id="@+id/rl_menu_warp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:gravity="bottom|center_horizontal">



        <TextView
            android:id="@+id/tv_new_action"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="28dp"
            android:clickable="true"
            android:drawablePadding="@dimen/dp_8"
            android:drawableTop="@mipmap/new_action_icon"
            android:gravity="center_horizontal"
            android:paddingBottom="@dimen/more_window_item_margin"
            android:text="建立行動"
            android:textColor="#666"/>

        <TextView
            android:id="@+id/tv_new_order"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="28dp"
            android:layout_marginRight="28dp"
            android:layout_toRightOf="@+id/tv_new_action"
            android:clickable="true"
            android:drawablePadding="@dimen/dp_8"
            android:drawableTop="@mipmap/new_order_icon"
            android:gravity="center_horizontal"
            android:paddingBottom="@dimen/more_window_item_margin"
            android:text="建立訂單"
            android:textColor="#666"/>

        <TextView
            android:id="@+id/tv_client_input"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="28dp"
            android:layout_toRightOf="@+id/tv_new_order"
            android:clickable="true"
            android:drawablePadding="@dimen/dp_8"
            android:drawableTop="@mipmap/client_input_icon"
            android:gravity="center_horizontal"
            android:paddingBottom="@dimen/more_window_item_margin"
            android:text="客戶錄入"
            android:textColor="#666"/>

        <TextView
            android:id="@+id/tv_ranking_listcon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_new_action"
            android:layout_marginRight="28dp"
            android:layout_marginTop="20dp"
            android:clickable="true"
            android:drawablePadding="@dimen/dp_8"
            android:drawableTop="@mipmap/ranking_listcon"
            android:gravity="center_horizontal"
            android:paddingBottom="@dimen/more_window_item_margin"
            android:text="琅琊榜"
            android:textColor="#666"/>

        <TextView
            android:id="@+id/tv_import_mail_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_new_order"
            android:layout_marginLeft="28dp"
            android:layout_marginRight="28dp"
            android:layout_marginTop="20dp"
            android:layout_toRightOf="@+id/tv_ranking_listcon"
            android:clickable="true"
            android:drawablePadding="@dimen/dp_8"
            android:drawableTop="@mipmap/import_mail_icon"
            android:gravity="center_horizontal"
            android:paddingBottom="@dimen/more_window_item_margin"
            android:text="通訊錄導入"
            android:textColor="#666"/>

        <TextView
            android:id="@+id/tv_scan_card_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_client_input"
            android:layout_marginBottom="140dp"
            android:layout_marginLeft="28dp"
            android:layout_marginTop="20dp"
            android:layout_toRightOf="@+id/tv_import_mail_icon"
            android:clickable="true"
            android:drawablePadding="@dimen/dp_8"
            android:drawableTop="@mipmap/scan_card_icon"
            android:gravity="center_horizontal"
            android:paddingBottom="@dimen/more_window_item_margin"
            android:text="掃名片"
            android:textColor="#666"/>
    </RelativeLayout>



    <!--打開與關閉的菜單-->
    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab_close_more_menu"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        style="@style/DialogTextStyle"
        android:layout_marginBottom="-15dp"
        android:background="@color/white"
        android:rotation="45"
        android:src="@drawable/ic_clear"/>
</RelativeLayout>
           

運作起來看看效果吧:

Android之路 - 實作高斯模糊的菜單前言分析實作效果

布局效果

行了,初步完成,接下來就是為tab的快捷菜單增加點選時間,然後進行動畫部分的編寫了。

編寫打開動畫

将menu布局gone起來,再做下一步。雖然有點水字數的嫌疑,但是我還是要放出來:

使用translationY動畫逐個逐個的将menu從螢幕外移動到原來的位置,而從第0個menu開始,後面的每個menu根據下标延遲啟動動畫。

- 第1個比第0個延遲開始動畫160毫秒*

- 第2個比第1個延遲開始動畫260毫秒*

- 第3個比第2個延遲開始動畫360毫秒*

Android之路 - 實作高斯模糊的菜單前言分析實作效果

感動羞澀

來看代碼部分的編寫吧:

/**
 * 打開動畫
 */
private void showAnimation() {
    // 擷取子view的個數,進行周遊
    for (int i = 0; i < rlMenuWrap.getChildCount(); i++) {
        // 擷取到相應的子view
        final View child = rlMenuWrap.getChildAt(i);
        // 關閉按鈕和背景進行跳過
        if (child.getId() == R.id.fab_close_more_menu || child.getId() == R.id.rl_bg) {
            continue;
        }
        // 先暫時隐藏
        child.setVisibility(View.INVISIBLE);
        mHandler.postDelayed(new Runnable() {

            @Override
            public void run() {
                //顯示菜單
                child.setVisibility(View.VISIBLE);
                // Y軸位移動畫 從800移動到目前位置
                ValueAnimator fadeAnim = ObjectAnimator.ofFloat(child, "translationY", 600, 0);
                //動畫時長
                fadeAnim.setDuration(200);
                // 自定義內插補點器
                KickBackAnimator kickAnimator = new KickBackAnimator();
                //設定速度
                kickAnimator.setDuration(100);
                // 設定內插補點器
                fadeAnim.setEvaluator(kickAnimator);
                //開始動畫
                fadeAnim.start();
            }
        }, i * 60);// 根據下标延遲執行動畫
    }
}
           

代碼裡面用到了一個自定義內插補點器,内容如下:

public class KickBackAnimator implements TypeEvaluator<Float> {
    private final float s = 1.70158f;
    float mDuration = 0f;

    public void setDuration(float duration) {
        mDuration = duration;
    }

    public Float evaluate(float fraction, Float startValue, Float endValue) {
        float t = mDuration * fraction;
        float b = startValue.floatValue();
        float c = endValue.floatValue() - startValue.floatValue();
        float d = mDuration;
        float result = calculate(t, b, c, d);
        return result;
    }

    public Float calculate(float t, float b, float c, float d) {
        return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
    }
}

           

接下來就是調用showAnimation方法了,在調用之前先setVisibility,來看看效果吧:

Android之路 - 實作高斯模糊的菜單前言分析實作效果

高斯模糊菜單01.gif

為了能夠更好的顯示效果,故意增加了動畫的時長,好了關于打開動畫就這樣完成,接下來就是關閉動畫的編寫了。

編寫關閉動畫

這裡不水了,關閉動畫與打開動畫基本上是一緻的,其他就兩點差別:

  • 延遲動畫的時間是相反的
  • 第0個動畫完成需要關閉菜單
/**
 * 關閉動畫
 *
 */
private void closeAnimation() {
    // 擷取所有的子view
    for (int i = 0; i < rlMenuWrap.getChildCount(); i++) {
        final View child = rlMenuWrap.getChildAt(i);
        // 判斷  關閉按鈕和背景不進入動畫
        if (child.getId() == R.id.fab_close_more_menu || child.getId() == R.id.rl_bg) {
            continue;
        }
        mHandler.postDelayed(new Runnable() {

            @Override
            public void run() {
                // 顯示
                child.setVisibility(View.VISIBLE);
                // Y軸位移動畫 從目前位置到600
                ValueAnimator fadeAnim = ObjectAnimator.ofFloat(child, "translationY", 0, 600);
                fadeAnim.setDuration(200);
                KickBackAnimator kickAnimator = new KickBackAnimator();
                kickAnimator.setDuration(100);
                fadeAnim.setEvaluator(kickAnimator);
                fadeAnim.start();
                fadeAnim.addListener(new BaseAnimatorListener() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        // 動畫完成後隐藏menu
                        child.setVisibility(View.INVISIBLE);
                    }
                });
            }
        }, (rlMenuWrap.getChildCount() - i - 1) * 30);// 設定和打開相反的延遲時長

        //第0個
        if (i == 0) {
            mHandler.postDelayed(new Runnable() {

                @Override
                public void run() {
                    // 隐藏menu
                    rlMoreMenuRoot.setVisibility(View.GONE);
                }
            }, (rlMenuWrap.getChildCount() - i) * 30 + 80);
        }
    }

}

           

看看效果吧:

Android之路 - 實作高斯模糊的菜單前言分析實作效果

高斯模糊菜單02.gif

此緻,菜單相關的效果也就完成了,剩下的就是高斯模糊和底部圓形菜單的旋轉了。

高斯模糊和旋轉

高斯模糊

高斯模糊就是一個工具方法而已,沒什麼其它出彩的地方,拿到截圖後用高斯模糊算法工具方法進行模糊算法,這部分就不放出來了,以免有水字數的嫌疑。可以直接在底部點選源碼進行檢視。

旋轉

其實就是一個簡單的方法,先放出來吧:

/**
 * 旋轉菜單按鈕
 */
private void rotationActionMenu(int from, int to) {
    ValueAnimator fadeAnim = ObjectAnimator.ofFloat(fabCloseMoreMenu, "rotation", from, to);
    fadeAnim.setDuration(300);
    KickBackAnimator kickAnimator = new KickBackAnimator();
    kickAnimator.setDuration(150);
    fadeAnim.setEvaluator(kickAnimator);
    fadeAnim.start();
}
           

在打開和關閉的時候傳入不同的值即可。

效果

最後,來看看相關的效果圖,再放一次。

Android之路 - 實作高斯模糊的菜單前言分析實作效果

才能夠本章可以看出,任何複雜的效果,都能通過拆分成小功能來實作。

未完待續、敬請期待!

我的部落格位址
Android之路 - 實作高斯模糊的菜單前言分析實作效果

FullScreenDeveloper

源碼位址

繼續閱讀