0%

Android 自定义视图总结

Android 自定义视图总结


很多在开发的过程中,经常会需要把某个UI视图给单独抽取出来,以便重复使用,下面举个简单例子,分析一下。

比如我们这边有个这样的视图,如下所示,显示一个订单模块中,经常显示一个商品的信息、数量以及价格。

上面的显示商品的实体是这样的。

1
public class GoodsItem implements Serializable {
2
3
    public String name;
4
5
    public int count;
6
7
    public double price;
8
9
    @Override
10
    public String toString() {
11
        return "GoodsItem{" +
12
                "name='" + name + '\'' +
13
                ", count=" + count +
14
                ", price=" + price +
15
                '}';
16
    }
17
}
18
19
20
GoodsItem goodsItem = new GoodsItem();
21
goodsItem.name = "可口可乐";
22
goodsItem.count = 123;
23
goodsItem.price = 321;

正常情况

正常情况下,我们如果只需要用一次,那么我们定义好布局就好了,然后简单的赋值就好,下面是代码。

1
<LinearLayout
2
    android:layout_width="match_parent"
3
    android:layout_height="50dp"
4
    android:gravity="center"
5
    android:orientation="horizontal"
6
    android:padding="10dp"
7
    tools:showIn="@layout/activity_main">
8
9
    <TextView
10
        android:id="@+id/tvName"
11
        android:layout_width="0dp"
12
        android:layout_height="wrap_content"
13
        android:layout_weight="1"
14
        tools:text="可口可乐" />
15
16
    <TextView
17
        android:id="@+id/tvCount"
18
        android:layout_width="80dp"
19
        android:layout_height="wrap_content"
20
        android:gravity="right"
21
        tools:text="x1" />
22
23
    <TextView
24
        android:id="@+id/tvPrice"
25
        android:layout_width="80dp"
26
        android:layout_height="wrap_content"
27
        android:gravity="right"
28
        tools:text="¥100" />
29
</LinearLayout>
1
// normal
2
TextView tvName = (TextView) findViewById(R.id.tvName);
3
TextView tvCount = (TextView) findViewById(R.id.tvCount);
4
TextView tvPrice = (TextView) findViewById(R.id.tvPrice);
5
tvName.setText(goodsItem.name);
6
tvCount.setText(String.format("x%s", goodsItem.count));
7
tvPrice.setText(String.format("¥%s", goodsItem.price));

上面是最简单的方式,也是初学Android的时候常用的方式。

Databinding

Google官方给出了一个Databinding的方式,这样我们代码了里面就可以少些很多代码,在对上面的代码进行少许优化后,就可以使用Databinding的方式。

1
<?xml version="1.0" encoding="UTF-8"?>
2
<layout xmlns:android="http://schemas.android.com/apk/res/android"
3
    xmlns:tools="http://schemas.android.com/tools">
4
5
    <LinearLayout
6
        android:layout_width="match_parent"
7
        android:layout_height="50dp"
8
        android:gravity="center"
9
        android:orientation="horizontal"
10
        android:padding="10dp"
11
        tools:showIn="@layout/activity_main">
12
13
        <TextView
14
            android:id="@+id/tvName"
15
            android:layout_width="0dp"
16
            android:layout_height="wrap_content"
17
            android:layout_weight="1"
18
            tools:text="可口可乐" />
19
20
        <TextView
21
            android:id="@+id/tvCount"
22
            android:layout_width="80dp"
23
            android:layout_height="wrap_content"
24
            android:gravity="right"
25
            tools:text="x1" />
26
27
        <TextView
28
            android:id="@+id/tvPrice"
29
            android:layout_width="80dp"
30
            android:layout_height="wrap_content"
31
            android:gravity="right"
32
            tools:text="¥100" />
33
    </LinearLayout>
34
</layout>
1
// databind
2
binding.includeDatabinding.tvName.setText(goodsItem.name);
3
binding.includeDatabinding.tvCount.setText(String.format("x%s", goodsItem.count));
4
binding.includeDatabinding.tvPrice.setText(String.format("¥%s", goodsItem.price));

使用Databinding的最好好处,就不需要写烦人的findViewById了。

Databinding升级

如果使用Databinding绑定的形式,那么赋值的方式就更加容易了,在定义xml的时候定义传递一个GoodsItem对象,然后在界面赋值一个对象就好了,简单明了。

