深入解析Android的自定义布局

来源:互联网 发布:淘宝618是什么日子 编辑:程序博客网 时间:2024/06/06 04:56

http://greenrobot.me/devpost/android-custom-layout/





如果你开发过Android的应用,你一定使用过Android内置的RelativeLayoutLinearLayoutFrameLayout等等布局,它们是Android的UI基础布局。这些内置的布局提供了实现复杂布局的强大工具,但仍然在某些情况下基于设计的需要你得实现自定义布局。原因有两个,首先使你的UI更加高效(通过减少view的数量或者使布局遍历更快)第二,常用控件无法实现。这篇文章将会阐述四种自定义布局实现的不同方式,讨论各自的利弊composite view, custom composite view, flat custom view, and async custom views。例子地址android-layout-samples。该例子用每种方式实现了相同的UI,图片加载使用的Picasso。UI是Twitter应用的简单版,没有交互只有布局。

第一种布局(复合视图)composite view

复合视图Composite View

复合视图(也称为混合视图)是将多个视图结合成一个可重用UI组件的最简单的方式。它们很容易实现:

  1. 继承Android的内置布局
  2. 在构造函数里填充布局
  3. 通过findViewById()初始化成员变量
  4. 添加自己的api查询更新视图的状态

TweetCompositeView 是一个复合视图。它继承了RelativeLayout,填充的tweet_composite_layout.xml, 提供了一个update()方法方便adapter刷新它的状态,很简单。

自定义复合视图Custom Composite View

TweetCompositeView在多数情况下都将会执行得很好。但为了便于讨论,我们假设你想要减少子视图的数量并且让布局遍历更加高效 。尽管复合视图容易实现,但使用常用布局存在消耗问题,特别是像LinearLayoutRelativeLayout。作为基础布局,它们不得不操纵大量的复杂布局并且在一次遍历中对子视图测量多次,线性布局的权重就是个例子,所以为你的应用量身定制一套子视图测绘的实现逻辑将会大大优化你得UI。

一个自定义复合视图即一个重写onMeasure和onLayout的简单复合视图,因此不应该继续继承一个像RelativeLayout之类的已存在容器,而应当继承一个更加广泛(抽象)的容器ViewGroup。

TweetLayoutView 就是这样实现的。注意不像TweetComposiveView,它没有继承theLinearLayout因而就避免了使用权重,而搞清楚在每个子视图应当使用MeasureSpec的哪一个布局要求,这个繁重的任务是由ViewGroup很好用的measureChildWithMargins()和getChildMeasureSpec()方法完成的。

TweetLayoutView可能并没有很好的包含所有可能的布局组合但其实并不需要。它肯定会很好的为你的实际需求优化布局,它允许你为你的应用写出更简单更高效的布局代码。

扁平化自定义视图Flat Custom View

如你所见,自定义复合视图使用ViewGroup的API很简单就可以写出来。多数情况下,它们会给你得应用想要的性能。然而你可能想在你动态的UI上一些重要的部分进一步优化,比如ListView, ViewPager等等。那么将所有的TweetLayoutView子视图合并到一个自定义视图里会怎么样?扁平化自定义视图就是这样。如图:

Custom Composite View (left) and Flat Custom View (right)

CUSTOM COMPOSITE VIEW (左) AND FLAT CUSTOM VIEW (右)

一个扁平化自定义视图是一个自己测量,摆放绘制自己内部元素的完整的自定义视图。所以它继承了View而不是ViewGroup。你可以在现实例子中找到,在设备上开启开发者选项里的“展示布局边界”然后打开像Twitter,Gmail和Pocket之类的应用你会发现,在列表UI上,它们全都用的扁平化自定义视图,这样做最大的优点就是有很大可能减小视图的层级,更快速的便利和减少内存占用。它就像一张空白画布一样给予你最大的自由空间,但代价是:你无法再使用已有的Android自带控件像TextView和ImageView。尽管你可以绘制text但省略号属性如何处理?当然你也可以轻松绘制一个bitmap但scaling模式属性呢?包括触摸事件,权限,键盘导航等等都有同样的问题。那么你很可能不得不重新实现一些平台支持的功能,所以你只应在你UI的核心部分考虑使用它,其他情况还是要依赖拥有平台所提供的功能的复合视图,自定义或自带的视图了。

TweetElementView是一个扁平化自定义视图,为了更容易实现,我创建了一个自定义视图框架叫做UIElement,你会在canvas包下面找到。该框架提供了一个和Android相似的用于测绘/摆放的API,它包含了轻化版的TextView和ImageView,这两个只有demo所需的功能,其他都没有实现——可以分别在TextElement和ImageElement中看到。它也有自己的inflater从资源文件实例化UIElement。

可能有价值的提示:UIElement框架处于早期开发阶段,可以把它看做是一个粗略的草图,也许以后会很有用。

你可能会注意到TweetElementView看上去很简单,那是因为真正的实现代码都在TweetElement里面,而TweetElementView更像是扮演着一个托管角色。

TweetElement布局代码和TweetLayoutView很相似,它里面包含了毕加索的不同请求因为没有使用ImageView。

异步自定义视图Async Custom View

众所周知,Android的UI框架是单线程的而这是有原因的:UI工具包不简单。将它们变成线程安全和异步将会是费劲而不值得的尝试。而这样也就有了一些基本限制,比如你不能在主线程外遍历布局——而这个限制在复杂和动态的UI上很有好处。比如如果你的应用里ListView的item很复杂(像多数社交应用那样),那么当滑动的时候将可能跳帧,因为ListView必须为它们滑动中的可见item里的子视图重新进行测量和摆放,在GridView,ViewPager等等视图上也是如此。那么如果我们可以在还未可见的子视图(比如还未滑动到得item)上做布局遍历而不阻塞主线程岂不是很好?这样一来,在需要时子视图调用(滑动到该item可见时)调用measure()和layout()将不会在UI线程上耗费时间。异步自定义视图就是这样一个允许布局在主线程之外遍历的实验,这是由Facebook的Paper团队开发的async node framework 所启发。

由于我们绝不可能在主线程之外碰到UI工具包的限制,我们需要能够测量/摆放视图内容的API而又不能直接触碰这个视图。而这就是UIElement框架提供给我们的。AsyncTweetView 是一个异步自定义视图,它使用了线程安全的AsyncTweetElement工厂去定义它的内容,不可见的情况下AsyncTweetElement在后台线程由Smoothie item loader创建好,提前测量之后缓存到内存中。

当然我不得不在异步行为上作出些许让步,因为在没有确切高度的情况下没办法展示布局的空白占位,也就是说在异步获取到该布局的确切信息时重新调整大小。所以每当AsyncTweetView要展示而在内存中没有找到匹配的AsyncTweetElement的时候,将会强制在UI线程上创建。还有,预加载和缓存延时需要非常好得实现才能保证更多缓存布局的使用。比如LRU缓存机制在这里并不理想。

除了这些限制之外,异步自定义视图的初步结果还是很有作用的,我会继续探索这个方面,优化UIElement框架并在其他UI中使用它看怎么样。

总结

面对布局时,你往自定义方向走得越远,从平台组件得到的支持就会越少。因此要避免过早的优化并且只在那些的确对你的应用有显著质量和性能影响的地方自定义。这不是非黑即白的单选,在基础控件和完全自定义视图之间还是有很广的空间——从简单的复合视图到复杂的异步视图,在实际运用中,你会用到更多这里没有提及的技术方法。







0 0
原创粉丝点击