Android EditText带清空按钮&动画
2015-10-15 19:24
381 查看
转载请标明出处:
/article/8129712.html
本文出自:【M家杰的博客】
概述
安卓开发中有时经常会用到编辑框(登录、找回密码等场景),但是安卓的EditText竟然不自带清空内容的功能,网上找了下资料自定义带清空功能的EditText还是比较多的,但是自带明文和密文切换的基本没有,所以自己没事干做了一个带点动画效果的自定义EditText。
实现的功能:
1. 单独EditText效果
![](http://img.blog.csdn.net/20151122144119294)
2. 配合TextInputLayout使用的效果
![](http://img.blog.csdn.net/20151122144158151)
这种方式很简单,但也存在局限性,比如drawableRight被占用、动画效果不可实现等。因为我自己比较喜欢带点动效的UI,所以换了一种方案实现:
重写onDraw()方法 –>利用属性动画刷新实现动效–>监听触控事件做清空操作
如果不想使用material的样式那么继承任意一个都区别不大,样式都可以通过修改背景改变。
以上的写法很常见,也很正常,但是我继承 AppCompatEditText是为了应用material的风格,AppCompatEditText源码的构造函数中就应用这种样式的代码都写好了,所以我们需要应用父类的构造函数。正确的写法:
在代码中调用
单独使用EditText的时候,调用:
效果
![](http://img.blog.csdn.net/20151122164754919)
配合TextInputLayout使用,调用
效果
![](http://img.blog.csdn.net/20151122164917407)
让你的 EditText 全部清除 http://mrfu.me/android/2015/07/30/allclear_edittext/
原因:我原本想在代码中调用这个方法就不用再XML中重复的写
GItHub代码已更新,CSDN无法改文件所以暂不更新
/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无法改文件所以暂不更新
相关文章推荐
- Android服务之AIDL
- android源码分析--MMS data 数据模型及业务逻辑transaction
- Android开发手记(8) ProgressDialog的使用
- Android背景渐变色(shape,gradient)
- Android内存泄漏研究
- Android 音量控制流程分析
- Android通过百度地图API用Service和Alarm在后台定时获取地理位置信息
- 百度地图API之根据经纬度查询地址信息(Android)
- DPI 相关
- [转]android中解析后台返回的json字符串
- Android Studio 打包及引用 aar
- Android 开启 Latencytop
- Android:阴影效果的另一种实现方法:layer-list
- Android Ctrl + 左键 不能直接打开xml问题
- Android6.0 Fingerprint Features(指纹识别)
- android主流框架整理
- Android中使用自定义View实现下载进度的显示
- Android5.0和6.0的新特性
- RxJava(RxAndroid)入门级教程及衍生教程
- Android MIFARE NFCA源码解析