@flyouting
2014-03-18T15:37:41.000000Z
字数 8882
阅读 3925
现在的 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">
<ImageView
android:id="@+id/thumbnail"
android:src="@drawable/thumbnail"
android:layout_width="72dip"
android:layout_height="72dip"
android:scaleType="centerCrop"/>
<TextView
android: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"/>
<TextView
android: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"/>
<ImageView
android: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.Card
android: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">
<ImageView
android:id="@+id/thumbnail"
android:src="@drawable/thumbnail"
android:layout_width="72dip"
android:layout_height="72dip"
android:scaleType="centerCrop"/>
<TextView
android: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"/>
<TextView
android: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"/>
<ImageView
android: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 attributes
int 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.Card
android: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">
<ImageView
android:id="@+id/thumbnail"
android:src="@drawable/thumbnail"
android:layout_width="72dip"
android:layout_height="72dip"
android:scaleType="centerCrop"/>
<TextView
android: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"/>
<TextView
android: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"/>
<ImageView
android: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);
}
@Override
protected 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">
<include
layout="@layout/card"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
有些人可能会问,为什么我不能只使用<INCLUDE>
方式把我的卡片的布局引入主布局,而不创建自定义视图,做这一切。这工作,如果你有UI组件只在您的自定义视图。在我来说,我认为我会有一些棘手的逻辑(图像下载/ DB访问,等等),我真的需要去自定义视图类。
翻译 @flyouting
2014 年 03月 17日
源地址:这里