@flyouting
2014-03-18T07:37:41.000000Z
字数 8882
阅读 4307
现在的 protip 将会成为当人们去创建复杂的自定义views时经常面对的一个问题.
让我们拿一个复杂的自定义view作为一个列子来试着指出它在创建过程中做了什么。

正如你所看到的,我这里有一个相当不错的View - 简单的卡片状的部件。由于在理论上我在这张卡片里将有一些逻辑,我决定为此创建自定义视图。
互联网上常见的方法 - 扩展内置的布局之一,并在初始化过程中inflate自定义布局:
Card.java
package com.trickyandroid.customview.app.view;import android.content.Context;import android.util.AttributeSet;import android.widget.ImageView;import android.widget.RelativeLayout;import android.widget.TextView;import com.trickyandroid.customview.app.R;public class Card extends RelativeLayout {private TextView header;private TextView description;private ImageView thumbnail;private ImageView icon;public Card(Context context) {super(context);init();}public Card(Context context, AttributeSet attrs) {super(context, attrs);init();}public Card(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}private void init() {inflate(getContext(), R.layout.card, this);this.header = (TextView)findViewById(R.id.header);this.description = (TextView)findViewById(R.id.description);this.thumbnail = (ImageView)findViewById(R.id.thumbnail);this.icon = (ImageView)findViewById(R.id.icon);}}
Card.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="@dimen/card_padding"android:background="@color/card_background"><ImageViewandroid:id="@+id/thumbnail"android:src="@drawable/thumbnail"android:layout_width="72dip"android:layout_height="72dip"android:scaleType="centerCrop"/><TextViewandroid:id="@+id/title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Card title"android:layout_toRightOf="@+id/thumbnail"android:layout_toLeftOf="@+id/icon"android:textAppearance="@android:style/TextAppearance.Holo.Medium"android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"/><TextViewandroid:id="@+id/description"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Card description"android:layout_toRightOf="@+id/thumbnail"android:layout_below="@+id/title"android:layout_toLeftOf="@+id/icon"android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"android:textAppearance="@android:style/TextAppearance.Holo.Small"/><ImageViewandroid:id="@+id/icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/icon"android:layout_alignParentRight="true"android:layout_centerVertical="true"/></RelativeLayout>
所以现在,当我们需要用到新创建的view时,唯一的事情就是在主布局中添加我们的view,就像其他默认view一样:
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"android:paddingBottom="@dimen/activity_vertical_margin"><com.trickyandroid.customview.app.view.Cardandroid:layout_width="wrap_content"android:layout_height="wrap_content"/></FrameLayout>
看起来很简单,很直接,不是么?但是,我们来看下视图层次。

我们看到,在我们真正的card内容之前有个一个额外的RelativeLayout,这是因为
Card类是RelativeLayout,当我们inflate实际的内容时,我们只是把这些内容加进了外层的这个RelativeLayout。
当然,这并不是什么大不了的事对于这种特殊的情况 - 我们不会在我们父布局RelativeLayout中做任何事情。但是,当我们有更复杂的布局和自定义视图的数量越来越大 - 你其实可以发现一些性能损失。这是因为有UI引擎很难短时间内遍历所有这些布局,测量它们和展示出来。
现在,让我们看看我们如何扁平化布局
card.xml
<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="@dimen/card_padding"android:background="@color/card_background"><ImageViewandroid:id="@+id/thumbnail"android:src="@drawable/thumbnail"android:layout_width="72dip"android:layout_height="72dip"android:scaleType="centerCrop"/><TextViewandroid:id="@+id/title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Card title"android:layout_toRightOf="@+id/thumbnail"android:layout_toLeftOf="@+id/icon"android:textAppearance="@android:style/TextAppearance.Holo.Medium"android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"/><TextViewandroid:id="@+id/description"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Card description"android:layout_toRightOf="@+id/thumbnail"android:layout_below="@+id/title"android:layout_toLeftOf="@+id/icon"android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"android:textAppearance="@android:style/TextAppearance.Holo.Small"/><ImageViewandroid:id="@+id/icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/icon"android:layout_alignParentRight="true"android:layout_centerVertical="true"/></merge>
结果是:

很不错!我们消除了额外的RelativeLayout,但失去了我们的最外层卡片布局属性 - 白色背景和填充距离。这是因为<marge>标签合并其内容,但不是自己。所以你的最外层的属性都将丢失。
有两种简单的方法来添加他们回来:
Card.java
.....private void init() {inflate(getContext(), R.layout.card, this);setBackgroundColor(getResources().getColor(R.color.card_background));//Add missing top level attributesint padding = (int)getResources().getDimension(R.dimen.card_padding);setPadding(padding, padding, padding, padding);this.header = (TextView)findViewById(R.id.header);this.description = (TextView)findViewById(R.id.description);this.thumbnail = (ImageView)findViewById(R.id.thumbnail);this.icon = (ImageView)findViewById(R.id.icon);}
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"android:paddingBottom="@dimen/activity_vertical_margin"><com.trickyandroid.customview.app.view.Cardandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@color/card_background"android:padding="@dimen/card_padding"/></FrameLayout>
另一种减少布局数目的方式是使用<include>标记。由于我的Card.java是RelativeLayout,我可以使其为我的内容的根视图。之后,我需要将其纳入我的主布局:
card.xml
<com.trickyandroid.views.Card xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="@dimen/card_padding"android:background="@color/card_background"><ImageViewandroid:id="@+id/thumbnail"android:src="@drawable/thumbnail"android:layout_width="72dip"android:layout_height="72dip"android:scaleType="centerCrop"/><TextViewandroid:id="@+id/title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Card title"android:layout_toRightOf="@+id/thumbnail"android:layout_toLeftOf="@+id/icon"android:textAppearance="@android:style/TextAppearance.Holo.Medium"android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"/><TextViewandroid:id="@+id/description"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Card description"android:layout_toRightOf="@+id/thumbnail"android:layout_below="@+id/title"android:layout_toLeftOf="@+id/icon"android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"android:textAppearance="@android:style/TextAppearance.Holo.Small"/><ImageViewandroid:id="@+id/icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/icon"android:layout_alignParentRight="true"android:layout_centerVertical="true"/></com.trickyandroid.views.Card>
Card.java:
public class Card extends RelativeLayout {private TextView header;private TextView description;private ImageView thumbnail;private ImageView icon;public Card(Context context) {super(context);}public Card(Context context, AttributeSet attrs) {super(context, attrs);}public Card(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();this.header = (TextView)findViewById(R.id.title);this.description = (TextView)findViewById(R.id.description);this.thumbnail = (ImageView)findViewById(R.id.thumbnail);this.icon = (ImageView)findViewById(R.id.icon);}}
在这种情况下,我并不需要手动inflate我的内容 - 它已经存在。通过XML指定。所以,我不需要我的init()了,所有的view初始化阶段去onFinishInflate()回调。
现在的问题是如何添加这个自定义视图到我的主要布局。我需要到<INCLUDE>它:
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"><includelayout="@layout/card"android:layout_width="wrap_content"android:layout_height="wrap_content" /></FrameLayout>
有些人可能会问,为什么我不能只使用<INCLUDE>方式把我的卡片的布局引入主布局,而不创建自定义视图,做这一切。这工作,如果你有UI组件只在您的自定义视图。在我来说,我认为我会有一些棘手的逻辑(图像下载/ DB访问,等等),我真的需要去自定义视图类。
翻译 @flyouting
2014 年 03月 17日
源地址:这里