您的位置:首页 > 移动开发 > Android开发

Android EditText带清空按钮&动画

2015-10-15 19:24 381 查看
转载请标明出处:

/article/8129712.html

本文出自:【M家杰的博客】

概述

安卓开发中有时经常会用到编辑框(登录、找回密码等场景),但是安卓的EditText竟然不自带清空内容的功能,网上找了下资料自定义带清空功能的EditText还是比较多的,但是自带明文和密文切换的基本没有,所以自己没事干做了一个带点动画效果的自定义EditText。

实现的功能:

1. 清除按钮
2. 明文切换按钮
3. 按钮进入的动画
4. 错误抖动效果
5. material 风格


DEMO

先放上演示图

1. 单独EditText效果



2. 配合TextInputLayout使用的效果



实现方式

网上最常见的实现带清空按钮的EditText方式是:设置drawableRight为删除按钮–>监听触控事件做清空操作

这种方式很简单,但也存在局限性,比如drawableRight被占用、动画效果不可实现等。因为我自己比较喜欢带点动效的UI,所以换了一种方案实现:

重写onDraw()方法 –>利用属性动画刷新实现动效–>监听触控事件做清空操作

完整的代码

先把完整的ClearEditText 的代码放上来,方便阅读和直接复制,后面详细讲解关键代码

public class ClearEditText extends AppCompatEditText {

//按钮资源
private final int CLEAR = R.drawable.clearfill;
//动画时长
private final int ANIMATOR_TIME = 200;
//按钮左右间隔,单位DP
private final int INTERVAL = 5;
//清除按钮宽度,单位DP
private final int WIDTH_OF_CLEAR = 23;

//间隔记录
private int Interval;
//清除按钮宽度记录
private int mWidth_clear;
//右内边距
private int mPaddingRight;
//清除按钮的bitmap
private Bitmap mBitmap_clear;
//清除按钮出现动画
private ValueAnimator mAnimator_visible;
//消失动画
private ValueAnimator mAnimator_gone;
//是否显示的记录
private boolean isVisible = false;

//右边添加其他按钮时使用
private int mRight = 0;

public ClearEditText(final Context context) {
super(context);
init(context);
}

public ClearEditText(final Context context,final AttributeSet attrs) {
super(context, attrs);
init(context);
}

public ClearEditText(final Context context,final AttributeSet attrs,final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context) {

//setSingleLine();这个方法不推荐写在代码中,原因请看博客尾部更新

mBitmap_clear = createBitmap(CLEAR,context);

Interval = dp2px(INTERVAL);
mWidth_clear = dp2px(WIDTH_OF_CLEAR);
mPaddingRight = Interval + mWidth_clear + Interval ;
mAnimator_gone = ValueAnimator.ofFloat(1f, 0f).setDuration(ANIMATOR_TIME);
mAnimator_visible = ValueAnimator.ofInt(mWidth_clear + Interval,0).setDuration(ANIMATOR_TIME);

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

//设置内边距
setPadding(getPaddingLeft(), getPaddingTop(), mPaddingRight+ mRight, getPaddingBottom());

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));//抗锯齿
if (mAnimator_visible.isRunning()) {
int x = (int) mAnimator_visible.getAnimatedValue();
drawClear(x,canvas);
invalidate();
}else if (isVisible){
drawClear(0,canvas);
}

if(mAnimator_gone.isRunning()){
float scale = (float) mAnimator_gone.getAnimatedValue();
drawClearGone(scale, canvas);
invalidate();
}
}

/**
* 绘制清除按钮出现的图案
* @param translationX 水平移动距离
* @param canvas
*/
protected void drawClear( int translationX,Canvas canvas){
int right = getWidth()+getScrollX() - Interval - mRight +translationX;
int left = right-mWidth_clear;
int top = (getHeight()-mWidth_clear)/2;
int bottom = top + mWidth_clear;
Rect rect = new Rect(left,top,right,bottom);
canvas.drawBitmap(mBitmap_clear, null, rect, null);

}

