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

jkhunter517 3年前
   <p>前些日子在微信朋友圈看到一个朋友发了一个很酷的Android特效,对于喜欢酷炫效果的我来说,真的好想知道它是怎么搞出来的!于是,在知道Google商店可以下载,我反编译了这个Demo并把源码开源到Github上,当然,目的只是想让很多喜欢这个东西的朋友知道是怎么实现的。我以为只要把原作者是谁说明了,就可以开源了,果然还是太年轻了。</p>    <p><img src="https://simg.open-open.com/show/44623fe72553a5f7ba0b0074c45109eb.gif"></p>    <p>那天晚上,代码家的干货群就讨论了我未经作者同意开源源代码的事。我看到后,意识到自己错了,就马上删了!drakeet给了我原作者的联系方式,我也发了邮件向作者说了这件事!</p>    <p><img src="https://simg.open-open.com/show/ecf349864fcc9748980173ff53698305.jpg"></p>    <p><br> 没错,果然今天作者就在Github上开源了! 源码下载地址:<a href="/misc/goto?guid=4959672006413788721">戳我</a></p>    <p>因为之前就看了源码实现,也有朋友叫我写一篇分析文,今天我带大家看看它是怎么实现的!</p>    <p><strong>一、小说界面过渡动画</strong></p>    <p><img src="https://simg.open-open.com/show/289f4d4a225104d844fa92f360f29b7a.gif"></p>    <p><br> (1)点击Fab,开启过渡界面动画效果,监听事件如下:</p>    <pre>  <code class="language-java">   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                  /**佘略部分代码**/              }          });</code></pre>    <p>这里退出动画的实现是这句<code>TransitionHelper.startExitAnim(root)</code>,传入的参数是当前Fragment的视图root。<code>TransitionHelper</code>是个动画实现类,主要做了视图进入、离开、恢复这些动画。我们进去看看退出动画的实现。源码如下:</p>    <pre>  <code class="language-java">    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);      }</code></pre>    <p>以上代码,你可以看出<code>startExitAnim</code>对当前Fragment的视图Root的每个子控件都做了不一样的动画,具体是实现是在<code>exitAnimate(...)</code>方法中,代码比较多,我就不贴了。<br> 主要是开启了6个ObjectAnimator动画做了view的旋转、缩放、平移、阴影等动画,其中有句代码很关键<code>View.setCameraDistance()</code>,设置Camera的距离,表现出透视效果。</p>    <p>一个Fragment做了离开的动画,我们看看它进入的Fragment动画是怎么实现的!上文中Fab监听的代码里有这句<code>getActivity()).goToFragment(windFragment)</code>,应该就是另一个Framgen进入的逻辑实现了,跟进去看看!</p>    <pre>  <code class="language-java">   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);      }</code></pre>    <p>握草,没看到进入的Framgent的动画,奇了怪了。我们进入 WindFragment 看具体实现!你会发现在<code>onCreateView</code>实现了。</p>    <pre>  <code class="language-java">    @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container,                               Bundle savedInstanceState) {          root = inflater.inflate(R.layout.fragment_wind, container, false);          ......          doIntroAnimation();//进入动画          .....          return root;      }</code></pre>    <p>而<code>doIntroAnimation</code>方法中调用了<code>TransitionHelper.startIntroAnim(...)</code>,你会看到</p>    <pre>  <code class="language-java"> 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);      }</code></pre>    <p>这个逻辑有和界面离开时的参不多,开启了多个ObjectAnimator动画做了view的旋转、缩放、平移、阴影等动画。</p>    <p>细心的你会发现,我给的效果和设计图的不同啊,没错,如果只是做过渡动画,还达不到很酷炫的效果,这里还有阴影的效果。</p>    <p><strong>二、小说绘制布局阴影</strong></p>    <p><img src="https://simg.open-open.com/show/6f49119b4dc254f306ee7e868210bfae.jpg"></p>    <p><br> 上图看到出,阴影效果很明显。我们看看Fragment的xml布局是这样的</p>    <pre>  <code class="language-java"><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></code></pre>    <p>你会发现都是一个外层DepthRendrer控件里有几个DepthLayout控件,而DepthRendrer和DepthLayout都继承RelativeLayout。DepthRendrer在初始化的时候设置了视图树中的焦点状态改变时,回调函数监听,计算绘制DepthLayout阴影的范围。</p>    <pre>  <code class="language-java">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;              }          });</code></pre>    <p><code>invalidate</code>会调用DepthRendrer中的</p>    <p><code>drawChild(Canvas canvas, View child, long drawingTime)</code>,绘制子 控件阴影。</p>    <pre>  <code class="language-java">    @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);      }</code></pre>    <p>这句代码主要是针对不同的控件绘制不同的阴影,比如矩形和圆形绘制阴影的方法是不一样的。</p>    <p><strong>三、小小总结</strong></p>    <p><img src="https://simg.open-open.com/show/e930a39ac36d03d550d8f3be2c54358c.gif"></p>    <p>,<br> 做到以上两步,基本上我们就可以得到上图的效果了,<br> 你会发现,现在看来这些酷炫的效果也只是一些简单的动画组合而成,在绘制好界面,就能弄出不错的效果了,看源码中,我能看出作者对细节的设计真的很用心!至于界面中的波浪、两只小熊的效果有时间说说,你有兴趣看看源码实现,其实也挺简单的。啊对了,这个酷炫的东西最低版本支持21,也就是说4.0系统的手机,只能呵呵哒了。</p>    <p>最后:在简书开坑写文章,呵呵,欢迎关注,最好是来批我的,这样我才能进步啊!</p>    <p>最后的之后:明天就是假期了,祝大家玩的开心点。</p>    <p> </p>    <p>文/<a href="/misc/goto?guid=4959672006508495855">小说家CJJ</a>(简书)<br>  </p>