diff --git "a/issue-34/\345\234\250Android\345\274\200\345\217\221\344\270\255\344\275\277\347\224\250RxJava.md" "b/issue-34/\345\234\250Android\345\274\200\345\217\221\344\270\255\344\275\277\347\224\250RxJava.md" index 9ebfccc6..a088b75b 100644 --- "a/issue-34/\345\234\250Android\345\274\200\345\217\221\344\270\255\344\275\277\347\224\250RxJava.md" +++ "b/issue-34/\345\234\250Android\345\274\200\345\217\221\344\270\255\344\275\277\347\224\250RxJava.md" @@ -9,11 +9,11 @@ - 校对者: [desmond1121](https://github.com/desmond1121) - 状态 : 完成 -[ReactiveX](http://reactivex.io/)是专注于异步工作的API,它将异步事件的处理雨观察者模式、迭代器模式及函数式编程相结合了起来。实时地处理返回数据是在工程中经常出现的情景,所以使用高效、可拓展的方式来解决这种问题非常重要。ReactiveX通过观察者模式以及操作符来提供灵活地处理异步通信的方式,你不用再去关注线程创造与同步这些繁琐的事情。 +[ReactiveX](http://reactivex.io/)是专注于异步工作的API,它将异步事件的处理与观察者模式、迭代器模式及函数式编程相结合了起来。实时地处理返回数据是在工程中经常出现的情景,所以使用高效、可拓展的方式来解决这种问题非常重要。ReactiveX通过观察者模式以及操作符来提供灵活地处理异步通信的方式,你不用再去关注线程创造与同步这些繁琐的事情。 ##RxJava介绍 -[RxJava](https://github.com/ReactiveX/RxJava)是一个开源的实现ReactiveX的工具。这里面有两种主要的类:`Observalbe`和`Subscriber`。在RxJava中,`Observable`类产生异步数据或事件,`Subscriber`类对这些数据和事件进行操作。正常的工作流程就是`Observable`产生一系列的数据或事件,然后完成或者产生异常。一个`Observable`可以拥有多个`Subscriber`,每一个被生成的事件都会出发被绑定的`Subscriber`的`onNext()`方法。当一个`Observable`生成完所有事件后,所有绑定的`Subscriber`的`onCompleted()`方法会被调用(在发生异常时会调用`onError()`方法)。现在我们对这两个类有了初步的认识,可以开始了解如何创建与订阅一个`Observer`了: +[RxJava](https://github.com/ReactiveX/RxJava)是一个开源的实现ReactiveX的工具。这里面有两种主要的类:`Observalbe`和`Subscriber`。在RxJava中,`Observable`类产生异步数据或事件,`Subscriber`类对这些数据和事件进行操作。正常的工作流程就是`Observable`产生一系列的数据或事件,然后完成或者产生异常。一个`Observable`可以拥有多个`Subscriber`,每一个被生成的事件都会触发被绑定的`Subscriber`的`onNext()`方法。当一个`Observable`生成完所有事件后,所有绑定的`Subscriber`的`onCompleted()`方法会被调用(在发生异常时会调用`onError()`方法)。现在我们对这两个类有了初步的认识,可以开始了解如何创建与订阅一个`Observer`了: Observable integerObservable = Observable.create(new Observable.OnSubscribe() { @Override @@ -47,7 +47,7 @@ 你可以看到,这个`Subscriber`将每一个收到的数据打印了出来。当你创建完`Observable`与`Subscriber`后,你需要将它们用`Observable.subscribe()`方法连接起来。 integerObservable.subscribe(integerSubscriber); - // Outputs: + // 输出: // onNext: 1 // onNext: 2 // onNext: 3 @@ -125,15 +125,14 @@ System.out.println("Complete!"); } - @Override - public void onError(Throwable e) { - } + @Override + public void onError(Throwable e) { } - @Override - public void onNext(Double value) { - System.out.println("onNext: " + value); - } - }); + @Override + public void onNext(Double value) { + System.out.println("onNext: " + value); + } + }); // Outputs: // onNext: 1.0 @@ -277,9 +276,9 @@ RxAndroid是RxJava的轻量级拓展工具,它提供了运行在主线程上 在订阅Single的时候,只有`onSuccess()`、`onError()`可以监听。同时你可以调用`Single.mergeWith()`操作符来将多个Single合成一个`Observable`,这个被合成的`Observable`会依次输出所有Single的结果。 -##防止内存溢出 +##防止内存泄露 -我们之前提到,使用`AsyncTask`的一大缺点就是它可能会造成内存溢出。当它们持有的Activity/Fragment的引用没有正确处理时就会这样。不幸的是,RxJava并不会自动防止这种情况发生,好在它可以很容易地防止内存泄露。`Observable.subscribe()`方法会返回一个`Subscription`对象,这个对象仅仅有两个方法:`isSbscribed()`与`unsubscribe()`。你可以在Activity/Fragment的`onDestroy`方法中调用`Subscription.isSubscribed()`检测是否这个异步任务仍在进行。如果它仍在进行,则调用`unsubscribe()`方法来结束任务,从而释放其中的强引用,防止内存泄露。如果你使用了多个`Observable`与`Subscriber`,那么你可以将它们添加到`CompositeSubscription`中,并调用`CompositeSubscription.unsubscribe()`结束所有的任务。 +我们之前提到,使用`AsyncTask`的一大缺点就是它可能会造成内存泄露。当它们持有的Activity/Fragment的引用没有正确处理时就会这样。不幸的是,RxJava并不会自动防止这种情况发生,好在它可以很容易地防止内存泄露。`Observable.subscribe()`方法会返回一个`Subscription`对象,这个对象仅仅有两个方法:`isSbscribed()`与`unsubscribe()`。你可以在Activity/Fragment的`onDestroy`方法中调用`Subscription.isSubscribed()`检测是否这个异步任务仍在进行。如果它仍在进行,则调用`unsubscribe()`方法来结束任务,从而释放其中的强引用,防止内存泄露。如果你使用了多个`Observable`与`Subscriber`,那么你可以将它们添加到`CompositeSubscription`中,并调用`CompositeSubscription.unsubscribe()`结束所有的任务。 ##结束语 @@ -306,4 +305,4 @@ Java 8 引入了Lambda表达式,可惜的是Android不支持Java 8,所以我 () -> v.setEnabled(true)); }); -Lambda表达式减少了很多RxJava的样本代码,我强烈推荐将RetroLambda引入工程中使用。它给开发带来的好处不仅仅于RxJava上(你会注意到OnCliclListener也是用Lambda表达式设置的)! +Lambda表达式减少了很多RxJava的样本代码,我强烈推荐将RetroLambda引入工程中使用。它给开发带来的好处不仅仅于RxJava上(你会注意到`OnCliclListener`也是用Lambda表达式设置的)! diff --git "a/issue-43/IndeterminateProgressbar\350\247\243\346\236\220-Part 2.md" "b/issue-43/IndeterminateProgressbar\350\247\243\346\236\220-Part 2.md" new file mode 100644 index 00000000..1ff2c1db --- /dev/null +++ "b/issue-43/IndeterminateProgressbar\350\247\243\346\236\220-Part 2.md" @@ -0,0 +1,101 @@ +IndeterminateProgressbar解析-Part 2 +--- + +> * 原文链接 : [Indeterminate – Part 2](https://blog.stylingandroid.com/indeterminate-part-2/) +* 原文作者 : [Mark Allison](https://blog.stylingandroid.com/) +* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载! +* 译者 : [chaossss](https://github.com/chaossss) +* 校对者: [chaossss](https://github.com/chaossss) +* 状态 : 完成 + + + + +IndeterminateProgressBar 能在用户进行某项不定时长的耗时操作时提供绝佳的用户体验,之前我有教过大家怎么创建[水平的 IndeterminateProgressBar](http://blog.csdn.net/u012403246/article/details/49582789),今天我就来教大家实现圆形的 IndeterminateProgressBar,这个控件将支持 API 11(Honeycomb)以上的设备。 + +在上一篇博文中我们学习了圆形 IndeterminateProgressBar 的 AOSP 实现方式,以及 AnimatedVectorDrawable 如何被用于显示 VectorDrawable 路径动画以得到圆形 IndeterminateProgressBar 的长度变化动画。在本篇博文中我们将把注意力转移到插值器上,即研究圆形 IndeterminateProgressBar 在显示长度变化动画时,是如何控制圆弧起点和终点的位置的。 + +不妨直接看 AOSP 的源码吧,[首先看 控制 trimPathStart 的值的插值器](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/interpolator/trim_start_interpolator.xml): + +```xml + + + +``` + +乍一看这代码怎么这么少,那个 PathInterpolator 到底是什么鬼? + +PathInterpolator 其实就是 Lollipop 中介绍的描述插值器功能最具代表性的插值器子类,看到这里不要绝望,在以后的博文中我们还会遇到 PathInterpolatorCompat 这个类。 + +A PathInterpolator takes an SVG pathData parameter which describes a mapping function of the form y = f(x). That sounds rather complex, but it is actually much simpler than it sounds. Effectively it is a square canvas which is one unit in each direction from 0,0 to 1,1. The PathInterpolator works by returning the y value for any given x value. The input values range from 0.0-1.0, so there only stipulation is that each possible value for x can only map to a single value of y – the path cannot double back on itself in the horizontal plane in other words. + +PathInterpolator 需要 SVG pathData 参数以描述 y = f(x) 的映射关系,可能听起来有点复杂,但其实它非常简单。实际上,就是一个每个方向上都有一个从 0,0 到 1,1 的单元的方形画布,PathInterpolator 可以根据任意给定的 x 的值返回给定函数所映射的 y 值,其接收的输入的取值范围是 0.0 - 1.0,所以其输入的唯一约定就是:每一个可能的 x 和 y 必须是1对1映射。 + +如果我们画了一条从 0,0 到 1,1 的直线,实际上就相当于创建了线性插值器(LinearInterpolator),因为每一个 x 的值对应的 y 值都相同。那么上述代码中 pathData 到底做了什么?运用一个有趣的技巧,我们可以创建 VectorDrawable 并添加该 path 元素,然后用 Android Studio 的预览功能就会看到下图: + +```xml + + + + + + +``` + +![](https://i2.wp.com/blog.stylingandroid.com/wp-content/uploads/2016/01/trim_start_interpolator.png?w=608) + +由图可知,0,0 就是路径的左上角,1,1 就是路径的右下角。当 x = [0.0 - 0.5], y = 0;当 x = (0.5, 1.0],y 值的增长逐渐增加,然后超过中值,增长速率又渐渐变慢,最终为 1.0。其实际作用与 AccelerateDecelerateInterpolator 相同。如果我们研究 trimPathEnd 使用的插值器,会发现这两个阶段完成的工作比使用传统插值器要复杂: + +```xml + + + +``` + +看起来这段 xml 代码和上面的很类似,但 pathData 对应的值是不同的,不妨再次利用 VectorDrawable 将这个插值器对应的函数图像显示出来: + +![](https://i0.wp.com/blog.stylingandroid.com/wp-content/uploads/2016/01/trim_start_and_end_interpolators.png?resize=223%2C300) + +图中红色显示作加速的部分,y 值快速增加直到大于中值,然后减缓增大的速率直到值为 1.0,相当于把上面的过程反过来。 + +trimPathEnd 会控制长度变化动画中圆弧的终点,trimPathStart 则控制该圆弧的起点。利用这些插值器我们可以让圆弧的长度自动变化,以显示长度变化的动画。最终结合旋转动画就可以实现该圆形 IndeterminateProgressbar 的效果,即下面视频展示的效果: + +[视频](https://youtu.be/g6Zo6WDS2Gg) + +现在我们明白该控件是怎么实现的了,而且 AnimatedVectorDrawable 支持 Android 5.0 以前的版本,因此我们不用担心它的兼容性问题(在写下本文时 VectorDrawableCompat 由于兼容性的问题不是个好选择)。因此我们不需要 VectorDrawable 就可以利用到目前为止学习的知识在 API 11 上实现酷炫的动画效果,在下一篇博文中我将会介绍应该怎么去实现。 + +因为这篇博文都是基于[ Google 源码](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/anim/progress_indeterminate_rotation_material.xml)进行的,所以我就不提供源码了,但后面的文章我保证都会有源码! \ No newline at end of file diff --git "a/issue-43/LayoutInflater.inflate() \346\226\271\346\263\225\345\211\226\346\236\220.md" "b/issue-43/LayoutInflater.inflate() \346\226\271\346\263\225\345\211\226\346\236\220.md" new file mode 100644 index 00000000..204498f7 --- /dev/null +++ "b/issue-43/LayoutInflater.inflate() \346\226\271\346\263\225\345\211\226\346\236\220.md" @@ -0,0 +1,146 @@ +LayoutInflater.inflate() 方法剖析 +--- + +> * 原文链接 : [Understanding Android's LayoutInflater.inflate()](https://www.bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/) +* 原文作者 : [Sean Farrell](https://www.bignerdranch.com/about-us/nerds/sean-farrell/) +* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载! +* 译者 : [chaossss](https://github.com/chaossss) +* 校对者: [chaossss](https://github.com/chaossss) +* 状态 : 完成 + + + + +程序员很容易满足于写模板代码,因为这样最省事,但不愿走出舒适区,沉溺于这样的生活的话,往往会忽略许多细节,而我就是其中一员。LayoutInflater 相信没有 Android 开发者会陌生,它能将 xml 布局转化为相应的视图(如:ViewGroup 和 Widget),它还可以在 Fragment 的 onCreateView() 方法里面初始化 Fragment 的布局。在阅读了 Google 给出的有关 LayoutInflater 的文档说明以及网上对它的讨论,我发现有许多开发者都不了解 LayoutInflater 的 inflate() 的细节,而且一直以错误的方式使用它。 + +使用 inflate() 方法的困惑大多来自 Google 文档对该方法的第三个参数 - attachToRoot 模糊的解释。 + +> 被初始化的视图布局是否应该添加到方法的第二个参数 ViewGroup root 上?如果不应该,root 只能被用于创建根布局所使用的 LayoutParams。 + +如果 attachToRoot 为 true,那么作为参数传入的 xml 布局就会被初始化,并添加到方法的第二个参数 - ViewGroup 上。 + +而 inflate() 方法将返回该组合后的 View,该 View 的根布局就是该 ViewGroup。而当 attachToRoot 为 false,xml 布局将被初始化且作为 inflate() 方法的返回值返回。但此时该 View 的根布局是 xml 文件中的根布局,而不是 inflate() 方法的第二个参数 ViewGroup 所对应的布局。不管怎样,ViewGroup 的 LayoutParams 都需要准确地按照该 xml 布局显示视图。 + +将 attachToRoot 设为 true 使得 xml 布局被添加到参数的 ViewGroup 中;将它设为 false,会使该布局以其他方式被添加到 ViewGroup 中。 + +##将 attachToRoot 设为 true + +假设我们在 xml 布局中显示一个 Button,其 width 和 height 都是 match_parent + +```xml + +``` + +现在我们要把该 Button 添加到 Activity/Fragment 中的 LinearLayout,假设此时 LinearLayout 已经是 Activity/Fragment 的成员,那么我们可以这样实现: + +```java +inflater.inflate(R.layout.custom_button, mLinearLayout, true); +``` + +我们让 inflate() 方法通过 xml 文件得到 Button 布局,且通过设置 attachToRoot 让 LayoutInflater 将 Button 添加到 mLinearLayout 中,Button 的 LayoutParams 应该是 LinearLayout.LayoutParams。 + +同样的,下面的方法和上面的效果一样,因为该方法默认将 attachToRoot 设为 true。 + +```java +inflater.inflate(R.layout.custom_button, mLinearLayout); +``` + +其他将 attachToRoot 设为 true 的正确用法是传入自定义 View,在下面的例子里,xml 布局中使用了 标签允许布局将它里面嵌套的 View 直接 include 到它的父布局中,而没有多加一层 ViewGroup,减小了 ViewTree 的深度。 + +```java +public class MyCustomView extends LinearLayout { + ... + private void init() { + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.view_with_merge_tag, this); + } +} +``` + +这样就是将 attachToRoot 设为 true 的完美用法了,布局文件没有设置 ViewGroup 去容纳控件,使得我们能将自定义的 LinearLayout 设为该布局的容器。如果我们没有使用 标签,而是将 FrameLayout 设置为它的根布局,那么初始化完成的工作和政策情况是一样的。该 FrameLayout 和 子元素都会被添加到 LinearLayout 中,LinearLayout 将作为根 ViewGroup 容纳 FrameLayout。 + +##将 attachToRoot 设为 false + +现在不妨学习应该在何时将 attachToRoot 设为 false 吧。此时,View 将不会被添加到第二个参数传递的 ViewGroup 中。 + +回忆前面的例子吧,我们可以通过下面的办法将该 Button 布局添加到 LinearLayout 中。 + +```java +Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false); +mLinearLayout.addView(button); +``` + +这两行代码和我们之前将 attachToRoot 设为 true 时一行代码完成的工作相同,将 attachToRoot 设为 false,意味着我们表示:不想将 View 添加到根 ViewGroup 中。但如果想添加到根 ViewGroup 中的话,通过 addView() 方法就可以实现。 + +虽说将 attachToRoot 设为 false 时,想要将 Button 添加到 ViewGroup 写的代码要比将 attachToRoot 设为 true 要多一些,但有些情况是必须将 attachToRoot 设为 false 的。 + +例如 RecyclerView 在 onCreateViewHolder() 方法中初始化子布局时就必须将 attachToRoot 设为 false + +```java +public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(getActivity()); + View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false); + return new ViewHolder(view); +} +``` + +何时初始化子布局,何时显示子布局,由 RecyclerView 决定,因此,将 View 添加到 ViewGroup 中的职责就不该由我们完成,所以 attachToRoot 就应该设为 false。 + +当在 Fragment 的 onCreateView() 中初始化并返回布局时,必须将 attachToRoot 设为 false,不然会抛出 IllegalStateException,因为该 View 已经属于某个父布局了。 + +```java +FragmentManager fragmentManager = getSupportFragmentManager(); +Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup); + +if (fragment == null) { + fragment = new MainFragment(); + fragmentManager.beginTransaction() + .add(R.id.root_viewGroup, fragment) + .commit(); +} +``` + +root_viewGroup 将在 Activity 中显示 Fragment,同时是 onCreateView() 中的 ViewGroup 参数,同时也是 inflate() 方法中的 ViewGroup 参数。将 Fragment 的 View 添加到 ViewGroup 的任务由 FragmentManager 完成,如果你不想两次进行该添加操作,就把 attachToRoot 设为 false 吧。 + +```java +public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false); + … + return view; +} +``` + +如果我们不想在 onCreateView() 中将它添加到 ViewGroup 中,为什么要在参数中提供 ViewGroup 参数呢?为什么 inflate() 方法需要根 ViewGroup?即使我们没有在初始化 View 后立刻将它添加到它的父 ViewGroup 中,我们也需要使用父 ViewGroup 的 LayoutParams 来初始化 View 的布局,即使它不会被添加到该 ViewGroup 中。 + +网上有很多有关 LayoutInflater 的用法,如果你想将 attachToRoot 设为 false,就将 ViewGroup 参数那填入 null。但如果父 ViewGroup 是可用的,还是应该将它填入该方法。 + +![](https://www.bignerdranch.com/img/blog/2016/02/null-root.png) + +Lint 会警告你别填入 null,虽说 App 不会因此崩溃,但可能会出现些问题。当 View 不知道 ViewGroup 的 LayoutParams 到底该是什么时,它会尝试通过自身的 generateDefaultLayoutParams 判断。而这些默认的 LayoutParams 可能不是你想要的。你在 xml 文件中设置的 LayoutParams 都会被无视,导致布局不是你想要的。 + +下面是你没有 ViewGroup 可传递的具体的场景。 + +```java +AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext); +View customView = inflater.inflate(R.layout.custom_alert_dialog, null); +... +dialogBuilder.setView(customView); +dialogBuilder.show(); +``` + +这种情况下,传递 null 是没有问题的,因为 AlertDialog 将 LayoutParams 重载,使得无论如何都是 match_parent。但通常情况下,你都是要传入父 ViewGroup 的。 + +##避免崩溃,错误布局以及对 LayoutInflater 的误解 + +希望这篇博文能帮你避免崩溃、错误布局以及对 LayoutInflater 的误解,下面是一些建议: + +- 如果你有可传递的根 ViewGroup,那就传递 +- 避免在 ViewGroup 参数那填入 null +- 当添加 View 到 ViewGroup 的职责不由我们负责时,attachToRoot 就设为 false +- 若 View 已经被添加到 ViewGroup 中,别将 attachToRoot 设为 true +- 自定义 View 很适合将 attachToRoot 设为 true \ No newline at end of file diff --git "a/issue-43/\347\273\223\345\220\210motion\345\222\214Transition\345\256\236\347\216\260\345\205\261\344\272\253\345\205\203\347\264\240\347\232\204\351\205\267\347\202\253\345\212\250\347\224\273.md" "b/issue-43/\347\273\223\345\220\210motion\345\222\214Transition\345\256\236\347\216\260\345\205\261\344\272\253\345\205\203\347\264\240\347\232\204\351\205\267\347\202\253\345\212\250\347\224\273.md" new file mode 100644 index 00000000..815d63e4 --- /dev/null +++ "b/issue-43/\347\273\223\345\220\210motion\345\222\214Transition\345\256\236\347\216\260\345\205\261\344\272\253\345\205\203\347\264\240\347\232\204\351\205\267\347\202\253\345\212\250\347\224\273.md" @@ -0,0 +1,268 @@ +结合motion和Transition实现共享元素的酷炫动画 +--- + +> * 原文链接 : [Meaningful Motion with Shared Element Transition and Circular Reveal Animation](http://www.thedroidsonroids.com/blog/android/meaningful-motion-with-shared-element-transition-and-circular-reveal-animation/) +* 原文作者 : [Mariusz Brona](http://www.thedroidsonroids.com/) +* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载! +* 译者 : [chaossss](https://github.com/chaossss) +* 校对者: [chaossss](https://github.com/chaossss) +* 状态 : 完成 + + + +##概述 + +水波纹动画是 Material Design 中最受欢迎的动画之一,下面是官方文档对它的描述: + +> 水波纹动画能在显示/隐藏一组 UI 元素时为用户提供连续的视觉体验。 + +使用水波纹动画,我们可以创造更酷炫的动画效果,今天我会教大家组合使用水波纹动画,淡出/淡入动画以及共享元素 Transition,创造酷炫的 Transition 动画,下面是某个 UI 控件使用该动画前和使用动画后的效果: + +使用前: + +[视频](https://youtu.be/GtSFkEzPbDM) + +使用后: + + +[视频](https://youtu.be/0KQfK3GatL8) + +##首先:共享元素 Transition + +Android Lollipop 介绍了新的设计理念 - Material Design,在本次更新中我们了解到新的,很酷的设计风格,例如共享元素 Transition。Transition 使我们能够根据用户的意向提供一致性体验,实现 Material Design 核心理念之一。具体实现可以通过下面三个步骤完成: + +##声明共享元素 Transition + +首先,我们需要声明使用 Transition 的 Activity 视图中某个 UI 控件为共享元素,这一步可以通过 xml 或 java 代码完成: + +```xml + +``` + +或 + +```java + mFab.setTransitionName("reveal"); +``` + +然后我们要在需要通过显示 Transition 动画启动的 Activity 中声明 Transition 的名称,然后通过这些方法启动 Activity: + +```java +Intent intent = new Intent(this, ContactActivity.class); +ActivityOptionsCompat.makeSceneTransitionAnimation(Activity activity,Pair... sharedElements); +ActivityCompat.startActivity(this, intent, option.toBundle()); +``` + +或 + +```java +Intent intent = new Intent(this, ContactActivity.class); +ActivityOptionsCompat.makeSceneTransitionAnimation(Activity activity, (View)sharedElement, String transitionName); +ActivityCompat.startActivity(this, intent, option.toBundle()); +``` + +##用 Transition 动画创建弧形 Motion + +在目标 Activity 中(在下面的例子中命名为 ContactActivity),需要通过以下代码设置正确的启动 Transition: + +```java + + @Bind(R.id.activity_contact_rl_container) RelativeLayout mRlContainer; + @Bind(R.id.activity_contact_fab) FloatingActionButton mFab; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + setupEnterAnimation(); + ... + } + + private void setupEnterAnimation() { + Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.change_bound_with_arc); + transition.setDuration(300); + getWindow().setSharedElementEnterTransition(transition); + transition.addListener(new Transition.TransitionListener() { + @Override + public void onTransitionStart(Transition transition) { + + } + + @Override + public void onTransitionEnd(Transition transition) { + animateRevealShow(mRlContainer); + } + + @Override + public void onTransitionCancel(Transition transition) { + + } + + @Override + public void onTransitionPause(Transition transition) { + + } + + @Override + public void onTransitionResume(Transition transition) { + + } + }); + } +``` + +```xml + + + + + + + +``` + +通过代码,我们将 xml 中声明的 Transition 初始化,设置其时长,并把它设为 SharedElementEnterTransition。然后就是最有趣的部分 - 组合实现动画,为此,我们必须实现 TransitionListener。在 onTransitionEnd() 回调方法中我们需要启动水波纹动画,在 xml 中我们声明了同时使用 changeBounds 和 arcMotion 的 transitionset,changeBounds 捕获两个 Activity 中目标 View 的边界,并显示动画;arcMotion 负责创建弧形弯曲路径。此外,我们还声明 minimumHorizontalAngle 和 minimumVerticalAngle 保证两点间的曲率。 + +在 onTransitionEnd() 回调中我们使用了 animateRevealShow() 方法,也就是下文要讲的内容: + +##第二部:水波纹动画 + +为了实现整个动画效果,我们先创建水波纹动画,然后淡入布局: + +```java +private void animateRevealShow(final View viewRoot) { + int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2; + int cy = (viewRoot.getTop() + viewRoot.getBottom()) / 2; + GUIUtils.animateRevealShow(this, viewRoot, mFab.getWidth() / 2, R.color.accent_color, + cx, cy, new OnRevealAnimationListener() { + @Override + public void onRevealHide() { + + } + + @Override + public void onRevealShow() { + initViews(); + } + }); +} + +//GUIUtils method: + +public static void animateRevealShow(final Context ctx, final View view, final int startRadius, + @ColorRes int color, int x, int y, OnRevealAnimationListener listener) { + float finalRadius = (float) Math.hypot(view.getWidth(), view.getHeight()); + Animator anim = ViewAnimationUtils.createCircularReveal(view, x, y, startRadius, finalRadius); + anim.setDuration(ctx.getResources().getInteger(R.integer.animation_duration)); + anim.setStartDelay(80); + anim.setInterpolator(new FastOutLinearInInterpolator()); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + view.setBackgroundColor(ContextCompat.getColor(ctx, color)); + } + + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.VISIBLE); + if(listener != null) { + listener.onRevealShow(); + } + } + }); + anim.start(); +} +``` + +在第一个 animateRevealShow() 方法中我们必须得到 centerX 和 centerY,然后调用 GUIUtils 的 animateRevealShow() 方法跳过重复代码,下面是对该方法的解释: + +context 用于将 @ColorRes 注解的值转换为颜色对应的常量以及从资源文件中读取动画的持续时间。centerX, centerY parameter, rootView 用于显示水波纹动画,自定义 Listener 用于 AnimatorListener 与 Activity 间的通信。当动画结束,通知 Listener 淡入 View,下面是用于淡入 View 的 initViews() 方法: + +```java +private void initViews() { + new Handler(Looper.getMainLooper()).post(() -> { + Animation animation = AnimationUtils.loadAnimation(this, android.R.anim.fade_in); + animation.setDuration(300); + mLlContainer.startAnimation(animation); + mIvClose.startAnimation(animation); + mLlContainer.setVisibility(View.VISIBLE); + mIvClose.setVisibility(View.VISIBLE); + }); +} +``` + +mLLContainer 和 mIvClose 是用于显示关闭按钮的 LinearLayout 及 ImageView。 + +##第三部:返回 Activity + +如你所见,整个动画的实现涉及多方面的处理。当我们点击关闭按钮,或者按下后退按钮,意味着要结束 ContactActivity,并以淡出效果显示水波纹动画,返回共享元素 Transition。第二步 Android 的框架层会帮我们完成,所以不用太在意,第一步才是我们真正要实现的,也就是下面解释的部分: + +重载 Activity 中的 onBackPressed() 方法: + +```java +@Override +public void onBackPressed() { + GUIUtils.animateRevealHide(this, mRlContainer, R.color.accent_color, mFab.getWidth() / 2, + new OnRevealAnimationListener() { + @Override + public void onRevealHide() { + backPressed(); + } + + @Override + public void onRevealShow() { + + } + }); +} + +//GUIUtils + +public static void animateRevealHide(final Context ctx, final View view, @ColorRes int color, + final int finalRadius, OnRevealAnimationListener listener) { + int cx = (view.getLeft() + view.getRight()) / 2; + int cy = (view.getTop() + view.getBottom()) / 2; + int startRadius = view.getWidth(); + Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, startRadius, finalRadius); + anim.setInterpolator(new FastOutLinearInInterpolator()); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + view.setBackgroundColor(ContextCompat.getColor(ctx,color)); + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if(listener != null) { + listener.onRevealHide(); + } + view.setVisibility(View.INVISIBLE); + } + }); + anim.setDuration(ctx.getResources().getInteger(R.integer.animation_duration)); + anim.start(); +} +``` + +点击后退按钮后,必须隐藏水波纹动画。因此 startRadius 为 View 的宽度,动画结束时 raidus 就变为 FAB 的宽度除2。当动画结束,需要通过 OnRevealAnimationListener 通知 Activity 调用 super.onBackPressed(),之后通过该弧形路径动画显示 MainActivity 的 FAB。 + +##结论 + +Lollipop 给我们提供了许多工具,使我们可以利用 motion 和 Transition 实现许多酷炫的 UI 组件,虽说这只支持 30% 的设备,但在未来这个数字会越来越大,让越来越多人注意到 Material Design。 + +感谢阅读! \ No newline at end of file diff --git "a/issue-43/\351\200\232\350\277\207CoordinatorLayout\347\232\204Behavior\346\213\246\346\210\252\344\270\200\345\210\207.md" "b/issue-43/\351\200\232\350\277\207CoordinatorLayout\347\232\204Behavior\346\213\246\346\210\252\344\270\200\345\210\207.md" new file mode 100644 index 00000000..37c8f7c7 --- /dev/null +++ "b/issue-43/\351\200\232\350\277\207CoordinatorLayout\347\232\204Behavior\346\213\246\346\210\252\344\270\200\345\210\207.md" @@ -0,0 +1,235 @@ +通过CoordinatorLayout的Behavior拦截一切 +--- + +> * 原文链接 : [Intercepting everything with CoordinatorLayout Behaviors](https://medium.com/google-developers/intercepting-everything-with-coordinatorlayout-behaviors-8c6adc140c26#.i6alrxteu) +* 原文作者 : [Ian Lake](https://medium.com/@ianhlake) +* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载! +* 译者 : [chaossss](https://github.com/chaossss) +* 校对者: [chaossss](https://github.com/chaossss) +* 状态 : 完成 + + + +![](https://cdn-images-1.medium.com/max/600/1*voovH-sZjps4amAdRiwRuw.png) + +如果你不研究 CoordinatorLayout,那你在探索 Android Design Support Library 的路上肯定不会走太远 - 因为 Android Design Support Library 中大多数 View 都需要 CoordinatorLayout。但是为什么呢?CoordinatorLayout 自身不需要完成太多的工作:将它与 Android 标准 UI 框架结合使用,它的作用和 FrameLayout 区别不大,那它为什么能提供那么多酷炫的效果呢?答案是:CoordinatorLayout.Behavior。通过将 CoordinatorLayout.Behavior 绑定到 CoordinatorLayout 中的子元素上,你就可以拦截点击事件,窗口插入,测量,布局,还有嵌套滚动。可以说 Android Design Support Library 大多数酷炫的效果都是通过 Behavior 完成的。 + +##创建 Behavior + +自定义 Behavior 很简单,创建 Behavior 子类就可以了: + +```java +public class FancyBehavior + extends CoordinatorLayout.Behavior { + /** + * Default constructor for instantiating a FancyBehavior in code. + */ + public FancyBehavior() { + } + /** + * Default constructor for inflating a FancyBehavior from layout. + * + * @param context The {@link Context}. + * @param attrs The {@link AttributeSet}. + */ + public FancyBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + // Extract any custom attributes out + // preferably prefixed with behavior_ to denote they + // belong to a behavior + } +} +``` + +注意类需要绑定的泛型类型,不妨创建可以绑定到任意 View 上的 FancyBehavior;如果你想 Behavior 只能使用到某一种特定的 View 上,可以这样: + +```java +public class FancyFrameLayoutBehavior + extends CoordinatorLayout.Behavior +``` + +通过 Behavior.setTag()/Behavior.getTag() 可以保存临时数据,onSaveInstanceState()/onRestoreInstanceState() 可以保存 Behavior 相关的实例状态。虽说我建议你创建自己的 Behavior,但这些方法让你能够创建状态性 Behavior。 + +##关联 Behavior + +当然,Behavior 不能单干 - 它需要绑定到 CoordinatorLayout 的子元素才能被使用,下面是关联的三种办法。 + +###通过代码绑定 + +你可能觉得 Behavior 是 CoordinatorLayout 给每个 View 添加的某种额外属性/其他的东西,但如果我告诉你 Behavior 是每个 View 的 LayoutParams 存储的内容,请不要太惊讶 - 这也是 Behavior 必须关联到 CoordinatorLayout 子元素上的原因,因为只有它们才有 LayoutParams 中对应的属性。 + +```java +FancyBehavior fancyBehavior = new FancyBehavior(); +CoordinatorLayout.LayoutParams params = + (CoordinatorLayout.LayoutParams) yourView.getLayoutParams(); +params.setBehavior(fancyBehavior); +``` + +在这种情况下,你会使用默认构造方法,当然,你也可以给构造方法设置任何你想要的参数 - 反正代码在手,天下我有嘛。 + +###通过 xml 绑定 + +不过不得不说,每次都用代码去完成绑定的工作有一丢丢麻烦,就像大多数自定义 LayoutParams,我们也有相应的 +layout_ 属性去设置 Behavior: + +```xml + +``` + +此时会调用 FancyBehavior(Context context, AttributeSet attrs) 构造方法,也就意味着可以声明任意自定义属性,然后在代码中获得这些属性,如果你想通过 xml 自定义 Behavior 的功能,这个办法就变得很棒了。 + +> Note: 与使用 layout_ 类似,你也可以使用 behavior_ 指定 Behavior 使用的属性。 + +###自动绑定 + +如果你创建了需要自定义 Behavior 的自定义 View,那你可能想让该 View 绑定默认的 Behavior,而不用在每次使用时在代码或 xml 中声明。为了实现这个特性,代码需要改成下面这样: + +```java +@CoordinatorLayout.DefaultBehavior(FancyFrameLayoutBehavior.class) +public class FancyFrameLayout extends FrameLayout { +} +``` + +这样就会自动绑定 Behavior 了,相当于 layout_behavior 默认设置为 DefaultBehavior。 + +##拦截点击事件 + +一旦自定义 Behavior 开发完成,就可以用它完成一些任务了,例如拦截点击事件。 + +不使用 CoordinatorLayout,总是需要创建 ViewGroup 的子类才能得到点击事件。但有了 CoordinatorLayout,就能在 Behavior 里调用 onInterceptTouchEvent() 以控制 CoordinatorLayout 的 onInterceptTouchEvent(),使 Behavior 能拦截点击事件。通过返回 true,Behavior 就能获取所有点击事件,这就是 SwipeDismissBehavior 的原理。 + +在 blocksInteractionBelow() 方法中返回 true,就会阻塞所有事件传递的交互,拦截所有事件。当然了,你可能希望得到一些视觉上的信号让你知道交互产生的事件都被阻塞了(最起码要让用户知道 App 崩溃了) - 这也是 blocksInteractionBelow() 的默认功能依赖于 getScrimOpacity() 的值的原因 - getScrimOpacity() 方法返回一个非 0 的值,以在 View 上绘制覆盖颜色(getScrimColor() 方法返回对应的颜色,默认是黑色),而且一下子禁止了所有点击事件。很方便。 + +##拦截窗口插入 + +我先假设你读过“Why would I want to fitsSystemWindows”,在博文中我们讨论了 fitsSystemWindows 到底干了啥,但说穿了他就是让你得到避免在系统窗口下绘制的窗口插入(例如状态栏和导航栏)。如果 fitsSystemWindows=“true”,那么任何绑定的 Behavior 都会调用 onApplyWindowInsets(),让 Behavior 具有比 View 更高的优先级。 + +> Note: 大多数情况下,Behavior 不会消耗所有的窗口插入,窗口插入应该通过 ViewCompat.dispatchApplyWindowInsets() 传递,以确保所有子 View 都有机会接触窗口插入。 + +##拦截 Measurement 和 Layout + +Measurement 和 Layout 是 Android 绘制机制的关键部分,这也意味着 Behavior 作为一切的拦截者,能够通过 onMeasureChild() 和 onLayoutChild() 回调先于 CoordinatorLayout 进行 measurement 和 layout 。 + +例如,让 Behavior 接受任意类型的 ViewGroup,并添加 maxWidth: + +```java +/* + * Copyright 2015 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package com.example.behaviors; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.design.widget.CoordinatorLayout; +import android.util.AttributeSet; +import android.view.ViewGroup; + +import static android.view.View.MeasureSpec; + +/** + * Behavior that imposes a maximum width on any ViewGroup. + * + *

Requires an attrs.xml of something like + * + *

+ * <declare-styleable name="MaxWidthBehavior_Params">
+ *     <attr name="behavior_maxWidth" format="dimension"/>
+ * </declare-styleable>
+ * 
+ */ +public class MaxWidthBehavior extends CoordinatorLayout.Behavior { + private int mMaxWidth; + + public MaxWidthBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.MaxWidthBehavior_Params); + mMaxWidth = a.getDimensionPixelSize( + R.styleable.MaxWidthBehavior_Params_behavior_maxWidth, 0); + a.recycle(); + } + + @Override + public boolean onMeasureChild(CoordinatorLayout parent, V child, + int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + if (mMaxWidth <= 0) { + // No max width means this Behavior is a no-op + return false; + } + int widthMode = MeasureSpec.getMode(parentWidthMeasureSpec); + int width = MeasureSpec.getSize(parentWidthMeasureSpec); + + if (widthMode == MeasureSpec.UNSPECIFIED || width > mMaxWidth) { + // Sorry to impose here, but max width is kind of a big deal + width = mMaxWidth; + widthMode = MeasureSpec.AT_MOST; + parent.onMeasureChild(child, + MeasureSpec.makeMeasureSpec(width, widthMode), widthUsed, + parentHeightMeasureSpec, heightUsed); + // We've measured the View, so CoordinatorLayout doesn't have to + return true; + } + + // Looks like the default measurement will work great + return false; + } +} +``` + +泛型 Behavior 固然好用,但你要记住,不是所有 Behavior 都应该是泛型的。 + +##理解 View 间依赖 + +上述所有功能只需要一个 View,但 Behavior 的魔力真正来源于在 View 间建立以来 - 例如,当另一个 View 发生改变,Behavior 获得回调,基于外部条件改变其功能。 + +Behavior 可以通过两种方式依赖于 View:当一个 View 被锚定到其他 View 上(被实现的依赖)或在 layoutDependsOn() 方法中返回 true。 + +使用 CoordinatorLayout 的 layout_anchor 属性就可以完成锚定。此外,再使用 layout_anchorGravity 属性,你可以有效地将两个 View 的位置绑定在一起。例如,将 FAB 锚定到 AppBarLayout 中,此时 FloatingActionButton.Behavior 会通过隐式依赖,在 AppBarLayout 滚动到屏幕可见范围之外时隐藏自身。 + +不管怎样,Behavior 都会在依赖的 View 被移除时获得 onDependentViewRemoved() 回调,而且只要依赖 View 发生了改变,onDependentViewChanged() 回调也会被触发。(例如改变自身大小,或者改变位置) + +将 View 绑定在一起的能力就是 Design Library 实现这么多酷炫效果的秘密 - 举例来说吧,FAB 和 Snackbar 的交互,FAB 的 Behavior 依赖于添加到 CoordinatorLayout 中的 Snackbar,通过调用 onDependentViewChanged() 回调就可以将 FAB 移动到 Snackbar 的上面,避免覆盖。 + +> Note: 当你添加依赖,View 总会在被依赖 View 被放置后被放置,无论其布局关系如何。 + +##嵌套滚动 + +对于嵌套滚动,下面几件事你需要记住: +1. 不需要在 NestedScrollView 中声明依赖,因为 CoordinatorLayout 的每一个子元素都能够获得 NestedScrollView 中的滚动事件 +2. NestedScrollView 不仅仅能应用于 CoordinatorLayout 的直接子元素,而能应用到 CoordinatorLayout 中的任意 View 上(CoordinatorLayout 的子布局的子布局的子布局的子布局……) +3. 虽说我把它叫做嵌套滚动,但实际上它能滚动和挥动 + +在 onStartNestedScroll() 方法中处理你感兴趣的嵌套滚动事件吧,你能在这得到滚动的坐标轴(例如水平和垂直坐标轴 - 不用在意到底在哪个方向发生了滚动),为了获得该方向上随后的滚动事件,必须返回 true。 + +onStartNestedScroll() 返回 true 后,嵌套滚动会按如下步骤运行: + +- onNestedPreScroll() 在 ScrollView 获得滚动事件前被调用,允许 Behavior 消耗一部分或所有滚动事件(最后被消耗的滚动事件是 int[] out 参数,也就是你能得到你消耗了什么事件的参数) +- onNestedScroll() 在滚动 View 滚动时被调用 - 你可以得到 View 已经滚动了多远以及还有多少没有滚动。 + +此外,还有与挥动操作等价的情况(即使挥动前的回调必须消耗所有非挥动的滚动事件,或不消耗任何一个)。 + +当嵌套滚动(挥动)结束,获得 onStopNestedScroll() 的回调调用,该回调标记了滚动的结束 - 同时期望下一次滚动发生前得到新的 onStartNestedScroll() 调用。 + +例如,你想在向下滚动时隐藏 FAB,向上滚动时显示 FAB - 实现这个功能将涉及 onStartNestedScroll() 和 onNestedScroll(),就像 ScrollAwareFABBehavior 中实现的那样。 + +###而这只是开始 + +Behavior 的每一个部分都很有趣,把它们组合起来就能对界面施行魔法,让界面变得酷炫。我强烈建议大家去看看 Design Library 的源码以发现更多更高级的 Behavior - Android SDK 搜索的 Chrome 拓展一直是我最爱的 Android 开源项目源码。 diff --git "a/issue-43/\351\201\277\345\205\215Android\345\272\224\347\224\250\345\206\267\345\220\257\345\212\250\351\227\256\351\242\230.md" "b/issue-43/\351\201\277\345\205\215Android\345\272\224\347\224\250\345\206\267\345\220\257\345\212\250\351\227\256\351\242\230.md" new file mode 100644 index 00000000..3638b952 --- /dev/null +++ "b/issue-43/\351\201\277\345\205\215Android\345\272\224\347\224\250\345\206\267\345\220\257\345\212\250\351\227\256\351\242\230.md" @@ -0,0 +1,182 @@ +避免Android应用冷启动问题 +--- + +> * 原文链接 : [Avoiding cold starts on Android](http://saulmm.github.io/avoding-android-cold-starts) +* 原文作者 : [Saúl Molinero](http://saulmm.github.io/) +* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载! +* 译者 : [chaossss](https://github.com/chaossss) +* 校对者: [chaossss](https://github.com/chaossss) +* 状态 : 完成 + + + +在过去的几周里,Android 开发者社区有人在讨论 Android 应用的冷启动问题(即启动应用时有一段时间屏幕不显示内容,背景全为白/黑),在本篇博文中,我将解释解决这个问题是否必要,以及如何解决它以使用户得到最好的使用体验。 + +本篇博文涉及的代码可以在 [Github](https://github.com/saulmm/onboarding-examples-android) + 看到。 + +##应用的冷启动问题 + +Colt McAnlis(Google 的一名工程师)再次开启了一个讨论帖来讨论有关 Splash/Launch 启动页面的正确用法,讨论的主题和 Cyril Mottier 之前开启的讨论相同,大体是说为什么我们在开发的时候应该避免使用 Splash 启动页。讨论的冲突点在于一部分人认为 Splash 启动页破坏了用户体验,增加了应用的体积等等…… + +在我看来,用户在使用应用时,应用的内容应该尽可能快呈现给用户,但当用户启动某个应用,Android 内核总会创建一个进程,使得屏幕不可避免地显示黑/白(取决于应用的 theme 或入口 Activity 的 theme)。 + +应用本身越复杂,或应用使用的 Application 类被重写过以需要完成更多的任务(如初始化数据分析,错误报告,等等……)时,这段时间会变得更长。 + +![](https://github.com/saulmm/OnboardingSample/blob/master/art/airbnb.gif?raw=true) + +Airbnb 在初始化时就会像上图那样留白 + +对用户来说,这样的界面显然不是他们想要看到的。假如应用的加载时间很长,我们可以通过 placeholder 用一些内容去填充它,或者显示 Logo 以加强品牌印象。 + +![](https://github.com/saulmm/OnboardingSample/blob/master/art/aliexpress.gif?raw=true) + +AliExpress 在初始化时会显示其 Logo + +##你的新帮手,windowBackground + +就像我们之前讨论的,在进程处于加载状态时,WindowManager 显示的 Window 由应用的 theme 决定其显示内容,准确地说,是由 android:windowBackground 的值决定的。就像 [Ian Lake](https://plus.google.com/+AndroidDevelopers/posts/Z1Wwainpjhd) 的这篇博文所提到的,如果我们为该属性设置 元素,该元素使用 MainActivity 的背景颜色,并在屏幕中间显示一个 Pizza 图像,可以实现下面的效果: + +![](https://github.com/saulmm/OnboardingSample/blob/master/art/simple.gif?raw=true) + +```xml + + + + + + + + +``` + +这里要将 的 android:opacity 属性设置为 opaque,使 为不透明的,要不然会出现一些问题,请读者记住这一点。此外,Activity 的布局背景应该填充了某个颜色,不然的话 会留在你的 Activity 中。 + +```xml + + + + + +``` + +##自定义启动页 + +利用 windowBackground 属性可以给用户更好的体验。如果应用很复杂,那么可以显示一个独特的 Activity 来完成登录操作或进行选择操作,利用图片和动画可以实现下面这个酷炫的效果: + +![](https://github.com/saulmm/OnboardingSample/blob/master/art/center.gif?raw=true) + +该动画在竖直方向上移动 ImageView,该 ImageView 包含了 元素。 + +```java +ViewCompat.animate(logoImageView) + .translationY(-250) + .setStartDelay(STARTUP_DELAY) + .setDuration(ANIM_ITEM_DURATION).setInterpolator( + new DecelerateInterpolator(1.2f)).start(); +``` + + +从效果图可以看到,ImageView 要稍微高于屏幕的中心点,实际位置会受 SystemBar 的大小影响,在这里我为它设置了 12dp 的顶部间距,差不多是状态栏高度的一半。 + +```java + +``` + +##自定义 placeholder + +使用 可以创建 placeholder 以显示 MainActivity 的内容,例如,可以通过 模仿 Toolbar。 + +![](https://github.com/saulmm/OnboardingSample/blob/master/art/toolbar_placeholder.png?raw=true) + +```xml + + + + + + + + + + + + + + +``` + +其中,第二个 模仿真正要被显示到屏幕上的 Toolbar,甚至我们可以将它的高度设置为 Toolbar 的高度(宽度小于状态栏一点点),应用一些酷炫的动画到 Toolbar,给用户更好的体验,效果图: + +![](https://github.com/saulmm/OnboardingSample/blob/master/art/placeholder.gif?raw=true) + +```java +private void collapseToolbar() { + int toolBarHeight; + TypedValue tv = new TypedValue(); + getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true); + toolBarHeight = TypedValue.complexToDimensionPixelSize( + tv.data, getResources().getDisplayMetrics()); + + ValueAnimator valueHeightAnimator = ValueAnimator + .ofInt(mContentViewHeight, toolBarHeight); + + valueHeightAnimator.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + ViewGroup.LayoutParams lp = mToolbar.getLayoutParams(); + lp.height = (Integer) animation.getAnimatedValue(); + mToolbar.setLayoutParams(lp); + } + }); + + valueHeightAnimator.start(); + valueHeightAnimator.addListener( + new AnimatorListenerAdapter() { + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + + // Fire recycler animator + mAdapter.addAll(ModelItem.getFakeItems()); + + // Animate fab + ViewCompat.animate(mFab).setStartDelay(600) + .setDuration(400).scaleY(1).scaleX(1).start(); + + } + }); +} +``` \ No newline at end of file diff --git "a/issue-45/IndeterminateProgressbar\350\247\243\346\236\220-Part 3.md" "b/issue-45/IndeterminateProgressbar\350\247\243\346\236\220-Part 3.md" new file mode 100644 index 00000000..5674d6c4 --- /dev/null +++ "b/issue-45/IndeterminateProgressbar\350\247\243\346\236\220-Part 3.md" @@ -0,0 +1,224 @@ +IndeterminateProgressbar解析-Part 3 +--- + +> * 原文链接 : [Indeterminate – Part 3](https://blog.stylingandroid.com/indeterminate-part-3/) +* 原文作者 : [Mark Allison](blog.stylingandroid.com) +* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载! +* 译者 : [chaossss](https://github.com/chaossss) +* 校对者: [chaossss](https://github.com/chaossss) +* 状态 : 完成 + + + +Indeterminate ProgressBars are a useful tool for communicating to our users that an operation is in progress when we cannot predict how long it is likely to take. Previously on Styling Android we’ve covered how to create a backwardly compatible approximation of the material styled horizontal indeterminate ProgressBar but we haven’t looked at the circular form – in this series we’ll create an approximation of the material circular indeterminate ProgressBar which will be backwardly compatible to API 11 (Honeycomb). + +Previously we’ve taken a deep dive in to the Lollipop+ implementation of the material circular indeterminate drawable, and now we’ll turn our attention to actually creating our own. + +First we’ll look at the actual Drawable component (which is analogous to the VectorDrawable we looked at in AOSP) that is responsible for actually drawing the circular component (or a section of one, at least). We can’t use VectorDrawable because it’s Lollipop+ only and VectorDrawableCompat isn’t ready for serious use (at the time of writing). But we can use good, old-fashioned Canvas and Paint to do what we need. + +Let’s first of all look at how we construct it: + +```java +public class IndeterminateDrawable extends Drawable implements Animatable { + private static final int STROKE_WIDTH = 6; + private static final int PADDING = 3; + + private final Paint paint; + private final RectF bounds; + private final float padding; + private float startAngle; + private float endAngle; + private float rotation; + private Animator animator; + + public static IndeterminateDrawable newInstance(Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + float strokeWidthPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, STROKE_WIDTH, displayMetrics); + float paddingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, PADDING, displayMetrics); + Paint paint = createPaint(fetchAccentColour(context), strokeWidthPx); + RectF bounds = new RectF(); + float padding = strokeWidthPx / 2 + paddingPx; + IndeterminateDrawable drawable = new IndeterminateDrawable(paint, bounds, padding); + drawable.createAnimator(); + return drawable; + } + + @ColorInt + private static int fetchControlColour(Context context) { + TypedArray typedArray = context.obtainStyledAttributes(0, new int[]{R.attr.colorControlActivated}); + try { + return typedArray.getColor(0, 0); + } finally { + typedArray.recycle(); + } + } + + static Paint createPaint(@ColorInt int colour, float strokeWidth) { + Paint paint = new Paint(); + paint.setColor(colour); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(strokeWidth); + paint.setAntiAlias(true); + paint.setStrokeCap(Paint.Cap.SQUARE); + return paint; + } + + IndeterminateDrawable(Paint paint, RectF bounds, float padding) { + this.paint = paint; + this.bounds = bounds; + this.padding = padding; + } + + private void createAnimator() { + startAngle = 0; + endAngle = 270; + animator = new AnimatorSet(); + } + . + . + . +} +``` + +First we determine the pixel values for the stroke width and internal padding of the drawable to closely match those used my the actual material drawable. It is important to convert these from DIP to PX values based upon the DisplayMetrics at runtime to ensure that we can consistency across displays with differing densities. + +Next we create a Paint object with the necessary colour and stroke width which will be used to actually draw the section of the circle. We can retrieve the correct colour from the theme by performing the appropriate lookup (we’re using the Design support library so this will honour the values in our Theme). + +It’s important to turn on anti-aliasing on the Paint otherwise we’ll get some lovely jaggy edges. Also we set a square cap on the path which mimics what is set for the pathData in the material VectorDrawable that we looked at earlier in the series. + +We also create a RectF object which we’ll use later on. Both the Paint and RectF objects will be used in the onDraw() method and we create them in advance and re-use them each time onDraw() is called to avoid any object creation and keep our onDraw() implementation as lean and clean as possible to ensure that we don’t drop any frames in the animations. + +Next we actually create the IndeterminateDrawable instance itself before calling createAnimator() to construct the animations. This is currently a dummy implementation which just sets some static default values. We’ll get the the Animators in due course! + +Because we subclass Drawable, we need to override some abstract methods: + +```java +public class IndeterminateDrawable extends Drawable implements Animatable { + . + . + . + @Override + public void setAlpha(int alpha) { + paint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + paint.setColorFilter(colorFilter); + } + + @Override + public int getOpacity() { + return paint.getAlpha(); + } + . + . + . +} +``` + +We just need to apply changes to our Paint object as needed. + +Because we implement Animatable we also need to override some methods to control the animation state: + +```java +public class IndeterminateDrawable extends Drawable implements Animatable { + . + . + . + @Override + public void start() { + animator.start(); + } + + @Override + public void stop() { + animator.end(); + } + + @Override + public boolean isRunning() { + return animator.isRunning(); + } + . + . + . +} +``` + +Once again we delegate these only to the Animator this time. + +We now need to implement some setters which will be used by the Animators later to change the parameters of the Drawable in order to animate it: + +```java +public class IndeterminateDrawable extends Drawable implements Animatable { + . + . + . + public void setStartAngle(float startAngle) { + this.startAngle = startAngle; + invalidateSelf(); + } + + public void setEndAngle(float endAngle) { + this.endAngle = endAngle; + invalidateSelf(); + } + + public void setRotation(float rotation) { + this.rotation = rotation; + invalidateSelf(); + } + . + . + . +} +``` + +If you’ve read the previous article in this series these should be fairly self-explanatory – we are going to use animators to dynamically change the start and end of the sweep, and also rotate the whole thing. + +Finally we have our onDraw(): + +```java +public class IndeterminateDrawable extends Drawable implements Animatable { + . + . + . + @Override + public void draw(Canvas canvas) { + bounds.set(padding, padding, canvas.getWidth() - padding, canvas.getHeight() - padding); + canvas.drawArc(bounds, rotation + startAngle, endAngle - startAngle, false, paint); + } +} +``` + +This is really simple! First we populate the RectF bounds object that we created earlier with the dimensions of the Canvas taking in to account the internal padding. Then we draw an arc within these bounds and set the start and end angles according to the values set. We also add the rotation to the start angle and this will effectively rotate everything. This will get rendered using the Paint object that we set up earlier. + +Actually using this in our Activity is pretty easy. We include a View in our layout (I’ve used an ImageView in the example code) and then construct the IndeterminateDrawable, and start the animations in exactly the same way as you would start and AnimatedVectorDrawable: + +```java +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + ImageView imageView = (ImageView) findViewById(R.id.image); + IndeterminateDrawable drawable = IndeterminateDrawable.newInstance(this); + imageView.setImageDrawable(drawable); + drawable.start(); + } +} +``` + +If we run this we can see that we get a static three quarters of a circle drawn because of the default values that we use in place of the animator construction: + +![](https://i2.wp.com/blog.stylingandroid.com/wp-content/uploads/2016/01/static_IndeterminateDrawable.png?resize=300%2C225&ssl=1) + +Just before we finish it is worth pointing out that there are some known issues with drawArc in the Skia library which Android uses to render things. Eugenio Marletti has [described these in detail](https://medium.com/@workingkills/the-mysterious-case-of-who-killed-arcs-on-android-9155f49166b8#.2uznv5mun) and his post is well worth a read. However, we’re only drawing a single arc here so I’m not bothered about the slight rendering discrepancies – they simply won’t be apparent in this case. + +In the next article we’ll turn our attention to the Animators which will bring this to life! + +The source code for this article is available [here](https://github.com/StylingAndroid/Indeterminate/tree/Part3). \ No newline at end of file diff --git "a/issue-45/\344\275\277\347\224\250\345\217\215\345\260\204\345\210\260\345\272\225\344\274\232\345\257\271\346\200\247\350\203\275\351\200\240\346\210\220\345\244\232\345\244\247\345\275\261\345\223\215\357\274\237.md" "b/issue-45/\344\275\277\347\224\250\345\217\215\345\260\204\345\210\260\345\272\225\344\274\232\345\257\271\346\200\247\350\203\275\351\200\240\346\210\220\345\244\232\345\244\247\345\275\261\345\223\215\357\274\237.md" new file mode 100644 index 00000000..6f57c5e9 --- /dev/null +++ "b/issue-45/\344\275\277\347\224\250\345\217\215\345\260\204\345\210\260\345\272\225\344\274\232\345\257\271\346\200\247\350\203\275\351\200\240\346\210\220\345\244\232\345\244\247\345\275\261\345\223\215\357\274\237.md" @@ -0,0 +1,99 @@ +使用反射到底会对性能造成多大影响? +--- + +> * 原文链接 : [How Slow is Reflection in Android?](http://blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html) +* 原文作者 : [Anton Krasov](http://blog.nimbledroid.com/) +* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载! +* 译者 : [chaossss](https://github.com/chaossss) +* 校对者: [chaossss](https://github.com/chaossss) +* 状态 : 完成 + + + +(So far we’ve analyzed a lot of apps and discovered a handful of issues that significantly slow down many apps. Starting from this post, we’ll describe these issues one by one.) + +Reflection is, of course, an extremely useful aspect of Java and Android development. Yet it turns out that reflection can very often be the source of significant slowdown within an Android application. Perhaps the most intuitive way of understanding this is going through a couple of real-life examples. + +##Two Real-world Examples + +Our first example involves NYTimes Android app. With the help of NimbleDroid, our friends at NYTimes found out that the reflective type adapters in Gson cost their Android app a 700ms startup delay. They eventually fixed this delay with manually written custom type adapters. + +Our second example involves Photobucket, a large photo-sharing platform. Here, reflection again causes a big bottleneck. + +![](http://blog.nimbledroid.com/assets/slow-android-reflection/com.photobucket.android-iricle-graph-top.png) +660ms for call com.photobucket.api.client.jersey.UserClient constructor + +We see that the com.photobucket.api.client.jersey.UserClient constructor takes an entire 660ms to run. Looking further into the icicle graph, we see that the reason for such a lag lies in reflection. Check this out: + +![](http://blog.nimbledroid.com/assets/slow-android-reflection/com.photobucket.android-iricle-graph-bottom.png) + +lots of reflection calls, like: java.lang.Class.getGenericInterfaces + +Note that the getGenericInterfaces() method returns the types of the interfaces that this class directly implements. Here, it is invoked 5 times and takes ~81ms. Sure, on the surface this may not seem like much, but altogether, use of the method is causing ~600ms of start time delay. Let’s take a deeper look at why this is taking so long. + +It turns out that this library allows developers to configure a REST client with annotations. The issue is that the library doesn’t process the annotations during build time, but instead parses and creates the REST client during runtime (with the help of reflection). From a performance point of view, this is catastrophic. + +##Micro-benchmarks + +We’ve created a simple test to quantify how slow reflection is. + +We will work with the android.app.Activity class and repeat operations 10,000 times, like this: + +```java +Class clazz = android.app.Activity.class; +for (int i = 0; i < 10000; i++) { + clazz.getFields(); +} +``` + +We’ve also set up two tests that include creating objects (of the type DummyItem, an empty dummy class) to look at the overhead purely caused by reflection. Here’s an example: + +```java +try { + for (int i = 0; i < 1_000_000; i++) { + DummyItem.class.newInstance(); + } +} catch (InstantiationException e) { + e.printStackTrace(); +} catch (IllegalAccessException e) { + e.printStackTrace(); +} +``` + +And here are our results (all numbers are in ms, measured on our personal devices with real usage to make the results more faithful to real world): + +||NEXUS 5 (6.0) ART|GALAXY S5 (5.0) ART|GALAXY S3 mini (4.1.2) Dalvik| +|getFields|1108|1626|27083| +|getDeclaredFields|347|951|7687| +|getGenericInterfaces|16|23|2927| +|getGenericSuperclass|247|298|665| +|makeAccessible|14|147|449| +|getObject|21|167|127| +|setObject|21|201|161| +|createDummyItems|312|358|774| +|createDummyItemsWithReflection 1332|6384|2891| + +It’s evident that reflection in Android is excruciatingly slow - compare the (1332ms, 6384ms, 2891ms) with reflection to the (312ms, 358ms, 774ms) without reflection. Interestingly, Android 5.0 ART on a more powerful device actually makes reflection much slower than Android 4.1 Dalvik on a less powerful device; only in Android 6.0 ART is the overhead reduced, but the overhead is still quite significant. + +##More real-world examples + +ActiveAndroid is another library that uses reflection. Let’s take a look at how it can affect start time by analyzing some real apps on the Play store: + +Here’s the Scribd app: + +![](http://blog.nimbledroid.com/assets/slow-android-reflection/Scribd.png) + +1093ms for call com.activeandroid.ActiveAndroid.initialize + +Myntra sees similar problems: + +![](http://blog.nimbledroid.com/assets/slow-android-reflection/Myntra.png) + +1421ms for call com.activeandroid.ActiveAndroid.initialize + +As you can see, the library requires more than a second to initialize. That’s a lot of time, especially taking into consideration that users expect an average app start time of 2s. + +To conclude, reflection in Android is really slow. To guarantee you offer users the smoothest experience possible, we recommend the following: + +> Recommendation: avoid using reflection (or libraries that use reflection) altogether. In particular, do not use reflective type adapters to serialize Java objects. \ No newline at end of file diff --git "a/issue-44/\345\256\211\345\215\223Binder\346\236\266\346\236\204\346\246\202\350\277\260.md" "b/issue-45/\345\256\211\345\215\223Binder\346\236\266\346\236\204\346\246\202\350\277\260.md" similarity index 100% rename from "issue-44/\345\256\211\345\215\223Binder\346\236\266\346\236\204\346\246\202\350\277\260.md" rename to "issue-45/\345\256\211\345\215\223Binder\346\236\266\346\236\204\346\246\202\350\277\260.md" diff --git "a/issue-45/\347\256\200\345\214\226\345\244\215\346\235\202\347\232\204\350\247\206\345\233\276\345\261\202\346\254\241.md" "b/issue-45/\347\256\200\345\214\226\345\244\215\346\235\202\347\232\204\350\247\206\345\233\276\345\261\202\346\254\241.md" new file mode 100644 index 00000000..e5691c66 --- /dev/null +++ "b/issue-45/\347\256\200\345\214\226\345\244\215\346\235\202\347\232\204\350\247\206\345\233\276\345\261\202\346\254\241.md" @@ -0,0 +1,90 @@ +简化复杂的视图层级 +--- + +> * 原文链接 : [Simplify Complex View Hierarchies](https://medium.com/google-developers/simplify-complex-view-hierarchies-5d358618b06f#.kuqg1wpi1) +* 原文作者 : [Aleks Haecky](https://medium.com/@alekshaecky) +* 译文出自 : [开发技术前线 www.devtf.cn](http://www.devtf.cn) +* 转载声明: 本译文已授权[开发者头条](http://toutiao.io/download)享有独家转载权,未经允许,不得转载! +* 译者 : [DroidWorkerLYF](https://github.com/DroidWorkerLYF) +* 校对者: [desmond1121](https://github.com/desmond1121) +* 状态 : 完成 + +App的核心是使用视图层级来创造用户界面和视觉体验。随着app的功能越来越丰富,视图层级会越来越复杂,并且成为性能问题的来源之一。最常见的表象是app卡顿,尤其是渲染复杂视图时。 + +简化或者重新布局你的视图层级可以提升app的性能表现,尤其是在低配设备和早期的Android版本上。额外的好处是,你的app会变得更容易维护。 + +**前期准备**阅读[通过移除无用的背景减少过度绘制](https://medium.com/google-developers/draw-what-you-see-and-clip-the-e11-out-of-the-rest-6df58c47873e#.d9t0874dv)这篇文章,来排除导致卡顿的这一常见原因,避免和视图层级带来的问题混淆在一起。 + +### 分析视图层级 +分析视图层级就是使用几个工具来定位并修复性能问题。所以,有时你只需要一个工具,有时则需要全部的工具一起使用来优化性能。 + +#### GPU呈现模式分析 + +1. [**运行GPU呈现模式分析**](http://developer.android.com/tools/performance/profile-gpu-rendering/index.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)**并且观察柱状图蓝色的部分。** 如果蓝色的部分过高并且导致柱状图高度超过了16ms的标志线,那就是你的app花费了大量时间来更新display list。在Android 6.0版本中增加了额外的颜色,表示Measure/Layout的浅绿色也容易超乎预期的耗时。导致这些的一个原因就是试图层级过于复杂了。当然这只能告诉你有问题,而不能告诉你问题出在哪里。让我们接着往下看。 + +#### 显示GPU视图更新 + +1. 在你的设备上**运行显示GPU视图更新工具**。在**开发者选项中**,找到**硬件加速渲染**然后打开**显示GPU视图更新**。 +2. 操作你的app。 +3. 在屏幕上的试图更新时会闪烁红色。如果你注意到屏幕上和正在更新的区域无关的视图闪烁,那很可能是两者之间在视图层级上相关联所以导致了视图被不正确的失效了。这样,你就可以关注在这些区域上来更快的查找到问题。 + +#### Hierarchy Viewer +这就是你要做大量工作的工具了 + +1. **开启** [**Hierarchy Viewer工具**](http://developer.android.com/tools/performance/hierarchy-viewer/index.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)。 +2. 找到你app中仍然有大量过度绘制的视图层级。考虑重新调整你的视图来减少过度绘制。 +3. 定位到视图层级复杂的区域,考虑如何能简化它。 +4. [**分析**](https://developer.android.com/tools/performance/hierarchy-viewer/profiling.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)视图层级中额外潜在的问题节点。 + +#### 使用lint深入挖掘问题 + +在布局文件上使用[lint](http://developer.android.com/tools/debugging/improving-w-lint.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)来搜寻对视图层级可能的优化。然而关于lint的好处需要另写一篇文章。 + +### 简化视图层级 +#### 移除对最终图像无用的视图 + +按照如下步骤来确定对最终呈现到屏幕上的图像无用的视图: + +1. 在Hierarchy Viewer中,从叶子节点像根节点查看视图层级。 +2. 点击每一个节点来查看屏幕当前的图像。在Layout View窗口查看视图是如何分层的 +3. 如果一个之前可见的视图被完全隐藏,那么你可能根本就不需要这个视图,如图1. +4. 在你的代码中移除被完全遮盖,从来不显示或者在屏幕区域外的视图。 + +![](https://github.com/DroidWorkerLYF/Translate/blob/master/Simplify-Complex-View-Hierarchies/1.png?raw=true) + +图1. 视图2和3被完全遮盖可以安全的移除。 + +#### 扁平化视图层级来减少嵌套 +1. 你的视图层级中有没有看起来类似图2的嵌套? +2. 当可行时,使用相对布局替代嵌套的线性布局来扁平化视图层级。查看[优化视图层级](http://developer.android.com/training/improving-layouts/optimizing-layout.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)这篇文章. + +![](https://github.com/DroidWorkerLYF/Translate/blob/master/Simplify-Complex-View-Hierarchies/2.png?raw=true) + +图2. 这个层次很深的视图层级可以变得扁平化来提升性能。 + +#### 减少视图的数量 + +1. 如果你的用户界面有很多简单的视图,你可以在不影响用户体验的前提下,像图3那样结合其中的一些。 +2. 所有的改变都会影响你呈现信息的方式当然设计上要做出权衡。不过要记住性能表现是你app成功与否最重要的一点,无论什么情况下,你都应该尽可能的选择优化它。 +3. 合并一些视图。比如:如果你减少字体和样式,就可以合并text view。 +4. 重新设计你的用户界面来使用更少的视图。 + +![](https://github.com/DroidWorkerLYF/Translate/blob/master/Simplify-Complex-View-Hierarchies/3.png?raw=true) + +图3. 合并视图的列子 + +#### 简化会导致多次layout的嵌套布局 + +像RelativeLayout这样的父视图,需要两次布局来最终确定子视图的位置。结果就是,他们的子视图也需要两次布局。当你再把这些父视图嵌套在一起时,每一个视图层级的布局的次数就会以指数级增长。 + +举个列子,RelativeLayout中嵌套一个ListView,ListView中嵌套一个GridView,GridView中嵌套一个view,这会导致8倍的布局次数,如图4. + +![](https://github.com/DroidWorkerLYF/Translate/blob/master/Simplify-Complex-View-Hierarchies/4.png?raw=true) + +- [RelativeLayout](http://developer.android.com/reference/android/widget/RelativeLayout.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog) +- [Linearlayouts](http://developer.android.com/reference/android/widget/LinearLayout.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)使用*measureWithLargestChild*方法 +- [GridView](http://developer.android.com/reference/android/widget/GridView.html?utm_campaign=app_series_complexviewhierarchies_012616&utm_source=medium&utm_medium=blog)使用gravity +- 继承自以上容器的自定义视图 +- 一些weights的使用也可以导致加倍的布局 + +使用任何以上的容器作为一个复杂视图的根节点,比如一个很深的子树的父节点,或者在布局中大量使用,都会影响性能。所以,多考虑如何在避免布局次数指数级增长的情况下达到相同的布局效果。比如,使用不设置gravity的gridview来替代relative layout作为根节点。 \ No newline at end of file