[原文地址] -- http://lucasr.org/2014/05/12/custom-layouts-on-android/
如果你做过Android开发的话,肯定会用到系统内置的layouts布局-- RelativeLayout, FrameLayout, LinearLayout等。这些layout是我们构建Android UI的基础工具。把系统内置的layouts结合起来就为我们实现复杂UI提供了一个工具箱,但是应用的某些设计仍然需要需要自己去自定义layout才能实现。自定义layout主要有两个原因,第一,为了使UI更加有效率,通过减少view的数量以及加快layout的遍历解析。第二,创建某些使用系统控件不太可能实现的UI时。在这片文章中,展示了四种不同的方式来实现自定义layout,同时对比他们的优缺点:composite view, custom composite view, flat custom view, async custom views。这些代码示例可以在 https://github.com/lucasr/android-layout-samples 中找到。这个示例应用使用上面这种四种方式来实现同一个UI界面,这个界面是一个简化版的twitter首页,没有交互只有layout。
Composite Views(compound views) 是实现可重用UI组件的最简单的方式,也非常容易实现:
1. 创建一个继承于内置layouts的子类
2. 在构造函数中,inflates一个merge布局
3. 使用findViewById()方法初始化指向内部view的成员
4. 添加自己的API方法,用来查询以及更新View的状态
TweetCompositeView是一个composite view,首先它继承于RelativeLayout,然后inflates tweet_composite_view.xml布局文件到TweetCompositeView中,最后暴露update()方法让adapter可以刷新它的状态。这个view的实现很简单,但是很实用。
As you can see, custom composite views are fairly simple to write usingViewGroup APIs. Most of the time, they will give you the performance your app needs.
However, you might want to go further and optimize your layouts even more on critical parts of your UI that are very dynamic e.g. ListViews, ViewPager,etc. What about merging all TweetLayoutView child views into a single custom view to rule them all? That is what flat custom views are about—see image below.
A flat custom view is a fully custom view that measures, arranges, and draws its inner elements. So you will be subclassing View instead of ViewGroup.
If you are looking for real-world examples, enable the “show layout bounds” developer option in your device and have a look at apps like Twitter, GMail, and Pocket. They all use flat custom views in their listing UIs.
The main benefit from using flat custom views is the great potential for flattening your view hierarchy, resulting in faster traversals and, potentially, a reduced memory footprint.
Flat custom views give you maximum freedom as they are literally a blank canvas. But this comes at a price: you won’t be able to use the feature-packed stock widgets such as TextView and ImageView. Yes, it is simple to draw texton a Canvas but what about ellipsizing? Yes, you can easily draw a bitmap but what about scaling modes? Same applies to touch events, accessibility, keyboard navigation, etc.
The bottom line is: with flat custom views,you will likely have to re-implement features that you would get for free from the platform. So you should only consider using them on core parts of your UI. For all other cases, just lean on the platform with composite views, custom or not.
TweetElementViewcode is a flat custom view. To make it easier to implement it, I created a little custom view framework called UIElement. You will find it in the canvascode package.
The UIElement framework provides a measure/layout API which is analogous to Android’s. It contains headless versions of TextView and ImageView with only the necessary features for this demo—see TextElementcode andImageElementcode respectively. It also has its own inflatercode to instantiateUIElements from layout resource filescode.
Probably worth noting: the UIElement framework is in a very early development stage. Consider it a very rough sketch of something that might actually become useful in the future.
You have probably noticed how simple TweetElementView looks. This is because the real code is all in TweetElementcode—with TweetElementView just acting as a hostcode.
The layout code in TweetElement is pretty much analogous toTweetLayoutView‘s. It handles Picasso requests differentlycode because it doesn’t use ImageViews.
As we all know, the Android UI framework is single-threaded. And this is for a good reason: UI toolkits are not your simplest piece of code. Making them thread-safe and asynchronous would be an unworthy herculean effort.
This single-threaded nature leads to some fundamental limitations. It means, for example, that you can’t do layout traversals off main thread at all—something that would be useful in very complex and dynamic UIs.
For example, if your app has complex items in a ListView (like most social apps do), you will probably end up skipping frames while scrolling becauseListView has to measurecode and layoutcode each child view to account for their new content as they become visible. The same issue applies toGridViews, ViewPagers, and the like.
Wouldn’t it be nice if we could do a layout traversal on the child views that are not visible yet without blocking the main thread? This way, the measure()and layout() calls on child views would take no time when needed in the UI thread.
Enter async custom view, an experiment to allow layout passes to happen off main thread. This is inspired by the async node framework developed by the Paper team at Facebook.
Given that we can never ever touch the UI toolkit off main thread, we need an API that is able to measure/layout the contents of a view without being directly coupled to it. This is exactly what the UIElement framework provides us.
AsyncTweetViewcode is an async custom view. It uses a thread-safe AsyncTweetElementcode factorycode to define its contents. Off-screenAsyncTweetElement instances are created, pre-measured, and cached in memory from a background thread using a Smoothie item loadercode.
I had to compromise the async behaviour a bit because there’s no sane way of showing layout placeholders on list items with arbitrary heights i.e. you end up resizing them once the layout gets delivered asynchronously. So whenever an AsyncTweetView is about to be displayed and it doesn’t find a matchingAsyncTweetElement in memory, it will force its creation in the UI threadcode.
Furthermore, both the preloading logic and the memory cache expiration would need to be a lot smarter to ensure more layout cache hits in the UI thread. For instance, using a LRU cachecode here doesn’t seem ideal.
Despite these limitations, the preliminary results from async custom views look very promising. I’ll continue the explorations in this area by refining theUIElement framework and using it in other kinds of UIs. Let’s see where it goes.
Wrapping up
When it comes to layouts, the more custom you go, the less you’ll be able to lean on the platform’s proven components. So avoid premature optimization and only go fully custom on areas that will actually affect the perceived quality and performance of your app.
This is not a black-and-white decision though. Between stock widgets and fully custom views there’s a wide spectrum of solutions—from simple composite views to the more complex async views. In practice, you’ll usually end up combining more than one of the techniques demonstrated here.