[关闭]
@flyouting 2014-03-18T15:37:41.000000Z 字数 8882 阅读 3963

Protip. Inflating layout for your custom view


现在的 protip 将会成为当人们去创建复杂的自定义views时经常面对的一个问题.
让我们拿一个复杂的自定义view作为一个列子来试着指出它在创建过程中做了什么。

此处输入图片的描述

正如你所看到的,我这里有一个相当不错的View - 简单的卡片状的部件。由于在理论上我在这张卡片里将有一些逻辑,我决定为此创建自定义视图。

互联网上常见的方法 - 扩展内置的布局之一,并在初始化过程中inflate自定义布局:

Card.java

  1. package com.trickyandroid.customview.app.view;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.widget.ImageView;
  5. import android.widget.RelativeLayout;
  6. import android.widget.TextView;
  7. import com.trickyandroid.customview.app.R;
  8. public class Card extends RelativeLayout {
  9. private TextView header;
  10. private TextView description;
  11. private ImageView thumbnail;
  12. private ImageView icon;
  13. public Card(Context context) {
  14. super(context);
  15. init();
  16. }
  17. public Card(Context context, AttributeSet attrs) {
  18. super(context, attrs);
  19. init();
  20. }
  21. public Card(Context context, AttributeSet attrs, int defStyle) {
  22. super(context, attrs, defStyle);
  23. init();
  24. }
  25. private void init() {
  26. inflate(getContext(), R.layout.card, this);
  27. this.header = (TextView)findViewById(R.id.header);
  28. this.description = (TextView)findViewById(R.id.description);
  29. this.thumbnail = (ImageView)findViewById(R.id.thumbnail);
  30. this.icon = (ImageView)findViewById(R.id.icon);
  31. }
  32. }

Card.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:padding="@dimen/card_padding"
  6. android:background="@color/card_background">
  7. <ImageView
  8. android:id="@+id/thumbnail"
  9. android:src="@drawable/thumbnail"
  10. android:layout_width="72dip"
  11. android:layout_height="72dip"
  12. android:scaleType="centerCrop"/>
  13. <TextView
  14. android:id="@+id/title"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:text="Card title"
  18. android:layout_toRightOf="@+id/thumbnail"
  19. android:layout_toLeftOf="@+id/icon"
  20. android:textAppearance="@android:style/TextAppearance.Holo.Medium"
  21. android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"/>
  22. <TextView
  23. android:id="@+id/description"
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:text="Card description"
  27. android:layout_toRightOf="@+id/thumbnail"
  28. android:layout_below="@+id/title"
  29. android:layout_toLeftOf="@+id/icon"
  30. android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"
  31. android:textAppearance="@android:style/TextAppearance.Holo.Small"/>
  32. <ImageView
  33. android:id="@+id/icon"
  34. android:layout_width="wrap_content"
  35. android:layout_height="wrap_content"
  36. android:src="@drawable/icon"
  37. android:layout_alignParentRight="true"
  38. android:layout_centerVertical="true"/>
  39. </RelativeLayout>

所以现在,当我们需要用到新创建的view时,唯一的事情就是在主布局中添加我们的view,就像其他默认view一样:

activity_main.xml

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:paddingLeft="@dimen/activity_horizontal_margin"
  6. android:paddingRight="@dimen/activity_horizontal_margin"
  7. android:paddingTop="@dimen/activity_vertical_margin"
  8. android:paddingBottom="@dimen/activity_vertical_margin">
  9. <com.trickyandroid.customview.app.view.Card
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"/>
  12. </FrameLayout>

看起来很简单,很直接,不是么?但是,我们来看下视图层次。

此处输入图片的描述

我们看到,在我们真正的card内容之前有个一个额外的RelativeLayout,这是因为
Card类是RelativeLayout,当我们inflate实际的内容时,我们只是把这些内容加进了外层的这个RelativeLayout

当然,这并不是什么大不了的事对于这种特殊的情况 - 我们不会在我们父布局RelativeLayout中做任何事情。但是,当我们有更复杂的布局和自定义视图的数量越来越大 - 你其实可以发现一些性能损失。这是因为有UI引擎很难短时间内遍历所有这些布局,测量它们和展示出来。

所以一般的规则是 - 越深的布局层次,越难遍历它。所以尽量扁平化布局

现在,让我们看看我们如何扁平化布局

Merge

card.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <merge xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:padding="@dimen/card_padding"
  6. android:background="@color/card_background">
  7. <ImageView
  8. android:id="@+id/thumbnail"
  9. android:src="@drawable/thumbnail"
  10. android:layout_width="72dip"
  11. android:layout_height="72dip"
  12. android:scaleType="centerCrop"/>
  13. <TextView
  14. android:id="@+id/title"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:text="Card title"
  18. android:layout_toRightOf="@+id/thumbnail"
  19. android:layout_toLeftOf="@+id/icon"
  20. android:textAppearance="@android:style/TextAppearance.Holo.Medium"
  21. android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"/>
  22. <TextView
  23. android:id="@+id/description"
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:text="Card description"
  27. android:layout_toRightOf="@+id/thumbnail"
  28. android:layout_below="@+id/title"
  29. android:layout_toLeftOf="@+id/icon"
  30. android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"
  31. android:textAppearance="@android:style/TextAppearance.Holo.Small"/>
  32. <ImageView
  33. android:id="@+id/icon"
  34. android:layout_width="wrap_content"
  35. android:layout_height="wrap_content"
  36. android:src="@drawable/icon"
  37. android:layout_alignParentRight="true"
  38. android:layout_centerVertical="true"/>
  39. </merge>

