当前位置: 首页 > 科技观察

深入剖析Android的自定义布局

时间:2023-03-18 00:37:06 科技观察

前面写了什么:本文由前FirefoxAndroid工程师(现转职Facebook)LucasRocha撰写。好好分析一下,结合这四种Android自定义布局方案写的示例工程,说明各自的优缺点以及四种方案的对比。看完这篇文章,对Android自定义布局也有了更深的了解,所以趁着兴奋,翻译成中文,原文链接在这里。只要你写过Android程序,就一定用过Android平台内置的几种布局——RelativeLayout、LinearLayout、FrameLayout等等。它们可以帮助我们很好地构建AndroidUI。这些内置布局已经提供了许多方便的组件,但在很多情况下您仍然需要自定义自己的布局。综上所述,自定义布局有两大优势:通过减少视图的使用和更快地遍历布局元素,使您的UI显示更高效;您可以构建现有视图无法实现的UI。在这篇博文中,我将实现四种不同的自定义布局并比较它们的优缺点。它们是:复合视图、自定义复合视图、平面自定义视图和异步自定义视图。这些代码实现可以在我的github上的android-layout-samples项目中找到。这个应用程序使用上面提到的四种自定义布局来实现相同的UI效果。他们使用Picasso加载图像。该应用程序的用户界面只是Twitter时间轴的简化版本——没有交互,只有布局。好吧,让我们从最常见的自定义布局开始:复合视图。复合视图复合视图(也称为复合视图)是将多个视图组合成可重用UI组件的多种方法中最简单的一种。该方法的实现过程如下:继承相关的内置布局。在构造函数中填充合并布局。初始化成员变量,通过findViewById()指向内部视图。添加自定义API以查询和更新视图状态。TweetCompositeViewcode是一个复合视图。继承RelativeLayout,填充tweet_composite_layout.xmlcode布局文件,***对外暴露update()方法,更新其在adaptercode中的状态。CustomCompositeView中上面提到的TweetCompositeView的实现可以满足大部分情况。但它在某些情况下不起作用。假设您现在想要减少子视图的数量,并使布局元素的便利性更加高效。这时候我们回头看看,虽然复合视图实现起来比较简单,但是使用这些内置布局还是有很大的开销——尤其是LinearLayout和RelativeLayout等比较复杂的容器。由于Android平台内置布局的实现,系统在遍历布局元素时需要处理多个布局的组合和子视图的多个度量——LinearLayout的layout_weight属性就是一个常见的例子。所以,你可以为你的app量身定做一套subview的计算和定位逻辑,这样可以大大优化你的UI。这种方式就是我接下来要介绍的自定义复合视图。顾名思义,自定义复合视图就是重写onMeasure()和onLayout()方法的复合视图。所以相比于之前的复合视图继承RelativeLayout,现在我们需要更进一步——继承更抽象的ViewGroup。TweetLayoutViewcode就是通过这种技术实现的。注意这个实现并没有像TweetComposiveView那样继承LinearLayout,这也避免了layout_weightcode属性的使用。这个煞费苦心的过程是通过ViewGroup的measureChildWithMargins()方法和背后的getChildMeasureSpec()方法计算出每个子view的MeasureSpec。TweetLayoutView不会正确处理所有可能的布局组合,但它不必这样做。我们肯定需要根据特定需求优化自定义布局,这样可以让我们编写简单高效的布局代码。FlatCustomView如您所见,自定义复合视图可以简单地通过使用ViewGroupAPI来实现。大多数时候,这种实现可以满足我们的需求。但是,如果我们想更进一步——优化我们应用程序中UI的关键部分,例如ListViews、ViewPager等。如果我们将TweetLayoutView的所有子视图合并到一个自定义视图中并一起管理它们会怎样?这是我们接下来要讨论的平面自定义视图——见下图。左边是CUSTOMCOMPOSITEVIEW,右边是FLATCUSTOMVIEW。平面自定义视图是完全自定义的视图,完全负责内部子视图的计算、位置排列和绘制。所以它直接继承了View而不是ViewGroup。如果你想知道现实生活中的应用程序中是否有这样的例子,很简单——打开手机“开发者模式”中的“显示布局边界”选项,然后打开Twitter、Gmail或Pocket应用程序,它们在列表UI中使用了平面自定义视图。使用平面自定义视图的主要好处是它可以极大地压缩应用程序的视图层次结构,这反过来又允许更快的布局元素遍历并最终减少内存使用。平面自定义视图可以给你最大的自由度,就好像你在一张白纸上画画一样。但这种自由是有代价的:您不能再使用现有的视图元素,例如TextView和ImageView。没错,在Canvas上绘制文本确实很容易,但是省略(即截断太长的文本)呢?同样的,在Canvas上画图确实很容易,但是怎么缩放呢?这些限制也适用于触摸事件、辅助功能、键盘导航等。所以使用flatcustomview的底线是:只将flatcustomview应用到你app的UI核心部分,其余的直接依赖Android平台提供的view。TweetElementViewcode是平面自定义视图。为了方便起见,我创建了一个名为UIElement的小型自定义视图框架。您可以在canvascode包中找到它。UIElement提供类似于Android平台的测量/布局API。它包含没有图像界面的TextView和ImageView,这两个元素包含几个必需的属性-分别参见TextElementcode和ImageElementcode。它还具有自己的inflatercode,可帮助从布局资源文件代码实例化UIElement。注意:UIElement仍处于非常早期的开发阶段,因此仍然存在很多错误,但在未来随着不断改进,UIElement可能会变得非常有用。你可能会认为TweetElementView的代码看起来很简单,这是因为实际的代码在TweetElementcode里面——实际上TweetElementView起到了托管代码的作用。TweetElement中的布局代码与TweetLayoutView'非常相似,只不过是使用Picasso来请求图片,但代码不同是因为TweetElement没有使用ImageView。AsyncCustomView众所周知,AndroidUI框架是单线程的。这样的单线程引入了一些限制。例如,您不能在主线程之外迭代布局元素——但这对复杂的动态UI来说是个好消息。如果您的应用程序在ListView中具有复杂的项目布局(如大多数社交应用程序),那么您在滑动ListView时很可能会跳帧,因为ListView需要提供将出现在列表中的新内容计算它们的视图大小代码和布局代码。GridViews、ViewPagers等也会出现同样的问题。如果我们可以在主线程以外的线程上对那些还没有出现的子视图进行布局遍历,是不是就可以解决上面的问题呢?也就是说,在子视图上调用measure()和layout()方法不会占用主线程的时间。所以asynccustomview是一个实验,让subview的布局遍历过程发生在主线程之外。这个想法的灵感来自于Facebook的Paperteamasync节点框架视频。由于我们永远无法在主线程之外接触Android平台的UI组件,因此我们需要一个API来测量和布局这个视图的内容,而不需要直接访问视图。这正是UIElement框架提供给我的。AsyncTweetViewcode是一个异步自定义视图。它使用线程安全的AsyncTweetElementcode工厂类代码来定义其内容。具体过程是一个Smoothie子项加载器代码在后台线程上创建、预测和缓存暂时不可见的AsyncTweetElement(在内存中供以后直接使用)。当然,我在实现这个异步UI的过程中做了一点妥协,因为你不知道如何显示任意高度的布局占位符。例如,当异步交付布局时,您只能在后台线程上更改布局的大小一次。因此,当一个AsyncTweetView即将显示,但在内存中找不到合适的AsyncTweetElement时,此时框架会强制在主线程创建一个AsyncTweetElementcode。此外,预加载逻辑和内存缓存过期时间设置也需要更好的实现,以保证主线程尽可能的利用内存中的缓存布局。例如,在该方案中使用LRU缓存代码并不是一个明智的选择。尽管有这些限制,使用异步自定义视图的初步结果还是很有希望的。当然我会通过重构UIElement框架和使用其他类的UI继续在这方面进行探索。让我们等着看。总结我们在布局方面定制的越多,我们从Android平台获得的依赖就越少。所以我们也要避免过早的优化,只在真正能提升应用质量和??性能的地方做全布局定制。这不是一个黑白分明的决定。在使用平台提供的UI元素和完全自定义这两个极端之间有许多解决方案——从简单的复合视图到复杂的异步视图。在实际项目中,你可能会结合文中的几种方案,写出一个优秀的应用。