/**
* 绘制清除按钮消失的图案
* @param scale 缩放比例
* @param canvas
*/
protected void drawClearGone( float scale,Canvas canvas){
int right = (int) (getWidth()+getScrollX()- Interval - mRight -mWidth_clear*(1f-scale)/2f);
int left = (int) (getWidth()+getScrollX()- Interval - mRight -mWidth_clear*(scale+(1f-scale)/2f));
int top = (int) ((getHeight()-mWidth_clear*scale)/2);
int bottom = (int) (top + mWidth_clear*scale);
Rect rect = new Rect(left,top,right,bottom);
canvas.drawBitmap(mBitmap_clear, null, rect, null);
}

/**
* 开始清除按钮的显示动画
*/
private void startVisibleAnimator() {
endAnaimator();
mAnimator_visible.start();
invalidate();
}

/**
* 开始清除按钮的消失动画
*/
private void startGoneAnimator() {
endAnaimator();
mAnimator_gone.start();
invalidate();
}

/**
* 结束所有动画
*/
private void endAnaimator(){
mAnimator_gone.end();
mAnimator_visible.end();
}

/**
* Edittext内容变化的监听
*/
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);

if(text.length()>0) {
if (!isVisible) {
isVisible = true;
startVisibleAnimator();
}
}else{
if (isVisible) {
isVisible = false;
startGoneAnimator();
}
}
}

/**
* 触控执行的监听
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {

boolean touchable = ( getWidth() - Interval - mRight - mWidth_clear < event.getX() ) && (event.getX() < getWidth() - Interval - mRight);
if (touchable) {
setError(null);
this.setText("");
}
}
return super.onTouchEvent(event);
}

/**
* 开始晃动动画
*/
public void startShakeAnimation(){
if(getAnimation() == null){
this.setAnimation(shakeAnimation(4));
}
this.startAnimation(getAnimation());
}

/**
* 晃动动画
* @param counts 0.5秒钟晃动多少下
* @return
*/
private Animation shakeAnimation(int counts){
Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0);
translateAnimation.setInterpolator(new CycleInterpolator(counts));
translateAnimation.setDuration(500);
return translateAnimation;
}

/**
* 给图标染上当前提示文本的颜色并且转出Bitmap
* @param resources
* @param context
* @return
*/
public Bitmap createBitmap(int resources,Context context) {
final Drawable drawable = ContextCompat.getDrawable(context, resources);
final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(wrappedDrawable, getCurrentHintTextColor());
return drawableToBitamp(wrappedDrawable);
}

/**
* drawable转换成bitmap
* @param drawable
* @return
*/
private Bitmap drawableToBitamp(Drawable drawable)
{
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
drawable.draw(canvas);
return bitmap;
}

public int dp2px(float dipValue) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}


继承EditText

自定义View首先要做的一步一般是继承,这里有两个可以选择EditText和AppCompatEditText,推荐继承后者,这样可以在5.0以下的版本也应用material 风格。颜色可以在样式中修改colorAccent的颜色改变如下:

<item name="colorAccent">@color/colorAccent</item>


如果不想使用material的样式那么继承任意一个都区别不大,样式都可以通过修改背景改变。

构造方法

自定义View,继承类之后马上要写的是构造方法,大多数情况下会像下面那样写(在这里不推荐这样写):

public ClearEditText(final Context context) {
this(context,null);
}

public ClearEditText(final Context context,final AttributeSet attrs) {
this(context, attrs,0);
}

public ClearEditText(final Context context,final AttributeSet attrs,final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}


以上的写法很常见,也很正常,但是我继承 AppCompatEditText是为了应用material的风格,AppCompatEditText源码的构造函数中就应用这种样式的代码都写好了,所以我们需要应用父类的构造函数。正确的写法

public ClearEditText(final Context context) {
super(context);
init(context);
}

public ClearEditText(final Context context,final AttributeSet attrs) {
super(context, attrs);
init(context);
}

public ClearEditText(final Context context,final AttributeSet attrs,final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}


关键代码解析

onMeasure

在onMeasure方法中主要是设置了右内边距,为了让输入框内容过长时按钮和文本内容不重叠

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//设置内边距
setPadding(getPaddingLeft(), getPaddingTop(), mPaddingRight+ mRight, getPaddingBottom());
}


onDraw

