Android 如何自定义EditText 下划线?

项目要求:
笔者曾经做过一个项目,其中登录界面的交互令人印象深刻。交互设计师给出了一个非常作的设计,要求做出包含根据情况可变色的下划线,左侧有可变图标,右侧有可变删除标志的输入框,如图


Android 如何自定义EditText 下划线?

记录制作过程:

  • 第一版本

public class LineEditText extends EditText {

private Paint mPaint;
private int color;
public static final int STATUS_FOCUSED = 1;
public static final int STATUS_UNFOCUSED = 2;
public static final int STATUS_ERROR = 3;
private int status = 2;
private Drawable del_btn;
private Drawable del_btn_down;
private int focusedDrawableId = R.drawable.user_select;// 默认的
private int unfocusedDrawableId = R.drawable.user;
private int errorDrawableId = R.drawable.user_error;
Drawable left = null;
private Context mContext;

public LineEditText(Context context) {

    super(context);
    mContext = context;
    init();
}

public LineEditText(Context context, AttributeSet attrs) {

    super(context, attrs);
    mContext = context;
    init();

}

public LineEditText(Context context, AttributeSet attrs, int defStryle) {

    super(context, attrs, defStryle);
    mContext = context;
    TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.lineEdittext, defStryle, 0);
    focusedDrawableId = a.getResourceId(
            R.styleable.lineEdittext_drawableFocus, R.drawable.user_select);
    unfocusedDrawableId = a.getResourceId(
            R.styleable.lineEdittext_drawableUnFocus, R.drawable.user);
    errorDrawableId = a.getResourceId(
            R.styleable.lineEdittext_drawableError, R.drawable.user_error);
    a.recycle();
    init();
}

/**
* 2014/7/31
*
* @author Aimee.ZHANG
*/

private void init() {
    mPaint = new Paint();
    // mPaint.setStyle(Paint.Style.FILL);
    mPaint.setStrokeWidth(3.0f);
    color = Color.parseColor("#bfbfbf");
    setStatus(status);
    del_btn = mContext.getResources().getDrawable(R.drawable.del_but_bg);
    del_btn_down = mContext.getResources().getDrawable(R.drawable.del_but_bg_down);
    addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence arg0, int arg1, int arg2,
                int arg3) {
        }

        @Override
        public void beforeTextChanged(CharSequence arg0, int arg1,
                int arg2, int arg3) {
        }

        @Override
        public void afterTextChanged(Editable arg0) {
            setDrawable();
        }
    });
    setDrawable();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(color);
    canvas.drawLine(0, this.getHeight() - 1, this.getWidth(),
            this.getHeight() - 1, mPaint);
}

// 删除图片
private void setDrawable() {
    if (length() < 1) {
        setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn, null);
    } else {
        setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn_down,null);
    }
}

// 处理删除事件
@Override
public boolean onTouchEvent(MotionEvent event) {
    if (del_btn_down != null && event.getAction() == MotionEvent.ACTION_UP) {
        int eventX = (int) event.getRawX();
        int eventY = (int) event.getRawY();
        Log.e("eventXY", "eventX = " + eventX + "; eventY = " + eventY);  
        Rect rect = new Rect();
        getGlobalVisibleRect(rect);
        rect.left = rect.right - 50;
        if (rect.contains(eventX, eventY))
        setText("");
    }
    return super.onTouchEvent(event);
}

public void setStatus(int status) {
    this.status = status;
    

    if (status == STATUS_ERROR) {
        try {
            left = getResources().getDrawable(errorDrawableId);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        setColor(Color.parseColor("#f57272"));
    } else if (status == STATUS_FOCUSED) {
        try {
            left = getResources().getDrawable(focusedDrawableId);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        setColor(Color.parseColor("#5e99f3"));
    } else {
        try {
            left = getResources().getDrawable(unfocusedDrawableId);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        setColor(Color.parseColor("#bfbfbf"));
    }
    if (left != null) {

// left.setBounds(0, 0, 30, 40);

// this.setCompoundDrawables(left, null, null, null);

        setCompoundDrawablesWithIntrinsicBounds(left,null,del_btn,null);
    }
    postInvalidate();
}

public void setLeftDrawable(int focusedDrawableId, int unfocusedDrawableId,
        int errorDrawableId) {
    this.focusedDrawableId = focusedDrawableId;
    this.unfocusedDrawableId = unfocusedDrawableId;
    this.errorDrawableId = errorDrawableId;
    setStatus(status);
}

@Override
protected void onFocusChanged(boolean focused, int direction,
        Rect previouslyFocusedRect) {
    super.onFocusChanged(focused, direction, previouslyFocusedRect);
    if (focused) {
        setStatus(STATUS_FOCUSED);
    } else {
        setStatus(STATUS_UNFOCUSED);
    }
}

@Override
protected void finalize() throws Throwable {
    super.finalize();
};

public void setColor(int color) {
    this.color = color;
    this.setTextColor(color);
    invalidate();
}

}

效果图:


Android 如何自定义EditText 下划线?

代码解释:

变量名 STATUS_FOCUSED,STATUS_UNFOCUSED,STATUS_ERROR 标示了三种状态,选中状况为蓝色,未选中状态为灰色,错误状态为红色。focusedDrawableId unfocusedDrawableId errorDrawableId存放三种状态的图片,放置于最左侧。

canvas.drawLine(0, this.getHeight() - 1, this.getWidth(),this.getHeight() - 1, mPaint); //画editText最下方的线
setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn, null); //放置左边的和右边的图片(左,上,右,下)
相当于 android:drawableLeft="" android:drawableRight=""

  • onTouchEvent 当手机点击时,第一个先执行的函数,当点击右侧删除图标是清空 edittext
  • setStatus 根据不同的状态,左边的图片不一样

存在的问题:
这版本虽然基本功能已经实现,但是不符合需求,设计中要求文本框中无文字时,右侧删除按钮不显示,不点击删除按钮,删除按钮要保持灰色,点击时才可以变蓝色。

因此有了第二个版本

public class LineEditText extends EditText implements TextWatcher, OnFocusChangeListener{

private Paint mPaint;
private int color;
public static final int STATUS_FOCUSED = 1;
public static final int STATUS_UNFOCUSED = 2;
public static final int STATUS_ERROR = 3;
private int status = 2;
private Drawable del_btn;
private Drawable del_btn_down;
private int focusedDrawableId = R.drawable.user_select;// 默认的
private int unfocusedDrawableId = R.drawable.user;
private int errorDrawableId = R.drawable.user_error;
Drawable left = null;
private Context mContext;
/** 
 * 是否获取焦点,默认没有焦点 
 */  
private boolean hasFocus = false;  
/** 
 * 手指抬起时的X坐标 
 */  
private int xUp = 0;  

public LineEditText(Context context) {
    super(context);
    mContext = context;
    init();
}

public LineEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
    mContext = context;
    init();

}

public LineEditText(Context context, AttributeSet attrs, int defStryle) {
    super(context, attrs, defStryle);
    mContext = context;
    TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.lineEdittext, defStryle, 0);
    focusedDrawableId = a.getResourceId(
            R.styleable.lineEdittext_drawableFocus, R.drawable.user_select);
    unfocusedDrawableId = a.getResourceId(
            R.styleable.lineEdittext_drawableUnFocus, R.drawable.user);
    errorDrawableId = a.getResourceId(
            R.styleable.lineEdittext_drawableError, R.drawable.user_error);
    a.recycle();
    init();
}

/**
 * 2014/7/31
 * 
 * @author Aimee.ZHANG
 */
private void init() {
    mPaint = new Paint();
    // mPaint.setStyle(Paint.Style.FILL);
    mPaint.setStrokeWidth(3.0f);
    color = Color.parseColor("#bfbfbf");
    setStatus(status);
    del_btn = mContext.getResources().getDrawable(R.drawable.del_but_bg);
    del_btn_down = mContext.getResources().getDrawable(R.drawable.del_but_bg_down);
    addListeners();
    setCompoundDrawablesWithIntrinsicBounds(left, null, null, null);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(color);
    canvas.drawLine(0, this.getHeight() - 1, this.getWidth(),
            this.getHeight() - 1, mPaint);
}

// 删除图片

// private void setDrawable() {
// if (length() < 1) {
// setCompoundDrawablesWithIntrinsicBounds(left, null, null, null);
// } else {
// setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn,null);
// }
// }

// 处理删除事件
@Override
public boolean onTouchEvent(MotionEvent event) {
    if (del_btn != null && event.getAction() == MotionEvent.ACTION_UP) {
        // 获取点击时手指抬起的X坐标  
        xUp = (int) event.getX();  
        Log.e("xUp", xUp+"");  
        /*Rect rect = new Rect();
        getGlobalVisibleRect(rect);
        rect.left = rect.right - 50;*/
          // 当点击的坐标到当前输入框右侧的距离小于等于 getCompoundPaddingRight() 的距离时,则认为是点击了删除图标  
        if ((getWidth() - xUp) <= getCompoundPaddingRight()) {  
            if (!TextUtils.isEmpty(getText().toString())) {
                setText("");  
            }  
        }
    }else if(del_btn != null && event.getAction() == MotionEvent.ACTION_DOWN && getText().length()!=0){
        setCompoundDrawablesWithIntrinsicBounds(left,null,del_btn_down,null);
    }else if(getText().length()!=0){
        setCompoundDrawablesWithIntrinsicBounds(left,null,del_btn,null);
    }
    return super.onTouchEvent(event);
}

