Depth-LIB-Android - 酷炫的Android特效

jkhunter517 8年前發布 | 30K 次閱讀 Android開發 移動開發 Android

前些日子在微信朋友圈看到一個朋友發了一個很酷的Android特效,對于喜歡酷炫效果的我來說,真的好想知道它是怎么搞出來的!于是,在知道Google商店可以下載,我反編譯了這個Demo并把源碼開源到Github上,當然,目的只是想讓很多喜歡這個東西的朋友知道是怎么實現的。我以為只要把原作者是誰說明了,就可以開源了,果然還是太年輕了。

那天晚上,代碼家的干貨群就討論了我未經作者同意開源源代碼的事。我看到后,意識到自己錯了,就馬上刪了!drakeet給了我原作者的聯系方式,我也發了郵件向作者說了這件事!


沒錯,果然今天作者就在Github上開源了! 源碼下載地址:戳我

因為之前就看了源碼實現,也有朋友叫我寫一篇分析文,今天我帶大家看看它是怎么實現的!

一、小說界面過渡動畫


(1)點擊Fab,開啟過渡界面動畫效果,監聽事件如下:

   root.findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {//在一個視圖樹中的焦點狀態發生改變時,所要調用的回調函數的接口類
                        root.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        TransitionHelper.startExitAnim(root);//當前界面離開時的動畫
                    }
                });
                WindFragment windFragment = new WindFragment();
                windFragment.setIntroAnimate(true);//設置WindFragment的動畫標志 
                ((RootActivity) getActivity()).goToFragment(windFragment);//添加進入的Fragment
                /**佘略部分代碼**/
            }
        });

這里退出動畫的實現是這句TransitionHelper.startExitAnim(root),傳入的參數是當前Fragment的視圖root。TransitionHelper是個動畫實現類,主要做了視圖進入、離開、恢復這些動畫。我們進去看看退出動畫的實現。源碼如下:

    public static void startExitAnim(View root) {
        exitAnimate((DepthLayout) root.findViewById(R.id.root_dl), 0, 30f, 15, 190, true);
        exitAnimate((DepthLayout) root.findViewById(R.id.appbar), MOVE_Y_STEP, 20f, 30, 170, true);
        exitAnimate((DepthLayout) root.findViewById(R.id.fab_container), MOVE_Y_STEP * 2f, 20f, 45, 210, true);
        exitAnimate((DepthLayout) root.findViewById(R.id.dl2), MOVE_Y_STEP, 20f, 60, 230, true);
        exitAnimate((DepthLayout) root.findViewById(R.id.dl3), MOVE_Y_STEP * 2, 20f, 75, 250, true);
    }

以上代碼,你可以看出startExitAnim對當前Fragment的視圖Root的每個子控件都做了不一樣的動畫,具體是實現是在exitAnimate(...)方法中,代碼比較多,我就不貼了。
主要是開啟了6個ObjectAnimator動畫做了view的旋轉、縮放、平移、陰影等動畫,其中有句代碼很關鍵View.setCameraDistance(),設置Camera的距離,表現出透視效果。

一個Fragment做了離開的動畫,我們看看它進入的Fragment動畫是怎么實現的!上文中Fab監聽的代碼里有這句getActivity()).goToFragment(windFragment),應該就是另一個Framgen進入的邏輯實現了,跟進去看看!

   public void goToFragment(final Fragment newFragment) {
        getFragmentManager().beginTransaction().add(R.id.fragment_container, newFragment).commit();//添加新的fragment
        final Fragment removeFragment = currentFragment;//記錄要移除的Fragment
        currentFragment = newFragment;
        getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {//延遲兩秒后,刪除記錄刪除的Fragment
                getFragmentManager().beginTransaction().remove(removeFragment).commit();
            }
        }, 2000);
    }

握草,沒看到進入的Framgent的動畫,奇了怪了。我們進入 WindFragment 看具體實現!你會發現在onCreateView實現了。

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        root = inflater.inflate(R.layout.fragment_wind, container, false);
        ......
        doIntroAnimation();//進入動畫
        .....
        return root;
    }