这次代码中最关键的就是改写了这个onDraw方法,在这里主要是运用了ValueAnimator,让它运行并且在这里判断是否是运行状态。然后通过动画的参数去计算位置,然后将按钮画出来,再调用invalidate()刷新(invalidate()是一个刷新视图的方法,调用之后会自动的再去重绘视图,即运行onDraw方法)。而我自己写的drawClear()和drawClearGone()2个方法主要是计算动画参数,然后用canvas将按钮图案画出来。

protected void onDraw(Canvas canvas) {
....
if (mAnimator_visible.isRunning()) {
int x = (int) mAnimator_visible.getAnimatedValue();
drawClear(x,canvas);
invalidate();
}else if (isVisible){
drawClear(0,canvas);
}

if(mAnimator_gone.isRunning()){
float scale = (float) mAnimator_gone.getAnimatedValue();
drawClearGone(scale, canvas);
invalidate();
}
}


内容改变的监听onTextChanged

网上很多例子中说需要实现TextWatcher接口来监听EditText的内容变化,其实不需要,既然已经继承了EditText可以直接重写onTextChanged()方法。在这里实现文本内容改变后的操作。这里的操作就是判断下删除按钮是否显示。

@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);

if(text.length()>0) {
if (!isVisible) {
isVisible = true;
startVisibleAnimator();
}
}else{
if (isVisible) {
isVisible = false;
startGoneAnimator();
}
}
}


触控事件监听

覆盖父类的onTouchEvent()方法实现监听,然后在手指抬起的时候判断抬起点的坐标是否在清除按钮的范围内,如果是就清空文本,不需要在写别的代码了,因为
this.setText("")
方法会触发上面说到的onTextChanged()方法。

@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {

boolean touchable = ( getWidth() - Interval - mRight - mWidth_clear < event.getX() ) && (event.getX() < getWidth() - Interval - mRight);
if (touchable) {
setError(null);
this.setText("");
}
}
return super.onTouchEvent(event);
}


额外的晃动动画

本来我自己是没写这个动画的,不过看见网上很多的自定义EditText里面都放了这段动画,代码还基本都一样,可能都是CTRL C的吧!所以我也低调的复制进去了!用不到的可以把这段删了。

在代码中调用
startShakeAnimation()
方法就可以实现晃动动画了。

/**
* 开始晃动动画
*/
public void startShakeAnimation(){
if(getAnimation() == null){
this.setAnimation(shakeAnimation(4));
}
this.startAnimation(getAnimation());
}

/**
* 晃动动画
* @param counts 0.5秒钟晃动多少下
* @return
*/
private Animation shakeAnimation(int counts){
Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0);
translateAnimation.setInterpolator(new CycleInterpolator(counts));
translateAnimation.setDuration(500);
return translateAnimation;
}


错误提示

EditText是自带错误提示的,可能很多同学不知道,这里做个演示。

单独使用EditText的时候,调用:
EditText.setError("输入的密码错误");


效果



配合TextInputLayout使用,调用
TextInputLayout.setError("输入的密码错误");


效果



关于PasswordEditText

PasswordEditText是我继承ClearEditText然后再添加了一个按钮实现的,实现的原理基本相同。这里不贴出来的原因是因为这个明文显示的按钮在绝大多数APP中都不常见,所以个人觉得没必要占用篇幅讲这个,想看的同学可以下载下面的源码查看,CSDN资源都是0分下载的。

参考资料

Android图形动画 /article/5664209.html

让你的 EditText 全部清除 http://mrfu.me/android/2015/07/30/allclear_edittext/

源码下载

【CSDN】 【GitHub】

更新

删除ClearEditText的init()方法中的setSingleLine();

原因:我原本想在代码中调用这个方法就不用再XML中重复的写
android:singleLine="true"
。但是我发现这很愚蠢!不是所有编辑框都需要单行的。而且在实际开发中这引起了一个BUG,当调用
setSingleLine()
方法时会重置编辑框的显示模式为
SingleLineTransformationMethod
,从而使原本在XML中设置的显示方式失效(最明显的就是设置密文显示失效变明文),具体可查看TextView源码中的
applySingleLine
方法。

GItHub代码已更新,CSDN无法改文件所以暂不更新
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: