2017年5月9日 星期二

Android Custom Component

如下圖,這是一個購物車常見的購買份數選擇。
將「減」按鈕、「加」按鈕及中間的數值,做成一個Component
這樣的好處是你的Activity會比較乾淨,加減Button的Click事件及邏輯在component處理,而不在Activity。

在做單元測試時,也會較方便,單獨針對這個Component測試就好。



新增一個 NumberSelect Layout


這個Layout裡有一個「-」Button、「+」Button、TextView數字
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/number_select_background">
<Button
android:id="@+id/minusButton"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:padding="0dp"
android:text="-"
android:textSize="32sp" />
<TextView
android:id="@+id/valueTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:width="30dp"
android:gravity="center"
android:layout_gravity="center_horizontal|center_vertical"
android:textColor="@color/colorPrimary" />
<Button
android:id="@+id/addButton"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:padding="0dp"
android:text="+"
android:textSize="32sp" />
</LinearLayout>

設定custom attributes


在 values 裡新增 attrs.xml ,分別是min_value最小值、max_value最大值、default_value預設值。

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumberSelect">
<attr name="default_value" format="integer" />
<attr name="min_value" format="integer" />
<attr name="max_value" format="integer" />
</declare-styleable>
</resources>

在Layout 設定屬性


attrs.xml 這裡設定好了之後,就可直接在layout上設定defaultValue、minValue、maxValue屬性。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:context="evan.chen.app.componentsample.MainActivity">
<evan.chen.app.componentsample.NumberSelect
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/number_select"
app:default_value="3"
app:min_value="0"
app:max_value="20"
/>
</android.support.constraint.ConstraintLayout>

建立NumberSelect.java


這段程式有點長,見下方的說明。
public class NumberSelect extends LinearLayout {
Button addButton;
Button minusButton;
TextView valueTextView;
private int minValue;
private int maxValue;
private int defaultValue;
private int textValue;
public interface NumberSelectListener {
public void onValueChange(int value);
}
private NumberSelectListener listener;
public NumberSelect(Context context) {
super(context);
init(context, null);
}
public NumberSelect(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public NumberSelect(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
View.inflate(context, R.layout.number_select, this);
setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
this.addButton = (Button) findViewById(R.id.addButton);
this.minusButton = (Button) findViewById(R.id.minusButton);
this.valueTextView = (TextView) findViewById(R.id.valueTextView);
this.textValue = 0;
this.maxValue = Integer.MAX_VALUE;
this.minValue = 0;
if (attrs != null) {
TypedArray attributes = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.NumberSelect,
0, 0);
this.maxValue = attributes.getInt(R.styleable.NumberSelect_max_value, this.maxValue);
this.minValue = attributes.getInt(R.styleable.NumberSelect_min_value, this.minValue);
this.defaultValue = attributes.getInt(R.styleable.NumberSelect_default_value, 0);
this.valueTextView.setText(String.valueOf(defaultValue));
}
this.addButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
addTextValue();
if ( listener != null) {
listener.onValueChange(textValue);
}
}
});
this.minusButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
minusTextValue();
if ( listener != null) {
listener.onValueChange(textValue);
}
}
});
}
public void setMaxValue(int value) {
this.maxValue = value;
}
public void setMinValue(int value) {
this.minValue = value;
}
public void setDefaultValue(int value) {
this.defaultValue = value;
}
private void addTextValue(){
if ( this.textValue < this.maxValue) {
this.textValue++;
this.valueTextView.setText(String.valueOf(this.textValue));
}
}
private void minusTextValue(){
if ( this.textValue > this.minValue) {
this.textValue--;
this.valueTextView.setText(String.valueOf(this.textValue));
}
}
public void setListener(NumberSelectListener listener) {
this.listener = listener;
}
}

在init 裡,取得在layout的min_value、max_value、default_value屬性。


private void init(Context context, AttributeSet attrs) {
View.inflate(context, R.layout.number_select, this);
setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
this.addButton = (Button) findViewById(R.id.addButton);
this.minusButton = (Button) findViewById(R.id.minusButton);
this.valueTextView = (TextView) findViewById(R.id.valueTextView);
this.textValue = 0;
this.maxValue = Integer.MAX_VALUE;
this.minValue = 0;
//get Attributes from component layout
if (attrs != null) {
TypedArray attributes = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.NumberSelect,
0, 0);
this.maxValue = attributes.getInt(R.styleable.NumberSelect_max_value, this.maxValue);
this.minValue = attributes.getInt(R.styleable.NumberSelect_min_value, this.minValue);
this.defaultValue = attributes.getInt(R.styleable.NumberSelect_default_value, 0);
this.valueTextView.setText(String.valueOf(defaultValue));
}
}

設定addButton、minusButton,加減份數時,異動中間的數值及設定callback


this.addButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
addTextValue();
if ( listener != null) {
listener.onValueChange(textValue);
}
}
});
this.minusButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
minusTextValue();
if ( listener != null) {
listener.onValueChange(textValue);
}
}
});

使用custom component


activity_main.xml 加入NumberSelect Custom Component


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:context="evan.chen.app.componentsample.MainActivity">
<evan.chen.app.componentsample.NumberSelect
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/number_select"
app:default_value="3"
app:min_value="0"
app:max_value="20"
/>
</android.support.constraint.ConstraintLayout>

完整程式


https://github.com/evanchen76/ComponentSample

參考

Custom Components

4 則留言:

  1. 天呀,你真的是我的救星
    卡了兩天怎麼都找不到的解決方案
    終於在此找到,太感謝你了!

    回覆刪除
    回覆
    1. 謝謝,也歡迎到我的新網誌有更多文章喔。
      https://medium.com/@evanchen76

      刪除
  2. 這幾天在研究ViewGroup, 這篇真是淺顯易懂!

    回覆刪除
    回覆
    1. 謝謝,也歡迎到我的新網誌有更多文章喔。
      https://medium.com/@evanchen76

      刪除