Depth-LIB-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(簡書)