Android 创建复合视图

示例

化合物视图是一个自定义ViewGroup其会被视为由周围的程序代码的单一视图。这样的ViewGroup在类似DDD的设计中非常有用,因为它可以对应于一个聚合(在此示例中为Contact)。可以在显示联系人的所有位置重复使用它。

这意味着周围的控制器代码(“活动”,“片段”或“适配器”)可以简单地将数据对象传递到视图,而无需将其拆散到许多不同的UI小部件中。

这有助于代码重用,并根据SOLID原则进行更好的设计。

布局XML

这通常是您开始的地方。您已经有一些XML可以重用,可能是作为<include/>。将其解压缩到一个单独的XML文件中,并将根标记包装在一个<merge>元素中:

<?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="match_parent">

        <ImageView
            android:id="@+id/photo"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_alignParentRight="true" />

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/photo" />

        <TextView
            android:id="@+id/phone_number"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/name"
            android:layout_toLeftOf="@id/photo" />
</merge>

此XML文件可以在Android Studio的布局编辑器中正常运行。您可以像对待其他任何布局一样对待它。

复合ViewGroup

获得XML文件后,创建自定义视图组。

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.ImageView;
import android.widget.TextView;

import myapp.R;

/**
 * A compound view to show contacts.
 *
 * This class can be put into an XML layout or instantiated programmatically, it
 * will work correctly either way.
 */
public class ContactView extends RelativeLayout {

    // 该类扩展了RelativeLayout,因为它带有自动
    //(MATCH_PARENT,MATCH_PARENT)其子项的布局。你可以扩展
    //如果您想要更多控制,则使用原始android.view.ViewGroup类。见
    // 注意在布局XML中为什么您不想扩展复杂的视图
    // 例如RelativeLayout。

    // 1.实现超类构造函数。
    public ContactView(Context context) {
        super(context);
        init(context, null);
    }

    // 省略了两个额外的构造函数,以简化示例

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ContactView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    // 2.通过使用`this`作为父对象来扩展XML来初始化视图
    private TextView mName;
    private TextView mPhoneNumber;
    private ImageView mPhoto;

    private void init(Context context, AttributeSet attrs) {
        LayoutInflater.from(context).inflate(R.layout.contact_view, this, true);
        mName = (TextView) findViewById(R.id.name);
        mPhoneNumber = (TextView) findViewById(R.id.phone_number);
        mPhoto = (ImageView) findViewById(R.id.photo);
    }

    //3.定义域模型中表示的设置器。这就是例子
    //    所有关于。所有控制器代码都只能调用此setter而不是摆弄
    //    很多字符串,可见性选项,颜色,动画等。如果您不使用
    //    自定义视图,此代码通常会以静态辅助方法(错误)或副本结尾 
    //    此代码的一部分将被复制粘贴到整个地方(更糟)。
    public void setContact(Contact contact) {
        mName.setText(contact.getName());
        mPhoneNumber.setText(contact.getPhoneNumber());
        if (contact.hasPhoto()) {
            mPhoto.setVisibility(View.VISIBLE);
            mPhoto.setImageBitmap(contact.getPhoto());
        } else {
            mPhoto.setVisibility(View.GONE);
        }
    }
}

该init(Context, AttributeSet)方法是您读取任何自定义XML属性的地方,如将属性添加到视图中所述。

放置好这些片段后,您就可以在应用程序中使用它了。

在XML中的用法

这是一个示例fragment_contact_info.xml,说明如何将单个ContactView放在消息列表的顶部:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- The compound view becomes like any other view XML element -->
    <myapp.ContactView
        android:id="@+id/contact"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/message_list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

代码中的用法

这RecyclerView.Adapter是显示联系人列表的示例。此示例说明了完全没有View操纵的情况下,控制器代码可获得多少清洁度。

package myapp;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

public class ContactsAdapter extends RecyclerView.Adapter<ContactsViewHolder> {

    private final Context context;

    public ContactsAdapter(final Context context) {
       this.context= context;
    }

    @Override
    public ContactsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ContactView v = new ContactView(context); // <--- this
        return new ContactsViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ContactsViewHolder holder, int position) {
        Contact contact = this.getItem(position);
        holder.setContact(contact);  // <--- this
    }

    static class ContactsViewHolder extendsRecyclerView.ViewHolder{

        public ContactsViewHolder(ContactView itemView) {
            super(itemView);
        }

        public void setContact(Contact contact) {
            ((ContactView) itemView).setContact(contact); // <--- this
        }
    }
}