這次帶來一個仿支付寶的支付密碼輸入框的控件,自己起了個名字叫AlipayEditText。先來看看效果:
首先告訴大家一個好消息,本次的控件不需要任何資源文件或佈局文件,就一個Java文件,複製粘貼即可用。話不多說,上控件本體:
AliPayEditText.java:
public class AliPayEditText extends EditText {
// 畫方框的畫筆
private Paint boxPaint;
// 畫密碼的畫筆(小圓點)
private Paint passwdPaint;
// 畫文字的畫筆
private Paint textPaint;
// 未輸入文字時的方框線寬
private int normalLineWidth;
// 未輸入文字時的方框顏色
private int normalLineColor;
// 輸入文字時的方框線寬
private int editLineWidth;
// 輸入文字時的方框顏色
private int editLineColor;
// 密碼(小圓點)顏色
private int passwdColor;
// 密碼(小圓點)半徑
private int passwdRadius;
// 輸入的文字大小
private int textSize;
// 輸入的文字顏色
private int textColor;
// 輸入最大長度
private int maxLength;
// 方框間距
private int margin;
// 震動時間
private int vibratePeriod;
// 震動幅度
private int vibrateX;
// 震動重複次數
private int repeatCount;
// 文字是否可見,爲false時是密碼模式,方框裏顯示的是小圓點
private boolean textVisibility = true;
// 文字是否加粗
private boolean fakeBoldText = true;
private int width;
private int height;
private String currentText = "";
private Context context;
private AnimatorSet animator;
private int currentRepeatCount;
private boolean isAnimationShowing;
// 輸入回調
private OnInputListener listener;
public AliPayEditText(Context context) {
this(context, null);
}
public AliPayEditText(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public AliPayEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/**
* 初始化方法
* 初始化參數和默認設置
*
* @param context
*/
private void init(Context context) {
this.context = context;
normalLineWidth = dp2px(1);
normalLineColor = Color.parseColor("#dddddd");
editLineWidth = dp2px(2);
editLineColor = Color.parseColor("#000000");
passwdRadius = dp2px(5);
passwdColor = Color.parseColor("#000000");
textSize = sp2px(16);
textColor = Color.parseColor("#000000");
maxLength = 6;
margin = dp2px(10);
repeatCount = 1;
currentRepeatCount = 1;
vibrateX = dp2px(10);
vibratePeriod = 80;
setFocusable(true);
setBackgroundDrawable(null);
setCursorVisible(false);
setSingleLine();
setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
setTextColor(Color.parseColor("#00000000"));
setHintTextColor(Color.parseColor("#00000000"));
boxPaint = new Paint();
boxPaint.setColor(normalLineColor);
boxPaint.setStrokeWidth(normalLineWidth);
passwdPaint = new Paint();
passwdPaint.setColor(passwdColor);
passwdPaint.setStrokeWidth(passwdRadius);
textPaint = new Paint();
textPaint.setColor(textColor);
textPaint.setTextSize(textSize);
textPaint.setFakeBoldText(fakeBoldText);
animator = new AnimatorSet();
}
/**
* 很重要
* 在佈局讀取完畢時重新計算輸入框的寬高
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getMeasuredWidth();
height = (width - (maxLength - 1) * margin - Math.max(normalLineWidth, editLineWidth)) / maxLength;
getLayoutParams().height = height + Math.max(normalLineWidth, editLineWidth);
}
/**
* 初始化和每次輸入文字時重繪視圖
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBox(canvas);
drawText(canvas);
}
/**
* 繪製方框
*
* @param canvas
*/
private void drawBox(Canvas canvas) {
// 循環繪製方框,個數與文字最大輸入數maxLength相同
for (int i = 0; i < maxLength; i++) {
boxPaint.setColor(i < currentText.length() ? editLineColor : normalLineColor);
boxPaint.setStrokeWidth(i < currentText.length() ? editLineWidth : normalLineWidth);
// 繪製方框左邊的線條
canvas.drawLine(i * (height + margin) + (i < currentText.length() ? editLineWidth : normalLineWidth) / 2, 0, i * (height + margin) + (i < currentText.length() ? editLineWidth : normalLineWidth) / 2, height, boxPaint);
// 繪製方框右邊的線條
canvas.drawLine(height + i * (height + margin) - (i < currentText.length() ? editLineWidth : normalLineWidth) / 2, 0, height + i * (height + margin) - (i < currentText.length() ? editLineWidth : normalLineWidth) / 2, height, boxPaint);
// 繪製方框上邊的線條
canvas.drawLine(i * (height + margin), (i < currentText.length() ? editLineWidth : normalLineWidth) / 2, height + i * (height + margin), (i < currentText.length() ? editLineWidth : normalLineWidth) / 2, boxPaint);
// 繪製方框下邊的線條
canvas.drawLine(i * (height + margin), height - (i < currentText.length() ? editLineWidth : normalLineWidth) / 2, height + i * (height + margin), height - (i < currentText.length() ? editLineWidth : normalLineWidth) / 2, boxPaint);
}
}
/**
* 繪製文字
*
* @param canvas
*/
private void drawText(Canvas canvas) {
for (int i = 0; i < currentText.length(); i++) {
if (textVisibility) {
// 非密碼模式畫文字
float deltaX = textPaint.measureText(String.valueOf(currentText.charAt(i))) / 2;
canvas.drawText(String.valueOf(currentText.charAt(i)), i * (height + margin) + height / 2 - deltaX, height / 2 + deltaX, textPaint);
} else {
// 密碼文字畫小圓點
canvas.drawCircle(i * (height + margin) + height / 2, height / 2, passwdRadius, passwdPaint);
}
}
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
currentText = text.toString();
invalidate();
if (currentText.length() == maxLength) {
if (!isAnimationShowing && listener != null) {
listener.onFinish(currentText);
}
} else {
if (!isAnimationShowing && listener != null) {
listener.onInput(currentText, currentText.length());
}
}
}
/**
* 震動
* 在一些情況(比如密碼輸入錯誤)時讓整個輸入框震動
*/
public void vibrate() {
currentRepeatCount = 0;
isAnimationShowing = true;
ObjectAnimator leftAnimator = ObjectAnimator.ofFloat(this, "TranslationX", 0, -vibrateX);
ObjectAnimator rightAnimator = ObjectAnimator.ofFloat(this, "TranslationX", -vibrateX, vibrateX);
ObjectAnimator backAnimator = ObjectAnimator.ofFloat(this, "TranslationX", vibrateX, 0);
leftAnimator.setDuration(vibratePeriod / 4);
rightAnimator.setDuration(vibratePeriod / 2);
backAnimator.setDuration(vibratePeriod / 4);
leftAnimator.setInterpolator(new LinearInterpolator());
rightAnimator.setInterpolator(new LinearInterpolator());
backAnimator.setInterpolator(new LinearInterpolator());
backAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (currentRepeatCount < repeatCount) {
animator.start();
currentRepeatCount++;
} else {
isAnimationShowing = false;
}
}
@Override
public void onAnimationCancel(Animator animation) {
isAnimationShowing = false;
}
});
animator.play(rightAnimator).after(leftAnimator).before(backAnimator);
animator.start();
}
/**
* 設置未輸入文字時方框線寬
*
* @param normalLineWidth 線寬值,單位dp
*/
public void setNormalLineWidth(int normalLineWidth) {
this.normalLineWidth = dp2px(normalLineWidth);
invalidate();
}
/**
* 設置未輸入文字時方框顏色
*
* @param normalLineColor 顏色值
*/
public void setNormalLineColor(int normalLineColor) {
this.normalLineColor = normalLineColor;
invalidate();
}
/**
* 設置輸入文字時方框線寬
*
* @param editLineWidth 線寬值,單位dp
*/
public void setEditLineWidth(int editLineWidth) {
this.editLineWidth = dp2px(editLineWidth);
invalidate();
}
/**
* 設置輸入文字時方框顏色
*
* @param editLineColor 顏色值
*/
public void setEditLineColor(int editLineColor) {
this.editLineColor = editLineColor;
invalidate();
}
/**
* 設置密碼(小圓點)顏色
*
* @param passwdColor 顏色值
*/
public void setPasswdColor(int passwdColor) {
this.passwdColor = passwdColor;
passwdPaint.setColor(passwdColor);
invalidate();
}
/**
* 設置密碼(小圓點)半徑
*
* @param passwdRadius 半徑值,單位dp
*/
public void setPasswdRadius(int passwdRadius) {
this.passwdRadius = dp2px(passwdRadius);
passwdPaint.setStrokeWidth(passwdRadius);
invalidate();
}
/**
* 設置文字大小
* 之所以方法名加個Draw是因爲setTextSize在TextView裏有同名方法
*
* @param textSize 大小值,單位sp
*/
public void setDrawTextSize(int textSize) {
this.textSize = sp2px(textSize);
textPaint.setTextSize(this.textSize);
invalidate();
}
/**
* 設置文字顏色
* 之所以方法名加個Draw是因爲setTextColor在TextView裏有同名方法
*
* @param textColor 顏色值
*/
public void setDrawTextColor(int textColor) {
this.textColor = textColor;
textPaint.setColor(textColor);
invalidate();
}
/**
* 設置文字最大長度
* 如果在佈局文件中設置了maxLength屬性,務必再設置一遍此方法,否則方框數默認爲6個
*
* @param maxLength 長度值,整數
*/
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
requestLayout();
}
/**
* 設置方框間距
*
* @param margin 間距值,單位dp
*/
public void setMargin(int margin) {
this.margin = dp2px(margin);
requestLayout();
}
/**
* 設置震動時間
*
* @param vibratePeriod 震動時間,單位毫秒
*/
public void setVibratePeriod(int vibratePeriod) {
this.vibratePeriod = vibratePeriod;
}
/**
* 設置文字是否可見
* 設置爲false時爲密碼模式,輸入顯示小圓點
*
* @param textVisibility
*/
public void setTextVisibility(boolean textVisibility) {
this.textVisibility = textVisibility;
invalidate();
}
/**
* 設置文字是否加粗,密碼模式下無效
*
* @param fakeBoldText
*/
public void setFakeBoldText(boolean fakeBoldText) {
this.fakeBoldText = fakeBoldText;
textPaint.setFakeBoldText(fakeBoldText);
}
/**
* 設置震動幅度,即震動偏離原始位置的最大距離
*
* @param vibrateX 震動幅度,單位dp
*/
public void setVibrateX(int vibrateX) {
this.vibrateX = dp2px(vibrateX);
}
/**
* 設置震動重複次數
* 左右擺動一個來回視爲重複一次
*
* @param repeatCount 重複次數,整數
*/
public void setRepeatCount(int repeatCount) {
this.repeatCount = repeatCount;
}
/**
* 輸入回調接口
*/
public interface OnInputListener {
/**
* 輸入事件發生且長度未達到最大長度時回調
*
* @param text 當前文字
* @param currentLength 當前文字長度
*/
void onInput(String text, int currentLength);
/**
* 輸入長度達到最大長度時回調
*
* @param text 當前文字
*/
void onFinish(String text);
}
/**
* 設置輸入完成時的回調接口
*
* @param listener
*/
public void setOnInputListener(OnInputListener listener) {
this.listener = listener;
}
/**
* 重置輸入框
* 調用後文字清空
*/
public void reset() {
setText("");
invalidate();
}
private int dp2px(float dipValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
private int sp2px(float spValue) {
float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
簡單說一下寫控件的思路:既然是個輸入框自然要繼承自EditText,爲了實現顯示效果我們可以把EditText的背景設爲null,遊標隱藏掉,這樣EditText的本體就相當於看不見了;然後在這上面覆蓋一層視圖,手動畫上方框和文字來“偷樑換柱”顯示效果就達到了,具體實現方法大家可以看一看源碼的註釋。
然後說一說用法。其實用法非常的傻瓜式,把把控件本體複製到你項目裏任意的文件夾(最要項目有個專門的文件夾來裝這些自定義控件),然後在佈局文件裏就可以直接引用了:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<com.min.alipayedittext.AliPayEditText
android:id="@+id/edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp" />
</LinearLayout>
然後MainActivity裏什麼都不用寫:
MainActivity.java:
public class MainActivity extends Activity {
private AliPayEditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
}
private void initData() {
}
private void initView() {
editText = (AliPayEditText) findViewById(R.id.edittext);
}
}
運行一下,就能看到開頭展示的那個效果了。
當然如果什麼都不寫,輸入的時候是沒有交互的(比如在密碼輸入完成的時候給一個回調,你就可以判斷密碼輸的對不對)。添加幾行代碼:
private void initView() {
...
editText.setOnInputListener(new AliPayEditText.OnInputListener() {
@Override
public void onInput(String text, int currentLength) {
editText.setEditLineColor(Color.parseColor("#000000"));
}
@Override
public void onFinish(String text) {
if (text.equals("ILoveU")) {
Toast.makeText(MainActivity.this, "哈哈", Toast.LENGTH_SHORT).show();
} else {
editText.setEditLineColor(Color.parseColor("#ff4500"));
editText.vibrate();
}
}
});
...
}
再次運行:
是不是就有模有樣了?當然控件還支持更多的屬性設置,源碼裏註釋都寫得很清楚,大家可以自己試一試,這裏簡單做幾個示範,添加代碼:
private void initView() {
...
editText.setMaxLength(8);
editText.setTextVisibility(false);
editText.setMargin(-1);
...
}
看一下效果:
至於EditText本身是一些屬性,比如inputType什麼的,自己在佈局文件裏設置一下就好了。
最後附上源碼地址:點擊打開鏈接
這次的內容就到這裏,我們下次再見。