public void setStatus(int status) {
    this.status = status;
    

    if (status == STATUS_ERROR) {
        try {
            left = getResources().getDrawable(errorDrawableId);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        setColor(Color.parseColor("#f57272"));
    } else if (status == STATUS_FOCUSED) {
        try {
            left = getResources().getDrawable(focusedDrawableId);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        setColor(Color.parseColor("#5e99f3"));
    } else {
        try {
            left = getResources().getDrawable(unfocusedDrawableId);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        setColor(Color.parseColor("#bfbfbf"));
    }
    if (left != null) {

// left.setBounds(0, 0, 30, 40);
// this.setCompoundDrawables(left, null, null, null);
setCompoundDrawablesWithIntrinsicBounds(left,null,null,null);
}
postInvalidate();
}

public void setLeftDrawable(int focusedDrawableId, int unfocusedDrawableId,
        int errorDrawableId) {
    this.focusedDrawableId = focusedDrawableId;
    this.unfocusedDrawableId = unfocusedDrawableId;
    this.errorDrawableId = errorDrawableId;
    setStatus(status);
}
 private void addListeners() {  
        try {  
            setOnFocusChangeListener(this);  
            addTextChangedListener(this);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
@Override
protected void onFocusChanged(boolean focused, int direction,
        Rect previouslyFocusedRect) {
    super.onFocusChanged(focused, direction, previouslyFocusedRect);
    this.hasFocus=focused;
    if (focused) {
        setStatus(STATUS_FOCUSED);
    } else {
        setStatus(STATUS_UNFOCUSED);
        setCompoundDrawablesWithIntrinsicBounds(left,null,null,null);
    }
}

@Override
protected void finalize() throws Throwable {
    super.finalize();
};

public void setColor(int color) {
    this.color = color;
    this.setTextColor(color);
    invalidate();
}



@Override
public void afterTextChanged(Editable arg0) {
    // TODO Auto-generated method stub
    postInvalidate();
}

@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
        int arg3) {
    // TODO Auto-generated method stub
     if (TextUtils.isEmpty(arg0)) {  
         // 如果为空,则不显示删除图标  
         setCompoundDrawablesWithIntrinsicBounds(left, null, null, null);  
     } else {  
         // 如果非空,则要显示删除图标  
         setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn, null);  
     }  
}
@Override
 public void onTextChanged(CharSequence s, int start, int before, int after) {  
   if (hasFocus) {  
       if (TextUtils.isEmpty(s)) {  
           // 如果为空,则不显示删除图标  
           setCompoundDrawablesWithIntrinsicBounds(left, null, null, null);  
       } else {  
           // 如果非空,则要显示删除图标  
           setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn, null);  
       }  
   }  

}

@Override
public void onFocusChange(View arg0, boolean arg1) {
    // TODO Auto-generated method stub
    try {  
        this.hasFocus = arg1;  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}  

}

比较关键的方法是:onTouchEvent

当进入界面,点击输入框,要判断输入框中是否已有文字,如果有则显示灰色的删除按钮,如果没有则不显示,如果点击了删除按钮,删除按钮变蓝色

存在的问题:
这个版本依旧存在问题,就是输入长度超过输入框,所画的线不会延伸,如图

Android 如何自定义EditText 下划线?

解决方法:

@Override

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(color);
    int x=this.getScrollX();
    int w=this.getMeasuredWidth();
    canvas.drawLine(0, this.getHeight() - 1, w+x,
            this.getHeight() - 1, mPaint);
}

w:获取控件长度

X:延伸后的长度

最终效果:


Android 如何自定义EditText 下划线?

在分享完这个界面的代码设计后,笔者跟大家唠一些新玩意。话说身处在帝都,如果不利用好帝都的丰厚资源,又如何对得起每天吸入的几十斤雾霾?

话唠的分享

在帝都生活,我每天早晨起来都会告诉自己,又是新的一天,要认真过。

写一个 APP 很容易,写好一个 APP 很难。如何检验自己所写的 APP 的性能状况,用户体验?

什么是 APM?

In the fields of information technology and systems management, Application Performance Management (APM) is the monitoring and management of performance and availability of software applications. APM strives to detect and diagnose complex application performance problems to maintain an expected level of service. APM is "the translation of IT metrics into business meaning .

国内外有已很多成熟的 APM 厂商,笔者也曾染指过几家,如AppDynamics,Newrelic,OneAPM

还有专注于 APP 崩溃监控的产品:Crashlytics,Crittercism,Bugly等

今天我想给大家分享的是从OneAPM Mobile Insight 产品中发现的一块新大陆--卡顿监控

对流畅度的概念,相信大家并不陌生,即 1s 中之内绘图刷新信号中断的次数。流畅度次数越接近 40 时,用户能感知到卡顿,流畅度在 20以下卡顿比较严重。OneAPM Mobile Insight的卡顿监控就是一个监控流畅度指标的模块。

Android 如何自定义EditText 下划线?
  • 卡顿趋势图:随时间的推移,反馈卡顿发生次数的趋势情况
  • 设备分布图:卡顿现象集中分布的设备类型
  • 卡顿页面:发生卡顿的页面有哪些,其中平均流畅度是多少,卡顿了多少次等信息。
Android 如何自定义EditText 下划线?
Android 如何自定义EditText 下划线?

查看单个页面的卡顿情况,并从页面线程加载的情况中分析造成卡顿原因

如果你也想检验一下自己所写的 APP 的用户体验情况,不妨试试这个新玩意~~

OneAPM Mobile Insight 以真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客

本文转自 OneAPM 官方博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容