1
<?xml version="1.0" encoding="UTF-8"?>
2
<layout xmlns:android="http://schemas.android.com/apk/res/android"
3
    xmlns:tools="http://schemas.android.com/tools">
4
5
    <data>
6
7
        <variable
8
            name="good"
9
            type="cn.mycommons.goodsdemo.GoodsItem" />
10
    </data>
11
12
    <LinearLayout
13
        android:layout_width="match_parent"
14
        android:layout_height="50dp"
15
        android:gravity="center"
16
        android:orientation="horizontal"
17
        android:padding="10dp"
18
        tools:showIn="@layout/activity_main">
19
20
        <TextView
21
            android:id="@+id/tvName"
22
            android:layout_width="0dp"
23
            android:layout_height="wrap_content"
24
            android:layout_weight="1"
25
            android:text="@{good.name}"
26
            tools:text="可口可乐" />
27
28
        <TextView
29
            android:id="@+id/tvCount"
30
            android:layout_width="80dp"
31
            android:layout_height="wrap_content"
32
            android:gravity="right"
33
            android:text='@{"x"+good.count}'
34
            tools:text="x1" />
35
36
        <TextView
37
            android:id="@+id/tvPrice"
38
            android:layout_width="80dp"
39
            android:layout_height="wrap_content"
40
            android:gravity="right"
41
            android:text='@{"¥"+good.price}'
42
            tools:text="¥100" />
43
    </LinearLayout>
44
</layout>
1
// databind with param
2
binding.includeDatabindingWithParam.setGood(goodsItem);

自定义View

刚刚的示例比较,可以使用简单复制就可以搞定,有时候,业务比较复制,可能还要处理手势事件,如果使用Databinding就不怎么方便了。
所以自定义View是我们另外的一种方式。

首先我们顶一个自定义的View,这个view是专门用来显示商品信息的。

1
public class GoodsItemView extends FrameLayout {
2
3
    private TextView tvName, tvCount, tvPrice;
4
5
    public GoodsItemView(Context context) {
6
        super(context);
7
8
        init();
9
    }
10
11
    public GoodsItemView(Context context, AttributeSet attrs) {
12
        super(context, attrs);
13
14
        init();
15
    }
16
17
    void init() {
18
        LayoutInflater.from(getContext()).inflate(R.layout.databinding, this);
19
20
        tvName = (TextView) findViewById(R.id.tvName);
21
        tvCount = (TextView) findViewById(R.id.tvCount);
22
        tvPrice = (TextView) findViewById(R.id.tvPrice);
23
    }
24
25
    public void updateUI(GoodsItem goodsItem) {
26
        tvName.setText(goodsItem.name);
27
        tvCount.setText(String.format("x%s", goodsItem.count));
28
        tvPrice.setText(String.format("¥%s", goodsItem.price));
29
    }
30
}

然在布局中引入就可以了,最好在Activity中找到所对应的对象,最后赋值就可以了。

1
<cn.mycommons.goodsdemo.GoodsItemView
2
    android:id="@+id/goodsItemView"
3
    android:layout_width="match_parent"
4
    android:layout_height="wrap_content" />
5
    
6
// custom view
7
GoodsItemView goodsItemView = (GoodsItemView) findViewById(R.id.goodsItemView);
8
goodsItemView.updateUI(goodsItem);

自定义Module

有时候一个自定义View会有所限制,比如,自定义View不能代码混淆,而且自定义View会受到分类的限制。
有时候仅仅只是一处使用,单独提取一个View代价太大,那么我们可以单独自定义一个Module的方式,这样做的话,可以减少Activity的代码量。
话不多说,先看代码。

首先商品展示界面还在定义在Activity的布局中。

1
<include
2
    android:id="@+id/includeModule"
3
    layout="@layout/databinding" />

然后我们定义一个通用的Module接口,以及实现类。

1
public interface IModule {
2
3
    void create();
4
    void destroy();
5
}
6
7
public class GoodsItemModule implements IModule {
8
    @NonNull
9
    private final View rootView;
10
    private TextView tvName, tvCount, tvPrice;
11
12
    public GoodsItemModule(@NonNull View rootView) {
13
        this.rootView = rootView;
14
    }
15
16
    @Override
17
    public void create() {
18
        tvName = (TextView) rootView.findViewById(R.id.tvName);
19
        tvCount = (TextView) rootView.findViewById(R.id.tvCount);
20
        tvPrice = (TextView) rootView.findViewById(R.id.tvPrice);
21
    }
22
23
    public void updateUI(GoodsItem goodsItem) {
24
        tvName.setText(goodsItem.name);
25
        tvCount.setText(String.format("x%s", goodsItem.count));
26
        tvPrice.setText(String.format("¥%s", goodsItem.price));
27
    }
28
29
    @Override
30
    public void destroy() {
31
        tvName = null;
32
        tvCount = null;
33
        tvPrice = null;
34
    }
35
}

