深入解析Android的自定义布局

原文链接: http://greenrobot.me/devpost/android-custom-layout/

深入解析Android的自定义布局_第1张图片

写在前面的话:

这篇文章是前Firefox Android工程师(现在 跳槽去Facebook了)   Lucas Rocha所写,文中对Android中常用的四种自定义布局方案进行了很好地分析,并结合这四种Android自定义布局方案所写的示例项目讲解了它们各自的优劣以及四种方案之间的比较。看完这篇文章,也让我对Android 自定义布局有了进一步的了解,于是趁着兴头,我把它翻译成中文, 原文链接在此。

 

只要你写过Android程序,你肯定使用过Android平台内建的几个布局—— RelativeLayout, LinearLayoutFrameLayout等等。 它们能帮助我们很好的构建Android UI。

这些内建的布局已经提供了很多方便的构件,但很多情况下你还是需要来定制自己的布局。

总结起来,自定义布局有两大优点:

  1. 通过减少view的使用和更快地遍历布局元素让你的UI显示更加有效率;
  2. 可以构建那些无法由已有的view实现的UI。

在这篇博文中,我将实现四种不同的自定义布局,并对它们的优缺点进行比较。它们分别是:composite view, custom composite view, flat custom view, 和 async custom views。

这些代码实现可以在我的github上的  android-layout-samples 项目里找到。这个app使用上面说到的四种自定义布局实现了相同的UI效果。它们使用  Picasso 来加载图片。这个app的UI只是twitter timeline的简化版本——没有交互,只有布局。

好啦,我们先从最常见的自定义布局开始吧:  composite view。

Composite View

Composite views (也被称为 compound views) 是众多将多个view结合成为一个可重用UI组件的方法中最简单的。这种方法的实现过程是这样的:

  1. 继承相关的内建的布局。
  2. 在构造函数里面填充一个  merge 布局。
  3. 初始化成员变量并通过  findViewById()指向内部view。
  4. 添加自定义的API来查询和更新view的状态。

TweetCompositeView code 就是一个 composite view。它继承于  RelativeLayout,并填充了  tweet_composite_layout.xml code 布局文件,最后向外界暴露了  update()方法来更新它在adapter code里面的状态。

Custom Composite View

上面提到的 TweetCompositeView 这种实现方式能满足大部分的情况。但是碰到某些情况就不灵了。假设你现在想要减少子视图的数量,让布局元素的便利更加有效。

这个时候我们可以回过头来看看,尽管 composite views 实现起来比较简单,但是使用这些内建的布局还是有不少的开销的——特别是  LinearLayout 和 RelativeLayout这种比较复杂的容器。由于Android平台内建布局的实现,在一次布局元素遍历中,系统需要处理许多布局的结合和子视图的多次测量—— LinearLayout的  layout_weight 的属性就是常见例子。

因此你可以为你的app量身定做一套子视图的计算和定位逻辑,这样的话你就可以极大的优化你的UI了。这种做法就是我接下来要介绍的  custom composite view.

顾名思义,一个 custom composite view 就是一个重写了 onMeasure() 和 onLayout() 方法的 composite view 。因此相比之前的composite view继承了  RelativeLayout,现在我们需要更进一步——继承更抽象的 ViewGroup。

TweetLayoutView code 就是通过这种技术实现的。注意现在这个实现不像 TweetComposiveView 继承了 LinearLayout ,这也就避免了  layout_weight code这个属性的使用了。

这个大费周折的过程通过ViewGroup’s 的 measureChildWithMargins() 方法和背后的  getChildMeasureSpec() 方法计算出了每个子视图的  MeasureSpec 。

TweetLayoutView 不能正确地处理所有可能的 layout 组合但是它也不必这样。我们肯定需要根据特定需求来优化我们的自定义布局,这种方式可以让我们写出简单高效的布局代码。

Flat Custom View

如你所见,custom composite views 可以简单地通过使用 ViewGroup 的API就可以实现了。大部分时候,这种实现是可以满足我们的需求的。

然而我们想更进一步的话——优化我们应用中的关键部分UI,比如  ListViews , ViewPager等等。如果我们把所有的  TweetLayoutView 子视图合并成一个单一的自定义视图然后统一管理会怎么样呢?这就是我们接下来要讨论的  flat custom view——参看下面的图片。

深入解析Android的自定义布局_第2张图片

左边为CUSTOM COMPOSITE VIEW ,右边是FLAT CUSTOM VIEW

 

flat custom view 就是一个完全自定义的 view ,它完全负责内部的子视图的计算,位置安排,绘制。所以它就直接继承了 View 而不是  ViewGroup

如果你想找找现实生活中app是否存在这样的例子,很简单——开启你手机“开发者模式”里面的 “显示布局边界”选项,然后打开 Twitter, Gmail, 或者 Pocket这些app,它们在列表UI里面都采用了 flat custom view。

