Android自定義控件一之自定義組合控件

在Android中因為需求千奇百怪,官方也無法提供所有的控件給用戶,所以需要我們自定義控件來滿足需求。這里記錄一下如何自定義組合控件的步驟。
自定義組合控件就是將若干個官方提供的控件進行組合,形成一個新的控件來進行使用??煞譃橐韵聨讉€步驟:

  • 繼承自某個ViewGroup,比如LinearLayout或者FrameLayout。
  • 定義屬性、獲取屬性。
  • 加載組合的View,根據(jù)屬性對UI進行修改。
  • 處理相關(guān)的事件。
  • 對外暴露接口。

這里就以自定義一個InputNumber計數(shù)器來實際操作下,最終結(jié)果如圖所示:
screenshot-20221009-003803.png
// 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 就等價于設(shè)為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都可以設(shè)置點擊事件
        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目錄下創(chuàng)建了attrs.xml,內(nèi)容如下:

<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); // 注入監(jiān)聽器

    }
    
    @Override
    public void onValueChange(int value) {
        // 由于實現(xiàn)了InputNumView.OnValueChangeListener,并且設(shè)置了監(jiān)聽器,
        //所以可以在這個方法中對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>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容