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以下卡顿比较严重。OneAPMMobile Insight的卡顿监控就是一个监控流畅度指标的模块。

Android 如何自定义EditText 下划线?

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

Android 如何自定义EditText 下划线?

Android 如何自定义EditText 下划线?

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

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

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

本文转自 OneAPM 官方博客

相关文章
|
2月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
38 1
|
3月前
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
46 3
|
3月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
128 0
|
2月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
2月前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
46 5
|
3月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
4月前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
74 10
|
4月前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
4月前
|
前端开发 Android开发 开发者
安卓应用开发中的自定义视图基础
【9月更文挑战第13天】在安卓开发的广阔天地中,自定义视图是一块神奇的画布,它允许开发者将想象力转化为用户界面的创新元素。本文将带你一探究竟,了解如何从零开始构建自定义视图,包括绘图基础、触摸事件处理,以及性能优化的实用技巧。无论你是想提升应用的视觉吸引力,还是追求更流畅的交互体验,这里都有你需要的金钥匙。
|
4月前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
65 3