在Android中因为需求千奇百怪,官方也无法提供所有的控件给用户,所以需要我们自定义控件来满足需求。这里记录一下如何自定义组合控件的步骤。
自定义组合控件就是将若干个官方提供的控件进行组合,形成一个新的控件来进行使用。可分为以下几个步骤:
- 继承自某个ViewGroup,比如LinearLayout或者FrameLayout。
- 定义属性、获取属性。
- 加载组合的View,根据属性对UI进行修改。
- 处理相关的事件。
- 对外暴露接口。
// 1. 继承自某个ViewGroup
public class InputNumView extends FrameLayout {
// 2.1 定义属性
private int mCurrentValue;
private int mPrevValue;
private TextView minusBtn;
private TextView plusBtn;
private EditText inputValue;
private OnValueChangeListener listener;
private int mMax;
private int mMin;
private int mStep;
private boolean mDisable;
private int mDefValue;
public InputNumView(@NonNull Context context) {
this(context, null);
}
public InputNumView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public InputNumView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.view_input_number, this, true);
// if attachToRoot = true 就等价于设为false,然后使用addView(view)
// final View view = LayoutInflater.from(context).inflate(R.layout.view_input_number, this, false);
// addView(view);
initView(context);
initAttrs(attrs, context);
setUpEvent();
}
// 2.2 获取属性
private void initAttrs(AttributeSet attrs, Context context) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.InputNumView);
mMax = a.getInt(R.styleable.InputNumView_max, 100);
mMin = a.getInt(R.styleable.InputNumView_min, 0);
mDefValue = a.getInt(R.styleable.InputNumView_defValue, 0);
mStep = a.getInt(R.styleable.InputNumView_step, 3);
mDisable = a.getBoolean(R.styleable.InputNumView_disable, false);
mCurrentValue = mDefValue;
updateValueText();
}
// 4. 处理事件
private void setUpEvent() {
minusBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mPrevValue = mCurrentValue;
mCurrentValue -= mStep;
if(mCurrentValue < mMin) {
mCurrentValue = mMin;
}
if(mPrevValue != mCurrentValue) {
if (listener != null) {
listener.onValueChange(mCurrentValue);
}
updateValueText();
}
}
});
plusBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mPrevValue = mCurrentValue;
mCurrentValue += mStep;
if(mCurrentValue > mMax) {
mCurrentValue = mMax;
}
if(mPrevValue != mCurrentValue) {
if (listener != null) {
listener.onValueChange(mCurrentValue);
}
updateValueText();
}
}
});
minusBtn.setEnabled(!mDisable);
plusBtn.setEnabled(!mDisable);
}
private void updateValueText() {
inputValue.setText(String.valueOf(mCurrentValue));
}
// 3. 加载组合的View
private void initView(Context context) {
// 任何的view都可以设置点击事件
minusBtn = findViewById(R.id.minus_tv);
plusBtn = findViewById(R.id.plus_tv);
inputValue = findViewById(R.id.input_value_et);
}
// 5. 对外暴露的接口
public void setOnValueChangeListener(OnValueChangeListener listener) {
this.listener = listener;
}
public interface OnValueChangeListener {
void onValueChange(int value);
}
}
对应的布局文件:
<!-- view_input_number.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="40dp">
<TextView
android:id="@+id/minus_tv"
android:layout_width="40dp"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="24sp"
android:background="@drawable/selector_view_btn_bg_left"
android:text="-" />
<EditText
android:id="@+id/input_value_et"
android:layout_width="40dp"
android:layout_height="match_parent"
android:background="@drawable/view_input_bg"
android:focusable="false"
android:gravity="center" />
<TextView
android:id="@+id/plus_tv"
android:layout_width="40dp"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="24sp"
android:background="@drawable/selector_view_btn_bg_right"
android:text="+" />
</LinearLayout>
需要注意的是,在定义属性时同时需要在xml文件中进行声明。我们在values目录下创建了attrs.xml,内容如下:
<resources>
<declare-styleable name="InputNumView">
<attr name="max" format="integer" />
<attr name="min" format="integer" />
<attr name="defValue" format="integer" />
<attr name="step" format="integer" />
<attr name="disable" format="boolean" />
</declare-styleable>
</resources>
只有对这些属性进行声明才可以进行属性的定义和获取。
最后给出MainActivity以及主页面的布局代码:
public class MainActivity extends AppCompatActivity implements InputNumView.OnValueChangeListener {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InputNumView inputNumView = findViewById(R.id.input_number_view);
inputNumView.setOnValueChangeListener(this); // 注入监听器
}
@Override
public void onValueChange(int value) {
// 由于实现了InputNumView.OnValueChangeListener,并且设置了监听器,
//所以可以在这个方法中对InputNumView的值的变化作出反应,进行一些其他的逻辑处理。
Log.i(TAG, "onValueChange ===> " + value);
}
}
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:orientation="horizontal"
android:background="#00aaff"
tools:context=".MainActivity">
<com.example.customviewdemo.custom.InputNumView
app:defValue="24"
app:step="5"
app:max="100"
app:min="0"
app:disable="false"
android:id="@+id/input_number_view"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>