1. 程式人生 > >Android自定義EditText:手把手教你做一款含一鍵刪除&自定義樣式的SuperEditText

Android自定義EditText:手把手教你做一款含一鍵刪除&自定義樣式的SuperEditText

前言

  • Android開發中,EditText的使用 非常常見
  • 本文將手把手教你做一款 附帶一鍵刪除功能 & 自定義樣式豐富SuperEditText控制元件,希望你們會喜歡。

效果圖

目錄

示意圖

1. 簡介

一款 附帶一鍵刪除功能 & 自定義樣式豐富SuperEditText控制元件

效果圖

2. 功能介紹

2.1 需求場景

對於 EditText來說,一般的需求有:

  • 方便使用者因出現輸入錯誤而進行2次輸入
  • 標識使用者正在填寫項
  • 根據具體場景增加一定的UI元素

2.2 功能需求

根據需求場景,得出EditText需要具備的功能如下:

  • 一鍵刪除
  • 豐富的自定義樣式:左側圖示、刪除功能圖示、分割線 & 游標 樣式變化。具體如下圖:

示意圖

注:該樣式的設定是系統自帶的 API 所不具備的

  • 功能列表

示意圖

2.3 功能示意

效果圖

3. 特點

對比市面上EditText控制元件,該控制元件Super_EditText 的特點是:

3.1 功能實用

  • 一鍵刪除功能 在需求中非常常見,現將其封裝後更加方便使用
  • 可自定義樣式程度高(比自帶的強大 & 方便),不復雜卻能滿足一般的EditText使用需求

    可自定義樣式如下:(注:該樣式的設定是系統自帶的 API 所不具備的)


示意圖

3.2 使用簡單

3.3 二次開發成本低

  • 具備詳細的原始碼分析文件(即本文)

所以,在其上做二次開發 & 定製化成本非常低。

4. 功能詳細設計

下面將給出詳細的功能邏輯

4.1 一鍵清空輸入欄位

  • 描述:將當前使用者輸入的欄位清空
  • 需求場景:方便使用者因出現輸入錯誤而進行2次輸入
  • 原型圖

示意圖

  • 原始碼分析
   /*
    * 步驟1:定義屬性
    * */

    private int  ic_deleteResID; // 刪除圖示 資源ID
    private Drawable  ic_delete; // 刪除圖示
    private
