7

Android自定义EditText:手把手教你做一款含一键删除&自定义样式的SuperEditText

 3 years ago
source link: http://www.androidchina.net/7373.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  • Android开发中,EditText的使用 非常常见
  • 本文将手把手教你做一款 附带一键删除功能 & 自定义样式丰富的 SuperEditText控件,希望你们会喜欢。

效果图

已在Github开源:Super_EditText,欢迎 Star !


示意图


一款 附带一键删除功能 & 自定义样式丰富的 SuperEditText控件

已在Github开源:Super_EditText,欢迎 Star !

效果图


2. 功能介绍

2.1 需求场景

对于 EditText来说,一般的需求有:

  • 方便用户因出现输入错误而进行2次输入
  • 标识用户正在填写项
  • 根据具体场景增加一定的UI元素

2.2 功能需求

根据需求场景,得出EditText需要具备的功能如下:

  • 丰富的自定义样式:左侧图标、删除功能图标、分割线 & 光标 样式变化。具体如下图:

示意图

注:该样式的设置是系统自带的 API 所不具备的

示意图

2.3 功能示意

效果图


对比市面上EditText控件,该控件Super_EditText 的特点是:

3.1 功能实用

  • 一键删除功能 在需求中非常常见,现将其封装后更加方便使用
  • 可自定义样式程度高(比自带的强大 & 方便),不复杂却能满足一般的EditText使用需求

    可自定义样式如下:(注:该样式的设置是系统自带的 API 所不具备的)

示意图

3.2 使用简单

3.3 二次开发成本低

  • 本项目已在 Github上开源:Super_EditText
  • 具备详细的源码分析文档(即本文)

所以,在其上做二次开发 & 定制化成本非常低。


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. 完整源码地址

Carson_Ho的Github地址:Super_EditText


6. 具体使用

具体请看文章:Android自定义View:你需要一款简单实用的SuperEditText(一键删除&自定义样式)


7. 贡献代码

  • 希望你们能和我一起完善这款简单 & 好用的SuperEditText控件,具体请看:贡献代码说明
  • 关于该开源项目的意见 & 建议可在Issue上提出。欢迎 Star !

  • 相信你一定会喜欢上 这款简单 & 好用的SuperEditText控件

    已在Github上开源:Super_EditText,欢迎 Star !

效果图

转载请注明:Android开发中文站 » Android自定义EditText:手把手教你做一款含一键删除&自定义样式的SuperEditText


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK