自定义View:2、组合视图

昨天联系了自定义View的第一类,直接继承View自己画图。其实很多情况下,我们并不需要自己去从头开始画图,而是将现有的各种视图组合在一起,方便我们的使用。那这种情况下该怎么做呢?

我们先来看看实现的效果怎么样(下图中,我将组合视图与CardView和RecyclerView结合在一起,看起来效果还蛮不错的):

device-2014-12-09-110725

其实操作起来也很简单,与直接继承View的办法类似,一共3个步骤:

  1. 编写Layout,可以在代码中设置Layout,也可以直接通过XML配置Layout,然后在代码中直接解析就可以。
  2. 编写属性
  3. 组合在一起成为一个新的View
  4. 使用我们新的编写的View

很简单吧?我们依次来看,具体应该怎么来做。

一:编写对应的Layout

代码如下:

<?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="match_parent"
    >
    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:scaleType="centerInside"
        android:src="@drawable/ic_launcher"
        android:id="@+id/avator"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:layout_toRightOf="@id/avator"
        android:layout_alignTop="@id/avator"
        android:id="@+id/name"
        android:text="name"
        android:textAppearance="?android:attr/textAppearanceLarge"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:layout_alignLeft="@id/name"
        android:layout_below="@id/name"
        android:text="Description here"
        android:layout_alignBottom="@id/avator"
        android:id="@+id/description"/>

</RelativeLayout>

视图的定义就不需要多说了,比较简单,其界面效果就是其中的名片小卡片,包括了一张图像,名字和简单的简介。

二、编写属性

为了以后能够更加方便的使用,我们当然最好为我们新编写的视图增加属性设置,其实和上一篇文章当中介绍的基本上一样,贴上代码。

    <declare-styleable name="NameCard">
        <attr name="avatar" format="reference"/>
        <attr name="name" format="string"/>
        <attr name="description" format="string"/>
    </declare-styleable>

我们为其制定了3个属性,分别是图像,姓名,介绍。通过这些,我们就可以在XML中直接指定默认的属性。

三、构建新的View

思路:

第一步:我们需要加载我们在步骤1中编写的Layout

第二步:根据XML属性或者用户的设置更改其中图像,姓名和介绍的内容。

其他的,就交给RelativeLayout去做吧。

代码如下:

public class NameCard extends RelativeLayout{

    public static class NameCardContent {
        private Drawable mAvatarImageDrawable;
        private String mName;
        private String mDescription;

        public NameCardContent() {
        }

        public NameCardContent(Drawable avatarImageDrawable, String name, String description) {
            mAvatarImageDrawable = avatarImageDrawable;
            mName = name;
            mDescription = description;
        }

        public Drawable getAvatarImageDrawable() {
            return mAvatarImageDrawable;
        }

        public void setAvatarImageDrawable(Drawable avatarImageDrawable) {
            mAvatarImageDrawable = avatarImageDrawable;
        }

        public String getName() {
            return mName;
        }

        public void setName(String name) {
            mName = name;
        }

        public String getDescription() {
            return mDescription;
        }

        public void setDescription(String description) {
            mDescription = description;
        }
    }

    private NameCardContent mNameCardContent = new NameCardContent();

    private ImageView mImageViewAvator;
    private TextView mTextViewName;
    private TextView mTextViewDescription;

    public NameCard(Context context) {
        this(context, null);
    }

    public NameCard(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NameCard(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.NameCard, 0, 0);
        try {
            mNameCardContent.mAvatarImageDrawable = typedArray.getDrawable(R.styleable.NameCard_avatar);
            mNameCardContent.mName = typedArray.getString(R.styleable.NameCard_name);
            mNameCardContent.mDescription = typedArray.getString(R.styleable.NameCard_description);
        } finally {
            typedArray.recycle();;
        }

        inflate(context, R.layout.namecard, this);

        mImageViewAvator = (ImageView) findViewById(R.id.avator);
        mTextViewName = (TextView) findViewById(R.id.name);
        mTextViewDescription = (TextView) findViewById(R.id.description);

        mImageViewAvator.setImageDrawable(mNameCardContent.getAvatarImageDrawable());
        mTextViewName.setText(mNameCardContent.getName());
        mTextViewDescription.setText(mNameCardContent.getDescription());
    }

