Android 自定義視圖總結
[TOC]
很多在開發的過程中,經常會需要把某個UI視圖給單獨抽取出來,以便重復使用,下面舉個簡單例子,分析一下。
比如我們這邊有個這樣的視圖,如下所示,顯示一個訂單模塊中,經常顯示一個商品的信息、數量以及價格。

上面的顯示商品的實體是這樣的。
public class GoodsItem implements Serializable {
public String name;
public int count;
public double price;
@Override
public String toString() {
return "GoodsItem{" +
"name='" + name + '\'' +
", count=" + count +
", price=" + price +
'}';
}
}
GoodsItem goodsItem = new GoodsItem();
goodsItem.name = "可口可樂";
goodsItem.count = 123;
goodsItem.price = 321;
正常情況
正常情況下,我們如果只需要用一次,那么我們定義好布局就好了,然后簡單的賦值就好,下面是代碼。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp"
tools:showIn="@layout/activity_main">
<TextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tools:text="可口可樂" />
<TextView
android:id="@+id/tvCount"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:gravity="right"
tools:text="x1" />
<TextView
android:id="@+id/tvPrice"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:gravity="right"
tools:text="¥100" />
</LinearLayout>
// normal
TextView tvName = (TextView) findViewById(R.id.tvName);
TextView tvCount = (TextView) findViewById(R.id.tvCount);
TextView tvPrice = (TextView) findViewById(R.id.tvPrice);
tvName.setText(goodsItem.name);
tvCount.setText(String.format("x%s", goodsItem.count));
tvPrice.setText(String.format("¥%s", goodsItem.price));
上面是最簡單的方式,也是初學Android的時候常用的方式。
Databinding
Google官方給出了一個Databinding的方式,這樣我們代碼了里面就可以少些很多代碼,在對上面的代碼進行少許優化后,就可以使用Databinding的方式。
<?xml version="1.0" encoding="UTF-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp"
tools:showIn="@layout/activity_main">
<TextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tools:text="可口可樂" />
<TextView
android:id="@+id/tvCount"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:gravity="right"
tools:text="x1" />
<TextView
android:id="@+id/tvPrice"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:gravity="right"
tools:text="¥100" />
</LinearLayout>
</layout>
// databind
binding.includeDatabinding.tvName.setText(goodsItem.name);
binding.includeDatabinding.tvCount.setText(String.format("x%s", goodsItem.count));
binding.includeDatabinding.tvPrice.setText(String.format("¥%s", goodsItem.price));
使用Databinding的最好好處,就不需要寫煩人的findViewById了。
Databinding升級
如果使用Databinding綁定的形式,那么賦值的方式就更加容易了,在定義xml的時候定義傳遞一個GoodsItem對象,然后在界面賦值一個對象就好了,簡單明了。
<?xml version="1.0" encoding="UTF-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="good"
type="cn.mycommons.goodsdemo.GoodsItem" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp"
tools:showIn="@layout/activity_main">
<TextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{good.name}"
tools:text="可口可樂" />
<TextView
android:id="@+id/tvCount"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:gravity="right"
android:text='@{"x"+good.count}'
tools:text="x1" />
<TextView
android:id="@+id/tvPrice"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:gravity="right"
android:text='@{"¥"+good.price}'
tools:text="¥100" />
</LinearLayout>
</layout>
// databind with param
binding.includeDatabindingWithParam.setGood(goodsItem);
自定義View
剛剛的示例比較,可以使用簡單復制就可以搞定,有時候,業務比較復制,可能還要處理手勢事件,如果使用Databinding就不怎么方便了。
所以自定義View是我們另外的一種方式。
首先我們頂一個自定義的View,這個view是專門用來顯示商品信息的。
public class GoodsItemView extends FrameLayout {
private TextView tvName, tvCount, tvPrice;
public GoodsItemView(Context context) {
super(context);
init();
}
public GoodsItemView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
void init() {
LayoutInflater.from(getContext()).inflate(R.layout.databinding, this);
tvName = (TextView) findViewById(R.id.tvName);
tvCount = (TextView) findViewById(R.id.tvCount);
tvPrice = (TextView) findViewById(R.id.tvPrice);
}
public void updateUI(GoodsItem goodsItem) {
tvName.setText(goodsItem.name);
tvCount.setText(String.format("x%s", goodsItem.count));
tvPrice.setText(String.format("¥%s", goodsItem.price));
}
}
然在布局中引入就可以了,最好在Activity中找到所對應的對象,最后賦值就可以了。
<cn.mycommons.goodsdemo.GoodsItemView
android:id="@+id/goodsItemView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
// custom view
GoodsItemView goodsItemView = (GoodsItemView) findViewById(R.id.goodsItemView);
goodsItemView.updateUI(goodsItem);
自定義Module
有時候一個自定義View會有所限制,比如,自定義View不能代碼混淆,而且自定義View會受到分類的限制。
有時候僅僅只是一處使用,單獨提取一個View代價太大,那么我們可以單獨自定義一個Module的方式,這樣做的話,可以減少Activity的代碼量。
話不多說,先看代碼。
首先商品展示界面還在定義在Activity的布局中。
<include
android:id="@+id/includeModule"
layout="@layout/databinding" />
然后我們定義一個通用的Module接口,以及實現類。
public interface IModule {
void create();
void destroy();
}
public class GoodsItemModule implements IModule {
@NonNull
private final View rootView;
private TextView tvName, tvCount, tvPrice;
public GoodsItemModule(@NonNull View rootView) {
this.rootView = rootView;
}
@Override
public void create() {
tvName = (TextView) rootView.findViewById(R.id.tvName);
tvCount = (TextView) rootView.findViewById(R.id.tvCount);
tvPrice = (TextView) rootView.findViewById(R.id.tvPrice);
}
public void updateUI(GoodsItem goodsItem) {
tvName.setText(goodsItem.name);
tvCount.setText(String.format("x%s", goodsItem.count));
tvPrice.setText(String.format("¥%s", goodsItem.price));
}
@Override
public void destroy() {
tvName = null;
tvCount = null;
tvPrice = null;
}
}
然后我們在Activity中,把商品展示的接的根視圖傳入到Module中,這樣對商品展示的所有邏輯都是寫在了GoodsItemModule中了,
這樣就減少了耦合,Activity的代碼量也比較少,同時 Module中定義了簡單的生命周期,可以在Activity中調用,方便管理。
// module
goodsItemModule = new GoodsItemModule(findViewById(R.id.includeModule));
goodsItemModule.create();
goodsItemModule.updateUI(goodsItem);
// 這一段可以在Activity.onDestroy中執行
goodsItemModule.destroy();
Fragment
看到上面了方法,是不是想到了Fragment,其實Fragment也是Google提供的自定義Module的一種實現。Fragment提供更加完善的生命周期,不過也是一個值得吐槽的地方。
導致很多實用Fragment的時候不知道怎么用,以及實用過于復雜。
下面展示下Fragment的使用。
public class GoodsItemFragment extends Fragment {
static final String EXTRA_GOODS_ITEM = "goods_item";
public static GoodsItemFragment newFragment(GoodsItem goodsItem) {
GoodsItemFragment fragment = new GoodsItemFragment();
Bundle args = new Bundle();
args.putSerializable(EXTRA_GOODS_ITEM, goodsItem);
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.databinding, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView tvName = (TextView) view.findViewById(R.id.tvName);
TextView tvCount = (TextView) view.findViewById(R.id.tvCount);
TextView tvPrice = (TextView) view.findViewById(R.id.tvPrice);
GoodsItem goodsItem = (GoodsItem) getArguments().getSerializable(EXTRA_GOODS_ITEM);
if (goodsItem != null) {
tvName.setText(goodsItem.name);
tvCount.setText(String.format("x%s", goodsItem.count));
tvPrice.setText(String.format("¥%s", goodsItem.price));
}
}
}
以及Activity中的調用方式。
// fragment
getSupportFragmentManager()
.beginTransaction()
.add(binding.fragmentContainer.getId(), GoodsItemFragment.newFragment(goodsItem))
.commit();
總結
上面展示了Android中自定義視圖模塊的常用方式,下面進行比較和總結:
- 正常情況 : 簡單明了,所有的開發人員都會,只不過代碼比較冗余,不利于解耦。
- Databinding : 簡單明了,減少繁瑣的findViewById代碼,而且方便。
- Databinding升級 : 更加簡單明了,不過有少許的學習成本,不過現在已經是Android程序員的基本技能了。
- 自定義View : 代碼稍微復雜,開發繁瑣,同時自定義View不能進行代碼混淆,可以根據View的名字,猜想邏輯。
- 自定義Module : 這種方式,簡單粗暴,學習成本比較低,小項目內可以自由使用,不過不利于推廣。
- Fragment : 高級的自定義Module方式,有學習成本,利于推廣和維護。
上面的自定義View和自定義Module是兩種程序的實習方式,有句話叫做組合由于繼承,所以有時候,我會選擇后者。
個人的喜愛程度是這樣,當然有時候會根據項目的實際情況進行選擇,也有時候會進行組合使用。
Databinding升級 = Databinding > Fragment > 自定義Module > 正常情況 > 自定義View
以上就是作者對Android自定義視圖總結,歡迎前來討論。