转载请注明出处: http://blog.csdn.net/jakeyangchina/article/details/53338444
今天使用支付宝支付时突然发现支付页面输入密码效果感觉很棒,又打开微信支付页面发现效果也类似,于是乎我利用星期日休息时间仿照效果也做了一个Demo,废话不多说了直接上效果图
效果图如下:
简单概要:
实现带动画效果,当输入长度增加时,圆变大,当输入长度减少时,圆变小,当输入完后自动会回调方法给调用者。密码输入长度通过属性设置android:maxLength=”6”
注意事项:
- 自定义MyEditText类继承EditText实现Animation.AnimationListener接口
- 代码中通过反射获取maxLength
- 写类MyAnimation继承Animation类获取百分比值实现动画效果
- 写回调接口
遇到的问题:
- 当输入长度增加,再此减少长度,再次增加长度后会发现会自动增加两个圆,减少也是,产生原因:获取当前长度有误,解决办法:在判断减少减少状态时,把获取到的当前长度+1
- 当输入完会执行多次回调方法,产生原因:快速输入内容到最长度,因为动画没执行完,当动画执行完会多执行一次回调,解决办法:使用标识符判断
接下来开始上代码分析:
首先布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:padding="5dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.jakeyang.testview.MainActivity">
<com.jakeyang.testview.MyEditText
android:layout_marginTop="20dp"
android:id="@+id/MyEditText"
android:inputType="numberPassword"
android:layout_width="match_parent"
android:layout_height="60dp"
android:maxLength="6" />
<TextView
android:textSize="29sp"
android:text="输入内容为:"
android:id="@+id/TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:textSize="29sp"
android:id="@+id/TextView_complete"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
这里必须设置android:maxLength=”6”
自定义MyEditText类继承EditText
/**
* 画线
*/
private Paint linePaint;
/**
* 画圆
*/
private Paint cPaint;
/**
* 控件宽度
*/
private int width;
/**
* 控件高度
*/
private int height;
/**
* 绘制框内填充色
*/
private Paint kPaintColor;
/**
* 记录框内颜色矩形大小
*/
private RectF krectF;
/**
* 记录外轮框矩形大小
*/
private RectF rectF;
/**
* 获取最大可以输入的长度
*/
private int maxLength;
/**
* 给边距值,因为线粗
*/
private float padding = 1f;
/**
* 四周倒角半径
*/
private float chamferRadius = 16f;
/**
* 圆半径
*/
private float radius = 22;
/**
* 百分比,用于计算圆的大小
*/
private float percent = 0f;
/**
* 控制框内背景色
*/
private String backgroundColor = "#eeeeee";
/**
* 动画类
*/
private MyAnimation animationy;
/**
* 运行一次
*/
private boolean isFirst = false;
/**
* 标识使回调方法执行一次,当执行圆减少时恢复可执行回调方法
*/
private boolean isFirstComplete = false;
/**
* 用于记录圆个数是增加还是减少
*/
private boolean isAdd = true;
/**
* 记录上次输入内容的长度
*/
private int length = 0;
/**
* 记录当前输入内容的长度
*/
private int currentLength = 0;
/**
* 记录当前输入的内容
*/
private CharSequence texts;
上面属性标注很明细了,就不再解说了
接下来通过反射获取系统中mMax字段值
/**
* 获取EditText输入框内允许输入的最大个数,通过属性必须设置值
* @return
*/
private int getMaxLength() {
//系统提供的方法,查看源码可知字段mMax保存在LengthFilter类内,LengthFilter类是InputFilter内部实现类
InputFilter[] filters = getFilters();
for(InputFilter filter : filters) {
if("android.text.InputFilter.LengthFilter".equals(filter.getClass().getCanonicalName())) {
try {
Field mMax = filter.getClass().getDeclaredField("mMax");
mMax.setAccessible(true);
//获取字段值
int num = (int)mMax.get(filter);
return num;
} catch (Exception e) {
e.printStackTrace();
}
}
}
return -1;
}
通过查看源码可知,InputFilter是接口,在InputFilter接口内部有LengthFilter类,LengthFilter是InputFilter实现类,在LengthFilter类中定义字段mMax,通过反射得到值
public MyEditText(Context context) {
this(context,null);
}
public MyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public MyEditText(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
上面是构造函数,其中init()方法是初始化函数具体代码如下
/**
* 初始化数据
*/
private void init() {
//隐藏光标
setCursorVisible(false);
//获取焦点
requestFocus();
setFocusableInTouchMode(true);
setFocusable(true);
//绘制外边边框和中间线条
linePaint = new Paint();
//设置抗锯齿
linePaint.setAntiAlias(true);
//设置样式
linePaint.setStyle(Paint.Style.STROKE);
//绘制框内填充色
kPaintColor = new Paint();
kPaintColor.setAntiAlias(true);
//设置框内颜色
kPaintColor.setColor(Color.parseColor(backgroundColor));
//设置填充
kPaintColor.setStyle(Paint.Style.FILL);
//绘制圆
cPaint = new Paint();
cPaint.setStyle(Paint.Style.FILL);
cPaint.setAntiAlias(true);
//创建动画
animationy = new MyAnimation();
animationy.setDuration(200);
animationy.setAnimationListener(this);
}
上面标注很明细了,可以直接看标准,不详细解释
接下来覆写onLayout方法,在其中获取控件宽高等
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//让代码执行一次
if(!isFirst && changed) {
//记录控件当前宽度
width = getWidth();
//记录控件当前高度
height = getHeight();
//绘制框内颜色
krectF = new RectF(padding,padding,width-padding,height-padding);
//绘制外轮廓
rectF = new RectF(padding,padding,width-padding,height-padding);
maxLength = getMaxLength();
//只允许执行一次
isFirst = true;
}
}
接下来覆写onTextChanged方法进行设置标识符确认圆是增加还是减少状态
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
//记录内容,用于传给回调函数
texts = text;
//获取当前文本输入的长度
currentLength = text.length();
if(currentLength >= length) {
//增加圆
isAdd = true;
}else {
//减少圆
isAdd = false;
//当减少圆时,恢复完成时的回调
isFirstComplete = false;
//防止当为减少时,圆瞬间减少两个
currentLength = currentLength + 1;
}
//回调函数,给调用者
if(listener != null) {
if(currentLength == 1 && !isAdd) {
//防止输入框内没有值时,调用者仍可获取到第一个输入值
listener.onTextChanged("");
}else {
listener.onTextChanged(text);
}
}
if(currentLength <= getMaxLength()) {
if(animationy != null) {
//每次进来清除上次动画
clearAnimation();
//开启新动画
startAnimation(animationy);
}else {
invalidate();
}
}
//记录上次输入内容的长度
length = currentLength;
}
当内容改变时系统调用onTextChanged方法,用length记录上次长度,用当前currentLength长度和上次长度进行比较判断,如果当前长度大于上次长度说明圆增加,把isAdd标识设置为true,否则反之,这里需要注意的是当圆为减少时,此时需要把当前长度+1,要不然绘制圆时会出现同时增加两个圆或者减少两个圆现象,原因是在绘制圆时for循环中当前长度少一个圆引起的,详细见for循环
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制框内颜色
canvas.drawRoundRect(krectF,chamferRadius,chamferRadius,kPaintColor);
//绘制外轮廓
canvas.drawRoundRect(rectF,chamferRadius,chamferRadius,linePaint);
//计算每个输入框宽
float x = width / maxLength;
//计算每个输入框高
float y = height;
//画线
for(int i = 1; i < maxLength; i++) {
//画框内竖线
canvas.drawLine(x*i,padding,x*i,y - padding,linePaint);
}
for(int i = 0; i < currentLength; i++) {
if(isAdd) {
//增加圆isAdd = true;
if(i < currentLength-1) {
//画圆,不为当前圆时,不需要产生动画效果
canvas.drawCircle(x/2+x*i,y/2,radius,cPaint);
}else if(i == currentLength-1) {
//画圆,当画当前圆时,需要产生扩大效果
canvas.drawCircle(x/2+x*i,y/2,radius*percent,cPaint);
}
}else {
//减少圆isAdd = false;
if(i < currentLength - 1) {
//画圆,不为当前圆时,不需要产生动画效果
canvas.drawCircle(x/2+x*i,y/2,radius,cPaint);
}else if(i == currentLength - 1) {
//画圆,当画当前圆时,需要产生缩小效果
canvas.drawCircle(x/2+x*i,y/2,radius-radius*percent,cPaint);
}
}
}
}
上面使用两个for循环,第一个for循环绘制竖线,第二个for循环是绘制圆,在for循环中通过判断当前isAdd是增加圆还是减少圆来进行绘制,需要注意是减少圆时的个数
/**
* 动画开始时,回调此方法
* @param animation
*/
@Override
public void onAnimationStart(Animation animation) {
}
/**
* 动画结束时回调次方法
* @param animation
*/
@Override
public void onAnimationEnd(Animation animation) {
//动画完成执行
if(isAdd && currentLength == getMaxLength() && listener != null && !isFirstComplete) {
//最后一个圆执行完动画后,执行此代码,为了保证只允许执行一次,添加个isFirstComplete标识用于判断
isFirstComplete = true;
listener.onComplete(texts);
}
}
/**
* 动画重复执行时,回调此方法
* @param animation
*/
@Override
public void onAnimationRepeat(Animation animation) {
}
上边方法为实现AnimationListener接口时需要实现的方法,其中需要注意的是在动画结束后去执行自定义回调方法,防止动画没执行完就执行回调
/**
* 执行动画
*/
private class MyAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
//记录百分比值
percent = interpolatedTime;
//百分比变化,需要更新页面
postInvalidate();
}
}
上边类为继承动画类,主要是为了得到applyTransformation方法,参数interpolatedTime是系统传回来的运动距离的百分值
接下来自定义完成时的接口
public EditTextContentListener listener;
/**
* 设置输入内容监听接口
* @param listener
*/
public void setOnEditTextContentListener(EditTextContentListener listener) {
this.listener = listener;
}
/**
* 输入内容监听接口,提供给调用者
*/
public interface EditTextContentListener {
/**
* 完成时,回调此方法
* @param text
*/
public void onComplete(CharSequence text);
/**
* 输入框内容改变时,回调次方法
* @param text
*/
public void onTextChanged(CharSequence text);
}
上面注解很详细,接下来Activity类
public class MainActivity extends AppCompatActivity {
private MyEditText myEditText;
private TextView textView;
private TextView textViewComplete;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.TextView);
textViewComplete = (TextView) findViewById(R.id.TextView_complete);
myEditText = (MyEditText) findViewById(R.id.MyEditText);
myEditText.setOnEditTextContentListener(new MyEditText.EditTextContentListener() {
@Override
public void onComplete(CharSequence text) {
if(text != null) {
textViewComplete.setText("输入完成,内容为:"+text);
}
}
@Override
public void onTextChanged(CharSequence text) {
if(text != null) {
textViewComplete.setText("");
textView.setText("输入内容为:"+text);
}
}
});
}
如果大家还有什么疑问,请在下方留言。
如何有不足的地方希望大家指出来,共同学习。
源码下载,请点击这里!