使用 flat custom view最主要的好处就是可以极大地压缩app 的视图层级,进而可以进行更快的布局元素遍历,最终可以减少内存占用。

Flat custom view 可以给你最大的自由,就好像你在一张白纸上面作画。但是这样的自由是有代价的:你不能使用已有的那些视图元素了,比如  TextView 和  ImageView。没错,在  Canvas 上面 描绘文本 的确很简单,但要你实现  ellipsizing(就是对过长的文本截断)呢?同样, 在  Canvas 上面  描绘图片确很简单,但是如何 缩放呢?这些限制同样适用于touch events, accessibility, keyboard navigation等等。

所以使用flat custom view的底线就是:只将flat custom view应用于你的app的UI核心部分,其他的就直接依赖Android平台提供的view了。

TweetElementView code 就是 flat custom view。为了更容易的实现它,我创建了一个小小的自定义视图框架叫做 UIElement。你可以在   canvas code 这个包里找到它。

UIElement 提供了和Android平台类似的 measure/layout API 。它包含了没有图像界面的  TextView 和  ImageView ,这两个元素包含了几个必需的特性——分别参看  TextElement code和 ImageElement code 。它还拥有自己的 inflater code ,帮助从 布局资源文件 code里面实例化 UIElement  。

注意:  UIElement 还处于非常早期的开发阶段,所以还有很多缺陷,不过将来随着不断的改进 UIElement 可能会变得非常有用。

你可能觉得 TweetElementView 的代码看起来很简单,这是因为实际代码都在  TweetElement code里面——实际上 TweetElementView 扮演托管的角色 code。

TweetElement  里面的布局代码和 TweetLayoutView‘非常类似,但是它使用 Picasso 请求图片时却不一样 code ,因为 TweetElement  没有使用 ImageView

Async Custom View

总所周知,Android UI 框架时 单线程的 。 这样的单线程会带来一些限制。比如,你不能在主线程之外遍历布局元素——然而这对复杂、动态的UI是很有益处的。

假如你的app 在一个 ListView 中很布局比较复杂的条目(就像大多数社交app一样),那么你在滑动 ListView 就很有可能出现跳帧的现象,因为 ListView 需要为列表中即将出现的新内容计算它们的视图大小 code和布局 code。同样的问题也会出现在 GridViewsViewPagers等等。

如果我们可以在主线程之外的线程上面对那些还没有出现的子视图进行布局遍历是不是就可以解决上面的问题了?也就是说,在子视图上面调用  measure() 和 layout() 方法都不会占用主线程的时间了。

所以  async custom view 就是一个允许子视图布局遍历过程发生在主线程之外的实验,这个idea是受到Facebook的Paperteam  async node framework 这个视频激发所想到的。

既然我们在主线程之外永远接触不到Android平台的UI组件,因此我们需要一个API在不能直接接触到这个视图的前提下对这个视图的内容进行测量、布局。这恰恰就是  UIElement 框架提供给我的功能。

AsyncTweetView code 就是一个 async custom view。它使用了一个线程安全的  AsyncTweetElement code 工厂类 code 来定义它的内容。具体过程是一个  Smoothie 子项加载器 code 在一个后台线程上对暂时不可见的 AsyncTweetElement 进行创建、预测量和缓存(在内存里面,以便后来直接使用)。

当然在实现这个异步UI的过程中我还是妥协了一些,因为你不知道如何显示任意高度的布局占位符。比如,当布局异步传递过来的时候你只能在后台线程对它们的大小进行一次更改。因此当一个  AsyncTweetView 就要显示的时候却无法在内存里面找到合适的 AsyncTweetElement ,这个时候框架就会强制在主线程上面创建一个 AsyncTweetElement  code。

还有,预先加载的逻辑和内存缓存过期时间设置都需要比较好的实现来保证在主线程尽可能多地利用内存里面的缓存布局。比如,这个方案中使用 LRU 缓存 code 就不是一个明智的选择。

尽管还存在这些限制,但是使用 async custom view 的得到的初步结果还是很有前途的。当然我也会通过重构这个UIElement  框架和使用其他类别的UI在这个领域继续探索。让我们静观其变吧。

总结

在我们涉及到布局的时候,我们自定义的越深,我们能从Android平台所能获得的依赖就越少。所以我们也要避免过早优化,只在确实能实实在在改善app质量和性能的区域进行完全的布局自定义。

这不是一个非黑即白的决定。在使用平台提供的UI元素和完全自定义的两种极端之间还有很多方案——从简单的composite views 到复杂的 async views。实际项目中,你可能会结合文中的几种方案写出优秀的app。


作者:iamzp2008 发表于2014-12-9 18:27:56 原文链接
阅读:44 评论:0 查看评论

你可能感兴趣的:(android,解析,定义)