研究支付寶密碼輸入控件及源碼實作
目标效果圖
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9klaNBDOXlFcK1mYoxWbhZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TOycDMzkzMxETNwUDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
實作思路
- 要想實作輸入,就少不了EditText
- 看整體布局應該是一個橫向的LinearLayout
- 每個格子看進來應該是多個子View
- 那麼我們是不是有思路了?沒錯!一個LinearLayout包含了多個EditText,首先這個思路是對的;其次,有必要每個子View都是EditText嗎?我們在監聽文本變化時,隻需要對一個EditText添加TextWatcher,是以,隻需要一個EditText,其它的可以是TextView就可以了(PS:EditText是繼承自TextView的)
一、初步實作外觀架構
自定義View,通過繼承LinearLayout的方式
- 先看布局inputview.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<EditText
android:cursorVisible="false"
android:gravity="center"
android:id="@+id/indexView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/bg_rec"
android:text="a"/>
</LinearLayout>
需要注意的是要把光标設定成不可見,因為我們的目标效果中不應該有光标
2. 填充布局,關鍵代碼是在init方法中填充幾個TextView,并設定背景和LayoutParams
public class GridPasswordView extends LinearLayout {
private EditText mIndexView;
private LinearLayout mContainer;
public GridPasswordView(Context context) {
this(context, null);
}
public GridPasswordView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, );
}
public GridPasswordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOrientation(HORIZONTAL);
setBackgroundColor(Color.GRAY);
mContainer = (LinearLayout) View.inflate(getContext(), R.layout.inputview, null);
mIndexView = (EditText) mContainer.findViewById(R.id.indexView);
for (int i = ; i < ; i++) {
TextView child = new TextView(getContext());
child.setText(i + "");
child.setLayoutParams(new LinearLayout.LayoutParams(, ViewGroup.LayoutParams
.MATCH_PARENT, ));
child.setGravity(Gravity.CENTER);
child.setBackground(getResources().getDrawable(R.drawable.bg_rec));
mContainer.addView(child);
}
mContainer.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
addView(mContainer);
}
}
可以看到,我們又另外在mContainer中添加了5個TextView,并設定了内容
3. 矩形框背景使用的是shape
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:color="@color/colorAccent"
android:width="1dp"/>
</shape>
- 在demo中使用
<feifu.com.testview.gridpswview.GridPasswordView
android:background="#f00"
android:id="@+id/grid"
android:layout_width="match_parent"
android:layout_height="30dp"/>
效果圖如下:
我們把EditText放在第一位也是有原因的,後面會講。
到這裡就實作了一個空的架子。
二、實作輸入和删除邏輯
輸入和删除的邏輯就要接收軟鍵盤的輸入和輸出了,是以我們需要監聽EditText的文本變化,如果有輸入,就依次輸入,如果按了删除,就依次删除。
1. 對EditText添加監聽
/**
* 監聽indexview的内容變化
*/
private TextWatcher mIndextViewWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
oldText = s.toString();
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Log.e(TAG, "count=" + count + ",start=" + start + ",before=" + before + ",s=" + s);
//輸入内容
if (count == ) {
//文本到達最大長度,不再接受輸入,要保證edittext不變
if (currentIndex >= maxLength) {
mIndexView.removeTextChangedListener(this);
mIndexView.setText(s.charAt() + "");
mIndexView.setSelection(mIndexView.getText().length());//設定光标到最後,保證輸入文本順序
mIndexView.addTextChangedListener(this);//設定文本後再次添加監聽
return;
}
if (currentIndex == ) {
currentIndex++;
} else {
TextView child = (TextView) mContainer.getChildAt(currentIndex++);
child.setText(s.charAt(s.length() - ) + "");
//需要及時移除監聽器,否則會形成死循環
mIndexView.removeTextChangedListener(this);
mIndexView.setText(s.charAt() + "");
mIndexView.setSelection(mIndexView.getText().length());//設定光标到最後,保證輸入文本順序
mIndexView.addTextChangedListener(this);//設定文本後再次添加監聽
}
} else if (count == ) {
Log.e(TAG, "currentIndex=" + currentIndex);
if (currentIndex <= ) {
return;
}
//删除内容
if (currentIndex == ) {
currentIndex --;
}else {
TextView child = (TextView) mContainer.getChildAt(currentIndex - );
currentIndex --;
child.setText("");
mIndexView.removeTextChangedListener(this);
mIndexView.setText(oldText);
mIndexView.addTextChangedListener(this);//設定文本後再次添加監聽
mIndexView.setSelection(mIndexView.getText().length());//設定光标位置到最後,否則無法删除
}
}
}
@Override
public void afterTextChanged(Editable s) {
}
};
主要的邏輯就在onTextChanged方法中,首先來說一個幾個參數的意思:
* s 表示編輯後的文本内容
* start表示目前的光标位置
* before表示改變前的内容數量
* count表示改變的數量,1表示輸入一個字元,0表示删除一個字元
2. 在onTextChanged方法中
* 首先判斷是輸入還是删除,根據count來判斷,1表示輸入一個字元,0表示删除一個字元
* 使用currentIndex表示目前文本個數
* 因為每次操作的其實都是EditText的内容,是以要及時的把EditText的内容給設定回原來的内容,要注意的一點是,在TextWatcher中設定EditText内容時,要先移除監聽器再改變内容,改變完後再把監聽器設定回來,否則會形成死循環
到這裡,基本的輸入和輸出就搞定了。
添加自定義屬性
接下來就是要添加一些自定義的屬性了,這樣更友善我們來定義自己需要的樣式。
自定義屬性的方式
- 在res/values/檔案夾下建立檔案attrs.xml或者attrs_xxx.xml
- 在attrs.xml中定義自己需要的屬性
<declare-styleable name="GridPswView">
<attr name="passwordTransformation" format="string"/>
<attr name="length" format="integer"/>
<attr name="borderColor" format="color"/>
<attr name="borderWidth" format="dimension"/>
<attr name="backgroundColor" format="color"/>
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
</declare-styleable>
如果需要定義屬性的值域可以如下
<attr name="percent_circle_gravity">
<flag name="left" value="0" />
<flag name="top" value="1" />
<flag name="center" value="2" />
<flag name="right" value="3" />
<flag name="bottom" value="4" />
</attr>
-
在我們的自定義view中擷取屬性的值,并且設定到我們的view中
構造方法中會有兩個參數,context和attrs,我們就是要用這兩個參數來擷取屬性值
private void initAttr(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GridPswView);
if (typedArray != null) {
mBorderColor = typedArray.getColor(R.styleable.GridPswView_borderColor, Color.BLUE);
mTextColor = typedArray.getColor(R.styleable.GridPswView_textColor, Color.GRAY);
mBackgroundColor = typedArray.getColor(R.styleable.GridPswView_backgroundColor, Color
.LTGRAY);
mPswTransformation = typedArray.getString(R.styleable
.GridPswView_passwordTransformation);
mMaxLength = typedArray.getInt(R.styleable.GridPswView_length, );
mBroderWidth = typedArray.getDimension(R.styleable.GridPswView_borderWidth, );
mTextSize = typedArray.getDimension(R.styleable.GridPswView_textSize, );
}
}
擷取到值以後直接在view中設定屬性就可以了,這裡不再詳述。
4. 在xml中設定屬性的值
唯一需要注意的是要引入命名空間,在AndroidStudio中會自行引入,不必多言。
設定屬性目前有以下幾個選項,後序還會添加其它的:
* 背景顔色
* 字型顔色
* 邊框顔色
* 字型大小
* 隐藏字元
* 長度
<feifu.com.testview.gridpswview.GridPswView
android:background="#f00"
android:layout_gravity="center_vertical"
android:id="@+id/grid"
ddDog:textColor="#0f0"
ddDog:backgroundColor="#f00"
ddDog:length="9"
ddDog:textSize="30sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
好了現在運作一下吧,看看效果
設定的顔色不是很好,看着較Low,讀者可以自行設定自行設定。
遺留問題
待續。。。
github源碼:
https://github.com/dd-Dog/grid-psw-view