自定义EditText的需求:
最近工作中需要一个可以删除所有字符的EditText,所以自己写了个自定义view继承Edittext,这个实现相对简单,只用到了自定义view中的部分事件。
首先我们来看一下效果,是怎么样的:
从途中可以看到总共分为两个部分,一个是标准的EditText,另一个是右边的我们自定义的图标,在未输入字符之前,图标是隐藏的,输入字符后,图标显示,点击图标即可删除EditText中的所有文字,同时隐藏图标。
继承EditText
首先我们需要编写一个类继承自EditText:
public class ClearEditText extends android.support.v7.widget.AppCompatEditText{
public ClearEditText(Context context) {
this(context, null);
}
public ClearEditText(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.editTextStyle);
}
public ClearEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
Log.d("顺序", "init");
}
}
继承EditText必须实现其中的构造方法,此处我们重写了三个,事实上只要一个即可,不定义属性时会默认设置为号为editTextStyle属性集。
我们都知道自定义view时候通常会重写onDraw和onMeasure方法,那么这几个方法到底是按怎样的顺序执行呢,我们可以在代码中添加测试代码来实验一下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("顺序", "onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
Log.d("顺序", "draw");
super.onDraw(canvas);
}
然后我们在一个fragment中加载这个view,输出日志
03-07 14:51:10.805 14606-14606/com.saka.customviewdemo D/顺序: init
03-07 14:51:10.820 14606-14606/com.saka.customviewdemo D/顺序: onMeasure
03-07 14:51:10.880 14606-14606/com.saka.customviewdemo D/顺序: draw
可以看到执行的顺序是按构造器—>onmeasure->onDraw来执行的。
设置图标
最简单的方法是让ui切图,切出不同的分辨率,放在drawable中,直接调用。
此处我没有UI,我也不擅长PS,所以我用xml做了一个简单的删除按钮。
首先创建一个vector类型的drawableresource
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportHeight="16"
android:viewportWidth="16">
<path
android:pathData="M 0 8 L 16 8"
android:strokeColor="#2c2c2c"
android:strokeWidth="3" />
<path
android:pathData="M 8 0 L 8 16"
android:strokeColor="#2c2c2c"
android:strokeWidth="3" />
</vector>
这个图标是正方形,边长是16dp线条宽度3dp,然后做了一个十字型,大概是这个样子
然后再自定义一个rotate类型的drawableresource,这个也就是我们要使用的图标资源:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/delete"
android:fromDegrees="0"
android:toDegrees="225"
android:pivotX="50%"
android:pivotY="50%">
</rotate>
这个rotaterawable设置了旋转角度是从0转到225度,旋转的中心位置在图片X轴和Y轴的中心位置。
添加Drawable
我们实现自定义EditText的思路是占用右边的drawable位置,点击这个drawable即可触发清除字符事件。
此处我们是在构造器中添加的Drawable,通过init()方法来加载右侧图标。那么怎样获取这个位置呢?
首先我来看一下继承关系
java.lang.Object
↳ android.view.View
↳ android.widget.TextView
↳ android.widget.EditText
在TextView中有这样一个属性:android:drawableRight
,这个属性是设置控件右边的图标。这个属性对应的java代码是setCompoundDrawablesWithIntrinsicBounds(int,int,int,int)
,这三个int值的顺序对应的位置是左上右下,其中第三个位置就是drawableright属性。此处应该注意,假如xml布局中设置了drawableright属性,同时java代码中设置了setCompoundDrawablesWithIntrinsicBounds(null,null,null,null),则java代码中的设置会覆盖xml布局中的设置。
既然能设置我们就有办法获取每一个图标。通过查看api我们找到一个方法,Drawable[] getCompoundDrawables ()
,注意这个方法返回的是一个drawable数组,长度是4,对应的图标位置是左上右下,即使你没有设置任何drawable,这时的四个值都为null。
另一个方法Drawable[] getCompoundDrawablesRelative ()
返回的也是一个数组,长度同样是4,对应的图标位置是start,top,end和bottom,注意和上面的方法区分。
此处为了简单起见,我们直接在代码中设置右侧图标,覆盖xml布局中的设置,同时设置图标不可见。
private RotateDrawable drawableRotate;
private void init() {
Log.d("顺序", "init");
setIconVisible(false, getCompoundDrawables());
}
private void setIconVisible(boolean b, Drawable[] drawables) {
if (b) {
setCompoundDrawablesWithIntrinsicBounds(drawables[0], drawables[1], getResources().getDrawable(R.drawable.mydelete), drawables[3]);
drawableRotate = (RotateDrawable) getCompoundDrawables()[2];
} else {
setCompoundDrawablesWithIntrinsicBounds(drawables[0], drawables[1], null, drawables[3]);
}
}
这样,我们的图标就引入了EditText中,只是它现在是隐藏的,我们无法看到他。
设置图标可见与不可见
我们的目标是在有文字时显示图标,没有文字时隐藏图标,这个时候我们最好的方法是实现TextWatcher方法,它一共有三个
public void beforeTextChanged(CharSequence s, int start,
int count, int after);
public void onTextChanged(CharSequence s, int start, int before, int count);
public void afterTextChanged(Editable s);
我们重点关注第二个方法,这个方法在更改s的时候会用这个回调来通知你,在s中,从start位置开始的count个字符刚刚替换了before开始的的旧文本。
这里我们就可以利用这几个参数来计算此时的状态:
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() == 0 && before > 0) {
//从有文字删除到无文字的时候
startAnimatorSetResver();
return;
}
if (start == 0 && s.length() > 0) {
//从无文字到有文字
setIconVisible(true, getCompoundDrawables());
setAnimator();
startAnimatorSet();
}
}
注解中已经说明了两个方法的作用,动画函数稍后讲。此时试试你就可以显示和隐藏图标了。
添加消失和出现的动画
private ValueAnimator alphaAnimator = ValueAnimator.ofInt(0, 255);
private ValueAnimator rotateAnimator = ValueAnimator.ofInt(0, 10000);
此处我们设置了两个动画,一个是用来设置通明度变化,一个是用来设置旋转角度的。drawable有两个属性可以用来设置,一个是setLevel(),这个level就是设置的旋转角度,范围是1-10000(假如你是用的是ScaleDrawable,这个level控制就是你的图片的大小)。另一个就是setAlpha(),这个alpha就是透明度,范围是0-255。
private void setAnimator() {
alphaAnimator.setDuration(1000);
alphaAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drawableRotate.setAlpha((Integer) animation.getAnimatedValue());
}
});
rotateAnimator.setDuration(1000);
rotateAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
rotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drawableRotate.setLevel((Integer) animation.getAnimatedValue());
}
});
}
然后我们定义一个同时启动动画的集合:
private void startAnimatorSet() {
AnimatorSet setVisible = new AnimatorSet();
setVisible.playTogether(alphaAnimator, rotateAnimator);
setVisible.start();
}
这样,我们设置图片显示的动画就完成了。当你输入字符时,就可以看到图标慢慢旋转出现了。
同理可以设置图标消失的动画,不详细写出了,可以看demo(我的代码水平有点懒,没有优化)。
设置点击事件
其实我们此处并不是真正的设置点击事件,而是通过判断用户的触摸行为来模拟点击事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (getCompoundDrawables()[2] != null) {
if (getWidth() - getTotalPaddingRight() < event.getX() &&
getWidth() - getPaddingRight() > event.getX()) {
this.setText("");
Log.d("点击了图片", "图片");
}
}
break;
}
return super.onTouchEvent(event);
}
我们并不是重写onTouchEvent事件,我们只是在onTouchEvent中添加了一个在手指离开屏幕时是否正在图片上的判断,然后将内容设置为空,经过次操作以后,继续原有的onTouchEvent流程。
判断手指离开屏幕的位置的方法是这样的,api中有这样的方法:getWidth返回的是控件的宽度,getTotalPadingRight返回的是空间右边的padding,包含了drawable,getPaddingRight返回的是view右边的padding,要是包含滚动条,滚动条的宽度也在pading内。
至此我们的自定义EditText就完成了,可以使用。
这篇文章只是简单的降解了一下自定义view中一些基本流程,要深入进去需要掌握的远远多于这些,下一节将继续我们的学习。