然后我们在Activity中,把商品展示的接的根视图传入到Module中,这样对商品展示的所有逻辑都是写在了GoodsItemModule中了,
这样就减少了耦合,Activity的代码量也比较少,同时 Module中定义了简单的生命周期,可以在Activity中调用,方便管理。

1
// module
2
goodsItemModule = new GoodsItemModule(findViewById(R.id.includeModule));
3
goodsItemModule.create();
4
goodsItemModule.updateUI(goodsItem);
5
6
// 这一段可以在Activity.onDestroy中执行
7
goodsItemModule.destroy();

Fragment

看到上面了方法,是不是想到了Fragment,其实Fragment也是Google提供的自定义Module的一种实现。Fragment提供更加完善的生命周期,不过也是一个值得吐槽的地方。
导致很多实用Fragment的时候不知道怎么用,以及实用过于复杂。

下面展示下Fragment的使用。

1
public class GoodsItemFragment extends Fragment {
2
3
    static final String EXTRA_GOODS_ITEM = "goods_item";
4
5
    public static GoodsItemFragment newFragment(GoodsItem goodsItem) {
6
        GoodsItemFragment fragment = new GoodsItemFragment();
7
        Bundle args = new Bundle();
8
        args.putSerializable(EXTRA_GOODS_ITEM, goodsItem);
9
        fragment.setArguments(args);
10
        return fragment;
11
    }
12
13
    @Nullable
14
    @Override
15
    public View onCreateView(LayoutInflater inflater,
16
                             @Nullable ViewGroup container,
17
                             @Nullable Bundle savedInstanceState) {
18
        return inflater.inflate(R.layout.databinding, container, false);
19
    }
20
21
    @Override
22
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
23
        super.onViewCreated(view, savedInstanceState);
24
25
       TextView tvName = (TextView) view.findViewById(R.id.tvName);
26
       TextView tvCount = (TextView) view.findViewById(R.id.tvCount);
27
       TextView tvPrice = (TextView) view.findViewById(R.id.tvPrice);
28
29
        GoodsItem goodsItem = (GoodsItem) getArguments().getSerializable(EXTRA_GOODS_ITEM);
30
        if (goodsItem != null) {
31
            tvName.setText(goodsItem.name);
32
            tvCount.setText(String.format("x%s", goodsItem.count));
33
            tvPrice.setText(String.format("¥%s", goodsItem.price));
34
        }
35
    }
36
}

以及Activity中的调用方式。

1
// fragment
2
getSupportFragmentManager()
3
        .beginTransaction()
4
        .add(binding.fragmentContainer.getId(), GoodsItemFragment.newFragment(goodsItem))
5
        .commit();

总结

上面展示了Android中自定义视图模块的常用方式,下面进行比较和总结:

  • 正常情况 : 简单明了,所有的开发人员都会,只不过代码比较冗余,不利于解耦。
  • Databinding : 简单明了,减少繁琐的findViewById代码,而且方便。
  • Databinding升级 : 更加简单明了,不过有少许的学习成本,不过现在已经是Android程序员的基本技能了。
  • 自定义View : 代码稍微复杂,开发繁琐,同时自定义View不能进行代码混淆,可以根据View的名字,猜想逻辑。
  • 自定义Module : 这种方式,简单粗暴,学习成本比较低,小项目内可以自由使用,不过不利于推广。
  • Fragment : 高级的自定义Module方式,有学习成本,利于推广和维护。

上面的自定义View和自定义Module是两种程序的实习方式,有句话叫做组合由于继承,所以有时候,我会选择后者。

个人的喜爱程度是这样,当然有时候会根据项目的实际情况进行选择,也有时候会进行组合使用。

Databinding升级 = Databinding > Fragment > 自定义Module > 正常情况 > 自定义View

以上就是作者对Android自定义视图总结,欢迎前来讨论。

示例代码地址

坚持原创技术分享,您的支持将鼓励我继续创作!