int delete_x,delete_y,delete_width,delete_height; // 刪除圖示起點(x,y)、刪除圖示寬、高(px) /* * 步驟2:初始化屬性 * */ private void init(Context context, AttributeSet attrs) { // 獲取控制元件資源 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SuperEditText); /** * 初始化刪除圖示 */ // 1. 獲取資源ID ic_deleteResID = typedArray.getResourceId(R.styleable.SuperEditText_ic_delete,R.drawable.delete); // 2. 根據資源ID獲取圖示資源(轉化成Drawable物件) ic_delete = getResources().getDrawable(ic_deleteResID); // 3. 設定圖示大小 // 起點(x,y)、寬= left_width、高 = left_height delete_x = typedArray.getInteger(R.styleable.SuperEditText_delete_x, 0); delete_y = typedArray.getInteger(R.styleable.SuperEditText_delete_y, 0); delete_width = typedArray.getInteger(R.styleable.SuperEditText_delete_width, 60); delete_height = typedArray.getInteger(R.styleable.SuperEditText_delete_height, 60); ic_delete.setBounds(delete_x, delete_y, delete_width, delete_height); /** * 步驟3:通過監聽複寫EditText本身的方法來確定是否顯示刪除圖示 * 監聽方法:onTextChanged() & onFocusChanged() * 呼叫時刻:當輸入框內容變化時 & 焦點發生變化時 */ @Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { super.onTextChanged(text, start, lengthBefore, lengthAfter); setDeleteIconVisible(hasFocus() && text.length() > 0,hasFocus()); // hasFocus()返回是否獲得EditTEXT的焦點,即是否選中 // setDeleteIconVisible() = 根據傳入的是否選中 & 是否有輸入來判斷是否顯示刪除圖示->>關注1 } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); setDeleteIconVisible(focused && length() > 0,focused); // focused = 是否獲得焦點 // 同樣根據setDeleteIconVisible()判斷是否要顯示刪除圖示->>關注1 } /** * 關注1 * 作用:判斷是否顯示刪除圖示 */ private void setDeleteIconVisible(boolean deleteVisible,boolean leftVisible) { setCompoundDrawables(leftVisible ? ic_left_click : ic_left_unclick, null, deleteVisible ? ic_delete: null, null); // setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)介紹 // 作用:在EditText上、下、左、右設定圖示(相當於android:drawableLeft="" android:drawableRight="") // 備註:傳入的Drawable物件必須已經setBounds(x,y,width,height),即必須設定過初始位置、寬和高等資訊 // x:元件在容器X軸上的起點 y:元件在容器Y軸上的起點 width:元件的長度 height:元件的高度 // 若不想在某個地方顯示,則設定為null // 另外一個相似的方法:setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom) // 作用:在EditText上、下、左、右設定圖示 // 與setCompoundDrawables的區別:setCompoundDrawablesWithIntrinsicBounds()傳入的Drawable的寬高=固有寬高(自動通過getIntrinsicWidth()& getIntrinsicHeight()獲取) // 不需要設定setBounds(x,y,width,height) } /** * 步驟4:對刪除圖示區域設定點選事件,即"點選 = 清空搜尋框內容" * 原理:當手指擡起的位置在刪除圖示的區域,即視為點選了刪除圖示 = 清空搜尋框內容 */ @Override public boolean onTouchEvent(MotionEvent event) { // 原理:當手指擡起的位置在刪除圖示的區域,即視為點選了刪除圖示 = 清空搜尋框內容 switch (event.getAction()) { // 判斷動作 = 手指擡起時 case MotionEvent.ACTION_UP: Drawable drawable = ic_delete; if (drawable != null && event.getX() <= (getWidth() - getPaddingRight()) && event.getX() >= (getWidth() - getPaddingRight() - drawable.getBounds().width())) { // 判斷條件說明 // event.getX() :擡起時的位置座標 // getWidth():控制元件的寬度 // getPaddingRight():刪除圖示圖示右邊緣至EditText控制元件右邊緣的距離 // 即:getWidth() - getPaddingRight() = 刪除圖示的右邊緣座標 = X1 // getWidth() - getPaddingRight() - drawable.getBounds().width() = 刪除圖示左邊緣的座標 = X2 // 所以X1與X2之間的區域 = 刪除圖示的區域 // 當手指擡起的位置在刪除圖示的區域(X2=<event.getX() <=X1),即視為點選了刪除圖示 = 清空搜尋框內容 setText(""); } break; } return super.onTouchEvent(event); }

示意圖

4.2 選中樣式

  • 描述:通過增加UI元素 & 互動樣式表示使用者正在填寫的專案
  • 需求場景:標識使用者正在填寫項
  • 樣式說明

示意圖

  • 原型圖

示意圖

  • 屬性說明

示意圖

示意圖

  • 原始碼分析
   /*
    * 步驟1:定義屬性
    * */
    private Paint mPaint; // 畫筆
    private int  ic_left_clickResID,ic_left_unclickResID;    // 左側圖示 資源ID(點選 & 無點選)
    private Drawable  ic_left_click,ic_left_unclick; // 左側圖示(點選 & 未點選)
    private int left_x,left_y,left_width,left_height; // 左側圖示起點(x,y)、左側圖示寬、高(px)

    private int cursor; // 游標

    // 分割線變數
    private int lineColor_click,lineColor_unclick;// 點選時 & 未點選顏色
    private int color; 
    private int linePosition; // 分割線位置

   /*
    * 步驟2:初始化屬性
    * */
