[原文地址] -- 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 View

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的实现很简单,但是很实用。



Custom Composite View

对于大多数情况来说,TweetCompositeView就可以很好地解决。但是,在某些较为复杂的UI情况下,你需要减少child views的数量以及让layout遍历解析更加有效率。尽管composite view实现起来比较简单,但是使用通用的layout会导致一些不必要的消耗,尤其使用较为复杂的容器时,比如LinearLayout和RelativeLayout。这种情况下,当系统进行UI创建的时候,可能需要处理大量的互相结合的layout,这可能导致在一次遍历中多次对child view的大小进行计算。LinearLayout的layout_weight就是一个很常见的例子。
通过为自身应用量身裁剪过的布局逻辑,在measuring和positioning子视图的中,可以极大优化UI的性能。这种方式就是 custom composite view。 一个custom composite view就是一个覆盖了onMeasure()和onLayout()方法的composite view。相应地,不再是子类化一个已经存在的类似RelativeLayout的容器,而是需要子类化一个更加通用的ViewGroup。
TweetLayoutView 就是使用这种方式实现的例子。需要注意是这里从TweetCompositeView实现转化为TweetLayoutView实现中,是如何去掉LinearLayout,并且避免了使用layout_weight的。TweetLayoutView中比较复杂的工作是需要设置每个child view的MeasureSpec,这个可以在ViewGroup的 measureChildWithMargins()和getChildMeasureSpec()方法中实现。

TweetLayoutView可能并没有正确地处理可能出现的layout组合,但这个关系不大,只需要实现较为简单的custom layout即可,毕竟仅仅是针对某个特定的需求,而不是通用的layout。这种方式可以让我们以一种简单并且高效的方式实现代码。
译者注:flat custom View以及async custom view是使用UIElements进行draw绘制的,这个成本对于我们的团队来说有些成本过高,暂时决定不使用,所以这两种方式,我也不再翻译。

Flat Custom 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. ListViewsViewPager,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.

CUSTOM COMPOSITE VIEW (LEFT) AND FLAT CUSTOM VIEW (RIGHT)

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.

Async Custom View

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 toGridViewsViewPagers, 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.



更多相关文章

  1. Android几种网络访问方式的比较
  2. FileOutputStream中的 3种write方式
  3. Kotlin Anko 使用相对布局 RelativeLayout
  4. Android异步更新UI的方式之使用AsyncTask异步任务
  5. Android(安卓)View体系(三)--实现 View 的滑动七种方式
  6. Android滑动冲突解决方式(下拉刷新上拉加载更多,适配RecyclerVie
  7. Android_Layout_xml布局
  8. Android的EditText自定义背景,无光标解决
  9. 三星 Galaxy Nexus,4.0.3/华为荣耀U8860 从程序自动创建快捷方式

随机推荐

  1. Android高手进阶教程(八)之----Android(
  2. android 传感器获取方向总结
  3. Android中应用调用系统权限
  4. vue3 父子组件传值详解
  5. 详解React 和 Redux的关系
  6. js-基础(五)classList对象、blur事件进行表
  7. Vue之Axios异步通信详解
  8. 属性重载与命名空间和类自动加载器
  9. kubeadm安装k8s 1.23.5
  10. 面向对象编程学习小结(一)