github源码:https://github.com/danielzeller/Depth-LIB-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系统的手机,只能呵呵哒了。