private void init(Context context, AttributeSet attrs) {

        // 獲取控制元件資源
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SuperEditText);

        /**
         * 初始化左側圖示(點選 & 未點選)
         */

        // a. 點選狀態的左側圖示
         // 1. 獲取資源ID
         ic_left_clickResID = typedArray.getResourceId(R.styleable.SuperEditText_ic_left_click, R.drawable.ic_left_click);
         // 2. 根據資源ID獲取圖示資源(轉化成Drawable物件)
         ic_left_click =  getResources().getDrawable(ic_left_clickResID);
         // 3. 設定圖示大小
         // 起點(x,y)、寬= left_width、高 = left_height
         left_x = typedArray.getInteger(R.styleable.SuperEditText_left_x, 0);
         left_y = typedArray.getInteger(R.styleable.SuperEditText_left_y, 0);
         left_width = typedArray.getInteger(R.styleable.SuperEditText_left_width, 60);
         left_height = typedArray.getInteger(R.styleable.SuperEditText_left_height, 60);

         ic_left_click.setBounds(left_x, left_y,left_width, left_height);
         // Drawable.setBounds(x,y,width,height) = 設定Drawable的初始位置、寬和高等資訊
         // x = 元件在容器X軸上的起點、y = 元件在容器Y軸上的起點、width=元件的長度、height = 元件的高度

        // b. 未點選狀態的左側圖示
         // 1. 獲取資源ID
         ic_left_unclickResID = typedArray.getResourceId(R.styleable.SuperEditText_ic_left_unclick, R.drawable.ic_left_unclick);
         // 2. 根據資源ID獲取圖示資源(轉化成Drawable物件)
         // 3. 設定圖示大小(此處預設左側圖示點解 & 未點選狀態的大小相同)
         ic_left_unclick =  getResources().getDrawable(ic_left_unclickResID);
         ic_left_unclick.setBounds(left_x, left_y,left_width, left_height);

        /**
         * 設定EditText左側圖片(初始狀態僅有左側圖片))
         */
        setCompoundDrawables( ic_left_unclick, null,
                null, null);

        // setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)介紹
        // 作用:在EditText上、下、左、右設定圖示(相當於android:drawableLeft=""  android:drawableRight="")
        // 備註:傳入的Drawable物件必須已經setBounds(x,y,width,height),即必須設定過初始位置、寬和高等資訊
        // x:元件在容器X軸上的起點 y:元件在容器Y軸上的起點 width:元件的長度 height:元件的高度
        // 若不想在某個地方顯示,則設定為null

        // 另外一個相似的方法:setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)
        // 作用:在EditText上、下、左、右設定圖示
        // 與setCompoundDrawables的區別:setCompoundDrawablesWithIntrinsicBounds()傳入的Drawable的寬高=固有寬高(自動通過getIntrinsicWidth()& getIntrinsicHeight()獲取)
        // 不需要設定setBounds(x,y,width,height)

        /**
         * 初始化游標(顏色 & 粗細)
         */
         // 原理:通過 反射機制 動態設定游標
         // 1. 獲取資源ID
         cursor = typedArray.getResourceId(R.styleable.SuperEditText_cursor, R.drawable.cursor);
         try {

            // 2. 通過反射 獲取游標屬性
            Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
            f.setAccessible(true);
            // 3. 傳入資源ID
            f.set(this, cursor);

         } catch (Exception e) {
            e.printStackTrace();
         }

        /**
         * 初始化分割線(顏色、粗細、位置)
         */
         // 1. 設定畫筆
         mPaint = new Paint();
         mPaint.setStrokeWidth(2.0f); // 分割線粗細

         // 2. 設定分割線顏色(使用十六進位制程式碼,如#333、#8e8e8e)
         int lineColorClick_default = context.getResources().getColor(R.color.lineColor_click); // 預設 = 藍色#1296db
         int lineColorunClick_default = context.getResources().getColor(R.color.lineColor_unclick); // 預設 = 灰色#9b9b9b
         lineColor_click = typedArray.getColor(R.styleable.SuperEditText_lineColor_click, lineColorClick_default);
         lineColor_unclick = typedArray.getColor(R.styleable.SuperEditText_lineColor_unclick, lineColorunClick_default);
         color = lineColor_unclick;

         mPaint.setColor(lineColor_unclick); // 分割線預設顏色 = 灰色
         setTextColor(color); // 字型預設顏色 = 灰色

         // 3. 分割線位置
         linePosition = typedArray.getInteger(R.styleable.SuperEditText_linePosition, 5);
         // 消除自帶下劃線
         setBackground(null);


   /**
     * 步驟3:通過監聽複寫EditText本身的方法來設定所有樣式
     * 監聽方法:onTextChanged() & onFocusChanged()
     * 呼叫時刻:當輸入框內容變化時 & 焦點發生變化時
     */
      @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        setDeleteIconVisible(hasFocus() && text.length() > 0,hasFocus());
        // hasFocus()返回是否獲得EditTEXT的焦點,即是否選中
        // setDeleteIconVisible() = 根據傳入的是否選中 & 是否有輸入來判斷是否顯示刪除圖示->>關注1
    }

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
        setDeleteIconVisible(focused && length() > 0,focused);
        // focused = 是否獲得焦點
        // 同樣根據setDeleteIconVisible()判斷是否要顯示刪除圖示->>關注1
    }

    /**
     * 關注1
     * 作用:設定分割線顏色
     */
    private void setDeleteIconVisible(boolean deleteVisible,boolean leftVisible) {
        color = leftVisible ? lineColor_click : lineColor_unclick;
        setTextColor(color);
        invalidate();
    }

   /**
     * 步驟4:繪製分割線
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(color);
        setTextColor(color);
        // 繪製分割線
        // 需要考慮:當輸入長度超過輸入框時,所畫的線需要跟隨著延伸
        // 解決方案:線的長度 = 控制元件長度 + 延伸後的長度
        int x=this.getScrollX(); // 獲取延伸後的長度
        int w=this.getMeasuredWidth(); // 獲取控制元件長度

        // 傳入引數時,線的長度 = 控制元件長度 + 延伸後的長度
                canvas.drawLine(0, this.getMeasuredHeight()- linePosition, w+x,
                        this.getMeasuredHeight() - linePosition, mPaint);

    }
}

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SuperEditText">

        <attr name="ic_delete" format="reference" />
        <attr name="delete_x" format="integer"  />
        <attr name="delete_y" format="integer"  />
        <attr name="delete_width" format="integer"  />
        <attr name="delete_height" format="integer"  />

        <attr name="ic_left_click" format="reference" />
        <attr name="ic_left_unclick" format="reference" />
        <attr name="left_x" format="integer"  />
        <attr name="left_y" format="integer"  />
        <attr name="left_width" format="integer"  />
        <attr name="left_height" format="integer"  />

        <attr name="lineColor_click" format="color"  />
        <attr name="lineColor_unclick" format="color"  />
        <attr name="linePosition" format="integer"  />

        <attr name="cursor" format="reference" />

    </declare-styleable>
</resources>

cursor.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="@color/lineColor_click" />
    <size android:width="1dp" />

</shape>

5. 完整原始碼地址

6. 具體使用

7. 貢獻程式碼

  • 希望你們能和我一起完善這款簡單 & 好用的SuperEditText控制元件,具體請看:貢獻程式碼說明
  • 關於該開源專案的意見 & 建議可在Issue上提出。歡迎 Star

8. 總結

  • 相信你一定會喜歡上 這款簡單 & 好用的SuperEditText控制元件

效果圖

請幫頂 或 評論點贊!因為你的鼓勵是我寫作的最大動力!