结果是:
此处输入图片的描述

很不错!我们消除了额外的RelativeLayout,但失去了我们的最外层卡片布局属性 - 白色背景和填充距离。这是因为<marge>标签合并其内容,但不是自己。所以你的最外层的属性都将丢失。
有两种简单的方法来添加他们回来:

1. 通过代码

Card.java

  1. .....
  2. private void init() {
  3. inflate(getContext(), R.layout.card, this);
  4. setBackgroundColor(getResources().getColor(R.color.card_background));
  5. //Add missing top level attributes
  6. int padding = (int)getResources().getDimension(R.dimen.card_padding);
  7. setPadding(padding, padding, padding, padding);
  8. this.header = (TextView)findViewById(R.id.header);
  9. this.description = (TextView)findViewById(R.id.description);
  10. this.thumbnail = (ImageView)findViewById(R.id.thumbnail);
  11. this.icon = (ImageView)findViewById(R.id.icon);
  12. }

2. 加入主布局时特别设定

activity_main.xml

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:paddingLeft="@dimen/activity_horizontal_margin"
  6. android:paddingRight="@dimen/activity_horizontal_margin"
  7. android:paddingTop="@dimen/activity_vertical_margin"
  8. android:paddingBottom="@dimen/activity_vertical_margin">
  9. <com.trickyandroid.customview.app.view.Card
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:background="@color/card_background"
  13. android:padding="@dimen/card_padding"/>
  14. </FrameLayout>

Include

另一种减少布局数目的方式是使用<include>标记。由于我的Card.javaRelativeLayout,我可以使其为我的内容的根视图。之后,我需要将其纳入我的主布局:
card.xml

  1. <com.trickyandroid.views.Card xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="wrap_content"
  4. android:padding="@dimen/card_padding"
  5. android:background="@color/card_background">
  6. <ImageView
  7. android:id="@+id/thumbnail"
  8. android:src="@drawable/thumbnail"
  9. android:layout_width="72dip"
  10. android:layout_height="72dip"
  11. android:scaleType="centerCrop"/>
  12. <TextView
  13. android:id="@+id/title"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:text="Card title"
  17. android:layout_toRightOf="@+id/thumbnail"
  18. android:layout_toLeftOf="@+id/icon"
  19. android:textAppearance="@android:style/TextAppearance.Holo.Medium"
  20. android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"/>
  21. <TextView
  22. android:id="@+id/description"
  23. android:layout_width="wrap_content"
  24. android:layout_height="wrap_content"
  25. android:text="Card description"
  26. android:layout_toRightOf="@+id/thumbnail"
  27. android:layout_below="@+id/title"
  28. android:layout_toLeftOf="@+id/icon"
  29. android:layout_marginLeft="?android:attr/listPreferredItemPaddingLeft"
  30. android:textAppearance="@android:style/TextAppearance.Holo.Small"/>
  31. <ImageView
  32. android:id="@+id/icon"
  33. android:layout_width="wrap_content"
  34. android:layout_height="wrap_content"
  35. android:src="@drawable/icon"
  36. android:layout_alignParentRight="true"
  37. android:layout_centerVertical="true"/>
  38. </com.trickyandroid.views.Card>

Card.java:

  1. public class Card extends RelativeLayout {
  2. private TextView header;
  3. private TextView description;
  4. private ImageView thumbnail;
  5. private ImageView icon;
  6. public Card(Context context) {
  7. super(context);
  8. }
  9. public Card(Context context, AttributeSet attrs) {
  10. super(context, attrs);
  11. }
  12. public Card(Context context, AttributeSet attrs, int defStyle) {
  13. super(context, attrs, defStyle);
  14. }
  15. @Override
  16. protected void onFinishInflate() {
  17. super.onFinishInflate();
  18. this.header = (TextView)findViewById(R.id.title);
  19. this.description = (TextView)findViewById(R.id.description);
  20. this.thumbnail = (ImageView)findViewById(R.id.thumbnail);
  21. this.icon = (ImageView)findViewById(R.id.icon);
  22. }
  23. }

在这种情况下,我并不需要手动inflate我的内容 - 它已经存在。通过XML指定。所以,我不需要我的init()了,所有的view初始化阶段去onFinishInflate()回调。

现在的问题是如何添加这个自定义视图到我的主要布局。我需要到<INCLUDE>它:
activity_main.xml

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:paddingBottom="@dimen/activity_vertical_margin"
  5. android:paddingLeft="@dimen/activity_horizontal_margin"
  6. android:paddingRight="@dimen/activity_horizontal_margin"
  7. android:paddingTop="@dimen/activity_vertical_margin">
  8. <include
  9. layout="@layout/card"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content" />
  12. </FrameLayout>

有些人可能会问,为什么我不能只使用<INCLUDE>方式把我的卡片的布局引入主布局,而不创建自定义视图,做这一切。这工作,如果你有UI组件只在您的自定义视图。在我来说,我认为我会有一些棘手的逻辑(图像下载/ DB访问,等等),我真的需要去自定义视图类。

翻译 @flyouting
2014 年 03月 17日
源地址:这里

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注