doIntroAnimation方法中調用了TransitionHelper.startIntroAnim(...),你會看到

 public static void startIntroAnim(View root, AnimatorListenerAdapter introEndListener) {
        introAnimate((DepthLayout) root.findViewById(R.id.root_dl), 0, 30f, 15, 180);
        introAnimate((DepthLayout) root.findViewById(R.id.appbar), MOVE_Y_STEP, 20f, 30, 170);
        introAnimate((DepthLayout) root.findViewById(R.id.fab_container), MOVE_Y_STEP * 2f, 20f, 45, 190);
        introAnimate((DepthLayout) root.findViewById(R.id.dl2), MOVE_Y_STEP, 20f, 60, 200);
        introAnimate((DepthLayout) root.findViewById(R.id.dl3), MOVE_Y_STEP * 2, 20f, 75, 210).addListener(introEndListener);
    }

這個邏輯有和界面離開時的參不多,開啟了多個ObjectAnimator動畫做了view的旋轉、縮放、平移、陰影等動畫。

細心的你會發現,我給的效果和設計圖的不同啊,沒錯,如果只是做過渡動畫,還達不到很酷炫的效果,這里還有陰影的效果。

二、小說繪制布局陰影


上圖看到出,陰影效果很明顯。我們看看Fragment的xml布局是這樣的

<no.agens.depth.lib.DepthRendrer
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".sample.WaterFragment"
    >

    <no.agens.depth.lib.DepthLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/appbar_height"
        android:background="@color/green"
        android:layerType="hardware"
        app:edge_color="@color/statusbar2"
        >

        <ImageView
            />

    </no.agens.depth.lib.DepthLayout>

    ......

    <no.agens.depth.lib.DepthLayout
        android:id="@+id/fab_container"
            ......
        >
        <android.support.design.widget.FloatingActionButton
            ......
            />
    </no.agens.depth.lib.DepthLayout>

</no.agens.depth.lib.DepthRendrer>

你會發現都是一個外層DepthRendrer控件里有幾個DepthLayout控件,而DepthRendrer和DepthLayout都繼承RelativeLayout。DepthRendrer在初始化的時候設置了視圖樹中的焦點狀態改變時,回調函數監聽,計算繪制DepthLayout陰影的范圍。

void setup() {
        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                for (int i = 0; i < getChildCount(); i++) {//遍歷子控件
                    View child = getChildAt(i);
                    if (child instanceof DepthLayout) {
                        //如果是DepthLayout控件 ,就調用DepthLayout的calculateBounds方法計算要繪制陰影的范圍
                        boolean hasChangedBounds = ((DepthLayout) child).calculateBounds();
                        if (hasChangedBounds)
                            invalidate();
                    }
                }
                return true;
            }
        });

invalidate會調用DepthRendrer中的

drawChild(Canvas canvas, View child, long drawingTime),繪制子 控件陰影。

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        if (child instanceof DepthLayout && !isInEditMode()) {
            DepthLayout dl = (DepthLayout) child;
            float[] src = new float[]{0, 0, dl.getWidth(), 0, dl.getWidth(), dl.getHeight(), 0, dl.getHeight()};
            if (dl.isCircle()) {//控件是否適圓形的
                dl.getCustomShadow().drawShadow(canvas, dl, roundSoftShadow);
                if (Math.abs(dl.getRotationX()) > 1 || Math.abs(dl.getRotationY()) > 1)
                    drawCornerBaseShape(dl, canvas, src);
            } else {
                dl.getCustomShadow().drawShadow(canvas, dl, softShadow);
                if (dl.getRotationX() != 0 || dl.getRotationY() != 0) {
                    if (getLongestHorizontalEdge(dl) > getLongestVerticalEdge(dl))
                        drawVerticalFirst(dl, canvas, src);
                    else
                        drawHorizontalFist(dl, canvas, src);
                }
            }
        }
        return super.drawChild(canvas, child, drawingTime);
    }

這句代碼主要是針對不同的控件繪制不同的陰影,比如矩形和圓形繪制陰影的方法是不一樣的。

三、小小總結

,
做到以上兩步,基本上我們就可以得到上圖的效果了,
你會發現,現在看來這些酷炫的效果也只是一些簡單的動畫組合而成,在繪制好界面,就能弄出不錯的效果了,看源碼中,我能看出作者對細節的設計真的很用心!至于界面中的波浪、兩只小熊的效果有時間說說,你有興趣看看源碼實現,其實也挺簡單的。啊對了,這個酷炫的東西最低版本支持21,也就是說4.0系統的手機,只能呵呵噠了。

最后:在簡書開坑寫文章,呵呵,歡迎關注,最好是來批我的,這樣我才能進步啊!

最后的之后:明天就是假期了,祝大家玩的開心點。

 

文/小說家CJJ(簡書)
 

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