    public Drawable getAvatarImageDrawable() {
        return mNameCardContent.getAvatarImageDrawable();
    }

    public void setAvatarImageDrawable(Drawable avatarImageDrawable) {
        mNameCardContent.setAvatarImageDrawable(avatarImageDrawable);
        mImageViewAvator.setImageDrawable(avatarImageDrawable);
    }

    public String getName() {
        return mNameCardContent.getName();
    }

    public void setName(String name) {
        mNameCardContent.setName(name);
        mTextViewName.setText(name);
    }

    public String getDescription() {
        return mNameCardContent.getDescription();
    }

    public void setDescription(String description) {
        mNameCardContent.setDescription(description);
        mTextViewDescription.setText(description);
    }
}

 

四、使用

根据我们上面的所想要的效果,首先,为CardView编写子视图界面:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
    <android.support.v7.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/cardview"
        android:layout_gravity="center"
        card_view:cardCornerRadius="10dp"
        card_view:cardElevation="10dp"
        >
        <me.happyhls.androiddemo.view.NameCard
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/namecard">
        </me.happyhls.androiddemo.view.NameCard>
    </android.support.v7.widget.CardView>
</LinearLayout>

然后编写Activity对应的RecyclerView的界面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">
    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recycleview"
        android:layout_gravity="center"
        ></android.support.v7.widget.RecyclerView>
</LinearLayout>

最后,在Activity中初始化RecyclerView和数据,代码如下:

public class TestNameCard extends Activity {

    private static final String TAG = TestNameCard.class.getSimpleName();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.lollipop_recyclerviewandcardview);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycleview);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        List<NameCard.NameCardContent> items = new ArrayList<NameCard.NameCardContent>();
        Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher);
        for (int i = 0; i < 100; i++) {
            items.add(new NameCard.NameCardContent(drawable, "Name" + i, "Description" + i));
        }
        MyAdapter adapter = new MyAdapter(this, items);
        recyclerView.setAdapter(adapter);
    }

    static class MyAdapter extends RecyclerView.Adapter<ViewHolder> {

        private List<NameCard.NameCardContent> mItems;
        private LayoutInflater mLayoutInflater;

        public MyAdapter(Context context, List<NameCard.NameCardContent> items) {
            this.mItems = new ArrayList<NameCard.NameCardContent>(items);
            mLayoutInflater = LayoutInflater.from(context);
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
            Log.d(TAG, "Adapter creating view for " + position);
            View view = mLayoutInflater.inflate(R.layout.namecard_item, parent, false);
            ViewHolder viewHolder = new ViewHolder(view);
            viewHolder.mNameCard = (NameCard) view.findViewById(R.id.namecard);
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(ViewHolder viewHolder, int position) {
            Log.d(TAG, "Adapter binding view for " + position);
            viewHolder.mNameCard.setAvatarImageDrawable(mItems.get(position).getAvatarImageDrawable());
            viewHolder.mNameCard.setName(mItems.get(position).getName());
            viewHolder.mNameCard.setDescription(mItems.get(position).getDescription());
        }

        @Override
        public int getItemCount() {
            return mItems.size();
        }
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View view) {
            super(view);
        }

        NameCard mNameCard;
    }
}

 

思考:优化的空间?

需要注意的是,上面我们的实现中,仅仅是展示了组合视图的相关的原理和RecyclerView/CardView的使用,但实际的生产环境中不会这么简单,几个简单的点:

1、NameCard当中,我们为每一个名片都保存了Drawable,而且是强引用,同时我们观察代码,可以发现所有的DrawableActivity中的List中的NameCardContent里面,在上面的代码中,我们所有的Drawable都是指向同一个对象,因此不会占用太多的内容空间,但在实际应用当中,不同的人对应的头像必然是不同的,那这个时候就不能再这样使用了,否则会必然导致OOM。(解决办法,加入Cache,保存Drawable对应的地址或者Id)

2、关于视图层次,上面的代码中,NameCard是一个RelativeLayout,但我们注意到其中加载的namecard.xml仍然其中任然有一层RelativeLayout,其实是不需要的,多于的,由于这个视图会多次被解析,因此这样必然会严重影响加载速度,所以此处应该将namecard.xml最外层去掉RelativeLayout,设置为merge标签即可。

 

 

 

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注