Android自定義密碼輸入框
項目地址
項目需要用到密碼框輸入,並且使用自定義的鍵盤,但是密碼框需要區分輸入完成、待輸入、未輸入顏色百度一番沒有結果就自己自定義一個了
自定義鍵盤 (我做的比較簡單就不累贅了)
我這裏使用的是簡單的鍵盤 需要看源碼點擊這裏
自定義輸入框View
1.控件屬性定義,自己根據需求羅列一下感覺需要的屬性如下
屬性名稱 | 作用 |
---|---|
textColor | 文字顏色默認黑色 |
textSize | 文文字尺寸默認 22 |
count | 輸入框個數默認6 |
width | 輸入框寬度默認40dp |
height | 輸入框高度默認40dp |
lineColor | 默認狀態的邊框顏色 默認黑色 |
fillColor | 默認狀態的填充顏色 默認白色 |
lineWidth | 默認狀態的邊框寬度 默認1dp |
focusLColor | 默認狀態的邊框顏色 默認黑色 |
focusFillColor | 默認狀態的填充顏色 默認白色 |
focusLineWidth | 默認狀態的邊框寬度 默認1dp |
employLColor | 默認狀態的邊框顏色 默認黑色 |
employFillColor | 默認狀態的填充顏色 默認白色 |
employLineWidth | 默認狀態的邊框寬度 默認1dp |
isContinuous | 輸入框是否連續(方便以後其他需要就添加了)默認true 連續 |
borderRadius | 文輸入框邊角半徑 默認0dp |
conceal | 是否隱藏文字 默認false 不隱藏 |
replaceString | 文字隱藏替換字符 默認沒有 |
replaceDrawable | 文字隱藏替換圖片(優先級高於replaceString) 默認沒有 |
circleRadius | 默認替換圖案半徑(圓形) 默認爲width的三分之一 |
circleColor | 默認替換圖案顏色 默認與textColor 一致 |
isContinuousRepeatChar | 是否過濾連續重複的字符 默認false 不過濾 |
isContinuousChar | 是否過濾連續的字符 默認false 不過濾 |
isInvokingKeyboard | 是否使用系統鍵盤 默認爲true 如果爲false的換需要自己手動調起鍵盤 |
根據自己的需求並考慮到我們後期的擴展性定義瞭如上屬性,屬性定義這個也比較簡單就不過多的描述:
2.初始化
首先我們要獲取屬性
通過:TypedArray typedArray =
context.obtainStyledAttributes(attrs, R.styleable.BorderPWEditText);獲取到typedArray 對象,使用typedArray對象我們將會獲取到用戶設置的屬性並可以指定屬性默認值
初始化畫筆
private void initPaint() {
//文字畫筆
paintText = new Paint(Paint.ANTI_ALIAS_FLAG);
paintText.setTextAlign(Paint.Align.CENTER);
paintText.setAntiAlias(true);
paintText.setTextSize(mTextSize);
paintText.setColor(mTextColor);
//邊框畫筆
borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setStrokeWidth(mLineWidth);
borderPaint.setColor(mLineColor);
borderPaint.setAntiAlias(true);
borderPaint.setStyle(Paint.Style.STROKE);
//填充畫筆
fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
fillPaint.setColor(mFillColor);
fillPaint.setAntiAlias(true);
fillPaint.setStyle(Paint.Style.FILL);
}
目前定義了三隻畫筆,可能有人說你不是有默認邊框填充,待輸入邊框填充,未輸入邊框填充類型嗎?但是我們梳理一下你你會發現 默認、待輸入、未輸入情況下畫筆的顏色和寬度不同,起始就只有填充和邊框畫筆類型所以我們就少定義一些變量,我們方法修改畫筆屬性並返回如下:我們只用傳當前的狀態
private Paint getBorderPaint(InputStatus status) {
if (InputStatus.No_Input == status) {
borderPaint.setStrokeWidth(mLineWidth);
borderPaint.setColor(mLineColor);
} else if (InputStatus.To_Input == status) {
borderPaint.setStrokeWidth(mFocusLineWidth);
borderPaint.setColor(mFocusLineColor);
} else if (InputStatus.Have_Input == status) {
borderPaint.setStrokeWidth(mEmployLineWidth);
borderPaint.setColor(mEmployLineColor);
}
return borderPaint;
}
private Paint getFillPaint(InputStatus status) {
if (InputStatus.No_Input == status) {
fillPaint.setColor(mFillColor);
} else if (InputStatus.To_Input == status) {
fillPaint.setColor(mFocusFillColor);
} else if (InputStatus.Have_Input == status) {
fillPaint.setColor(mEmployFillColor);
}
return fillPaint;
}
我們話默認替換圖案的畫筆呢,我們這個畫筆不一定適用,因爲用戶可能定義替換文字我們就直接適用文字畫筆了,如果用戶使用替換圖案我們話圖適不使用畫筆的,只有繪製默認替換圖適需要,所以我們這個畫筆在需要時再去延遲創建
根據屬性值初始化部分配置
還有什麼屬性需要設置呢?我當時也是不請求根據自己控件需要動態添加的,但是如果我們繼承EditText的話就需要注意了在初始完需要調用setBackgroundColor(Color.TRANSPARENT);進行處理,如果不處理你就能看到你繪製的控件上覆蓋着一條線,通過Xml佈局設置 background會引發其問題具體原因還沒有深究。可能我們還需要使用setFilters屬性設置部分攔截比如長度,重複處理等。我們在做其他自定義控件是就可以在這裏處理一個默認配置
開始測量,我們沒有指定測量的方式,這個根據自己定義控件的特性自行修改onMeasure完成測量
我們需要指定控件的繪製起始位置我們需要重新onSizeChanged方法這個方法在控件尺寸改變是會調用我們設置如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (isContinuous) {
startX = (w - count * mWidth) / 2;
} else {
startX = (w - count * mWidth - (count - 1) * intervalWidth) / 2;
}
}
繪製:接下來就是繪製的我們需要重新onDraw方法,看一下我們這裏很清晰因爲我把操作方法都封裝了
@Override
protected void onDraw(Canvas canvas) {
//繪製邊框
drawPeripheryBorder(canvas, position);
if (isConceal) {
//繪製替換符
drawConceal(canvas);
} else {
//繪製文字
drawText(canvas);
}
}
看看我們的具體實現:
先看drawPeripheryBorder()
/**
* 背景框繪製
*
* @param canvas
* @param position
*/
private void drawPeripheryBorder(Canvas canvas, int position) {
循環所有的邊框
for (int i = 0; i < count; i++) {
計算當前位置邊框的左上角與右下角的座標點
float left = startX + i * mWidth;
float top = 0;
float right = left + mWidth;
float bottom = mHeight;
if (!isContinuous) {
left = left + i * intervalWidth;
right = left + mWidth;
}
if (i < position) {
//輸入過的位置
if (i == 0) {
PeripheryBorder(canvas, left, top, right, bottom, 1, InputStatus.Have_Input);
} else if (i == count - 1) {
PeripheryBorder(canvas, left, top, right, bottom, 2, InputStatus.Have_Input);
} else {
PeripheryBorder(canvas, left, top, right, bottom, 3, InputStatus.Have_Input);
}
} else if (i > position) {
//未輸入的邊框
if (i == 0) {
PeripheryBorder(canvas, left, top, right, bottom, 1, InputStatus.No_Input);
} else if (i == count - 1) {
PeripheryBorder(canvas, left, top, right, bottom, 2, InputStatus.No_Input);
} else {
PeripheryBorder(canvas, left, top, right, bottom, 3, InputStatus.No_Input);
}
}
}
//結束時繪製選中框避免別覆蓋
if (position < count) {
float left = startX + position * mWidth;
float top = 0;
float right = left + mWidth;
float bottom = mHeight;
if (!isContinuous) {
left = left + position * intervalWidth;
right = left + mWidth;
}
if (position == 0) {
PeripheryBorder(canvas, left, top, right, bottom, 1, InputStatus.To_Input);
} else if (position == count - 1) {
PeripheryBorder(canvas, left, top, right, bottom, 2, InputStatus.To_Input);
} else {
PeripheryBorder(canvas, left - mFocusLineWidth / 2, top, right + mFocusLineWidth / 2, bottom, 3, InputStatus.To_Input);
}
}
}
看到嗎這裏嗎處理不同繪製的流程具體繪製還有方法
/**
* 繪製背景
*
* @param canvas 畫布
* @param left 左邊座標
* @param top 頭座標
* @param right 右座標
* @param bottom 下座標
* @param locationType 1.左邊,2.右邊 3.中間
* @param status 表示當前位置
*/
public void PeripheryBorder(Canvas canvas, float left, float top, float right, float bottom, int locationType, InputStatus status) {
int lineWidth = status == InputStatus.No_Input ? mLineWidth : (status == InputStatus.Have_Input ? mEmployLineWidth : mFocusLineWidth);
if (locationType == 1) {
//繪製最左邊(因爲我們有圓角,因爲連續的時候只有最左邊與最右邊有圓角)
Path FillPathRoundRect = new Path();
FillPathRoundRect.addRoundRect(
new RectF(left, top + lineWidth, right, bottom - lineWidth),
(isContinuous ? getLeftRadius() : getAllRadius()), //看到這句了嗎,他是判斷當前邊框是繪製的圓角位置
Path.Direction.CCW
);
canvas.drawPath(FillPathRoundRect, getFillPaint(status));
Path borderPathRoundRect = new Path();
borderPathRoundRect.addRoundRect(
new RectF(left, top + lineWidth / 2, right, bottom - lineWidth / 2),
isContinuous ? getLeftRadius() : getAllRadius(),
Path.Direction.CCW
);
canvas.drawPath(borderPathRoundRect, getBorderPaint(status));
} else if (locationType == 2) {
//繪製最右邊邊框
Path FillPathRoundRect = new Path();
FillPathRoundRect.addRoundRect(
new RectF(left, top + lineWidth, right, bottom - lineWidth),
isContinuous ? getRightRadius() : getAllRadius(),//看到這句了嗎,他是判斷當前邊框是繪製的圓角位置
Path.Direction.CCW
);
canvas.drawPath(FillPathRoundRect, getFillPaint(status));
Path borderPathRoundRect = new Path();
borderPathRoundRect.addRoundRect(
new RectF(left, top + lineWidth / 2, right, bottom - lineWidth / 2),
isContinuous ? getRightRadius() : getAllRadius(),
Path.Direction.CCW
);
canvas.drawPath(borderPathRoundRect, getBorderPaint(status));
} else if (locationType == 3) {
//繪製中間邊框
if (isContinuous) {
canvas.drawRect(
new RectF(left, top + lineWidth, right, bottom - lineWidth), getFillPaint(status)
);
canvas.drawRect(new RectF(left, top + lineWidth / 2, right, bottom - lineWidth / 2), getBorderPaint(status));
} else {
Path FillPathRoundRect = new Path();
FillPathRoundRect.addRoundRect(
new RectF(left, top + lineWidth, right, bottom - lineWidth),
getAllRadius(),
Path.Direction.CCW
);
canvas.drawPath(FillPathRoundRect, getFillPaint(status));
Path borderPathRoundRect = new Path();
borderPathRoundRect.addRoundRect(
new RectF(left, top + lineWidth / 2, right, bottom - lineWidth / 2),
getAllRadius(),
Path.Direction.CCW
);
canvas.drawPath(borderPathRoundRect, getBorderPaint(status));
}
}
}
接着我們看drawText()文字繪製方法:簡單的文本繪製
/**
* 繪製文字
*/
private void drawText(Canvas canvas) {
char[] chars = getText().toString().toCharArray();
for (int i = 0, n = chars.length; i < n; i++) {
//繪製輸入狀態
Paint.FontMetrics fontMetrics = paintText.getFontMetrics();
int baseLineY = (int) (mWidth / 2 - fontMetrics.top / 2 - fontMetrics.bottom / 2);
canvas.drawText(
String.valueOf(chars[i]),
(startX + i * mWidth + mWidth / 2 + (isContinuous ? 0 : i * intervalWidth)),
baseLineY,
paintText
);
}
}
drawConceal(canvas)方法:這個方法也很簡單就是判斷邏輯具體的操作有是方法,具體代碼我就不貼了需要看源碼點擊這裏
/**
* 繪製隱藏圖標
*
* @param canvas
*/
public void drawConceal(Canvas canvas) {
if (mReplaceDrawable != null) {
drawDrawableConceal(canvas); //繪製用戶指定的遮蓋圖標
} else if (!TextUtils.isEmpty(mReplaceString)) {
drawReplaceText(canvas); //繪製用戶設置的遮蓋字符
} else {
drawCircle(canvas); //設置默認的遮蓋圖案
}
}
到此我們的自定控件的代碼基本完成,起始自定義控件難點就在測量繪製這裏,當我們解決了測量繪製的問題基本上自定義view就大功告成了,後續可能需要添加部分設置啊,回調啊進行更友好的交互比如我們這個控件就添加了setmInputOverListener屬性用於監聽用戶輸入完成回調
自定義密碼輸入框的使用
添加依賴
1.在項目根build.gradle中添加如下代碼
allprojects {
repositories {
…
maven { url “https://jitpack.io” }
}
}
2. 在使用的Module中添加如下引用
dependencies {
…
implementation ‘com.github.rupertoL:SpecialView:1.2’
…
}
3.佈局Xml中使用:
<cn.lp.input_library.BorderPWEditText
android:id="@+id/BorderPWEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:inputType="text"
app:borderRadius="5dp"
app:conceal="true"
app:employFillColor="#FF9800"
app:employLColor="#F44336"
app:employLineWidth="2dp"
app:focusFillColor="#009688"
app:focusLColor="#673AB7"
app:focusLineWidth="2dp"
app:height="40dp"
app:isContinuousChar="true"
app:lineWidth="2dp"
app:width="40dp"
/>
根據前面的屬性說明自行添加相應屬性實現效果
4.監聽輸入
BorderPWEditText.setmInputOverListener(object : BorderPWEditText.InputOverListener {
override fun InputOver(string: String?) {
Toast.makeText(baseContext, "當前接收的數據爲:${string}", Toast.LENGTH_LONG).show()
}
override fun InputHint(string: String?) {
Toast.makeText(baseContext, string, Toast.LENGTH_LONG).show()
}
})
當然我們還可以使用最簡單的方式,就是什麼也不配置
<cn.lp.input_library.BorderPWEditText
android:id="@+id/PWEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:inputType="number"
/>