├── PasswordInputView.iml ├── PasswordInputView.jpg ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yuzhi │ │ └── fine │ │ └── passwordinputview │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yuzhi │ │ │ └── fine │ │ │ └── passwordinputview │ │ │ ├── MainActivity.java │ │ │ └── PasswordInputView.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── piv_attrs.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── yuzhi │ └── fine │ └── passwordinputview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle /PasswordInputView.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /PasswordInputView.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianshaojie/Android-PasswordInputView/67916679ff713888cf1f8687a0063e209daf57ad/PasswordInputView.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-PasswordInputView 2 | 3 | Android自定义密码输入框控件 4 | 5 | ![PasswordInputView.jpg](https://github.com/tianshaojie/Android-PasswordInputView/blob/master/PasswordInputView.jpg) 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.yuzhi.fine.passwordinputview" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.2.0' 26 | } 27 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yuzhi/fine/passwordinputview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.yuzhi.fine.passwordinputview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/yuzhi/fine/passwordinputview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yuzhi.fine.passwordinputview; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.Toast; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_main); 15 | 16 | final PasswordInputView passwordInputView = (PasswordInputView) findViewById(R.id.passwordInputView); 17 | Button button = (Button) findViewById(R.id.button); 18 | button.setOnClickListener(new View.OnClickListener() { 19 | @Override 20 | public void onClick(View v) { 21 | Toast.makeText(MainActivity.this, passwordInputView.getText(), Toast.LENGTH_SHORT).show(); 22 | } 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/yuzhi/fine/passwordinputview/PasswordInputView.java: -------------------------------------------------------------------------------- 1 | package com.yuzhi.fine.passwordinputview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.RectF; 9 | import android.util.AttributeSet; 10 | import android.util.DisplayMetrics; 11 | import android.util.TypedValue; 12 | import android.widget.EditText; 13 | 14 | import static android.graphics.Paint.ANTI_ALIAS_FLAG; 15 | 16 | /** 17 | * Desc: 18 | * User: tiansj 19 | */ 20 | public class PasswordInputView extends EditText { 21 | 22 | private static final int defaultContMargin = 5; 23 | private static final int defaultSplitLineWidth = 3; 24 | 25 | private int borderColor = 0xFFCCCCCC; 26 | private float borderWidth = 5; 27 | private float borderRadius = 3; 28 | 29 | private int passwordLength = 6; 30 | private int passwordColor = 0xFFCCCCCC; 31 | private float passwordWidth = 8; 32 | private float passwordRadius = 3; 33 | 34 | private Paint passwordPaint = new Paint(ANTI_ALIAS_FLAG); 35 | private Paint borderPaint = new Paint(ANTI_ALIAS_FLAG); 36 | private int textLength; 37 | 38 | public PasswordInputView(Context context, AttributeSet attrs) { 39 | super(context, attrs); 40 | 41 | DisplayMetrics dm = getResources().getDisplayMetrics(); 42 | borderWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, borderWidth, dm); 43 | borderRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, borderRadius, dm); 44 | passwordLength = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, passwordLength, dm); 45 | passwordWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, passwordWidth, dm); 46 | passwordRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, passwordRadius, dm); 47 | 48 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PasswordInputView, 0, 0); 49 | borderColor = a.getColor(R.styleable.PasswordInputView_pivBorderColor, borderColor); 50 | borderWidth = a.getDimension(R.styleable.PasswordInputView_pivBorderWidth, borderWidth); 51 | borderRadius = a.getDimension(R.styleable.PasswordInputView_pivBorderRadius, borderRadius); 52 | passwordLength = a.getInt(R.styleable.PasswordInputView_pivPasswordLength, passwordLength); 53 | passwordColor = a.getColor(R.styleable.PasswordInputView_pivPasswordColor, passwordColor); 54 | passwordWidth = a.getDimension(R.styleable.PasswordInputView_pivPasswordWidth, passwordWidth); 55 | passwordRadius = a.getDimension(R.styleable.PasswordInputView_pivPasswordRadius, passwordRadius); 56 | a.recycle(); 57 | 58 | borderPaint.setStrokeWidth(borderWidth); 59 | borderPaint.setColor(borderColor); 60 | passwordPaint.setStrokeWidth(passwordWidth); 61 | passwordPaint.setStyle(Paint.Style.FILL); 62 | passwordPaint.setColor(passwordColor); 63 | } 64 | 65 | @Override 66 | protected void onDraw(Canvas canvas) { 67 | int width = getWidth(); 68 | int height = getHeight(); 69 | 70 | // 外边框 71 | RectF rect = new RectF(0, 0, width, height); 72 | borderPaint.setColor(borderColor); 73 | canvas.drawRoundRect(rect, borderRadius, borderRadius, borderPaint); 74 | 75 | // 内容区 76 | RectF rectIn = new RectF(rect.left + defaultContMargin, rect.top + defaultContMargin, 77 | rect.right - defaultContMargin, rect.bottom - defaultContMargin); 78 | borderPaint.setColor(Color.WHITE); 79 | canvas.drawRoundRect(rectIn, borderRadius, borderRadius, borderPaint); 80 | 81 | // 分割线 82 | borderPaint.setColor(borderColor); 83 | borderPaint.setStrokeWidth(defaultSplitLineWidth); 84 | for (int i = 1; i < passwordLength; i++) { 85 | float x = width * i / passwordLength; 86 | canvas.drawLine(x, 0, x, height, borderPaint); 87 | } 88 | 89 | // 密码 90 | float cx, cy = height/ 2; 91 | float half = width / passwordLength / 2; 92 | for(int i = 0; i < textLength; i++) { 93 | cx = width * i / passwordLength + half; 94 | canvas.drawCircle(cx, cy, passwordWidth, passwordPaint); 95 | } 96 | } 97 | 98 | @Override 99 | protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 100 | super.onTextChanged(text, start, lengthBefore, lengthAfter); 101 | this.textLength = text.toString().length(); 102 | invalidate(); 103 | } 104 | 105 | public int getBorderColor() { 106 | return borderColor; 107 | } 108 | 109 | public void setBorderColor(int borderColor) { 110 | this.borderColor = borderColor; 111 | borderPaint.setColor(borderColor); 112 | invalidate(); 113 | } 114 | 115 | public float getBorderWidth() { 116 | return borderWidth; 117 | } 118 | 119 | public void setBorderWidth(float borderWidth) { 120 | this.borderWidth = borderWidth; 121 | borderPaint.setStrokeWidth(borderWidth); 122 | invalidate(); 123 | } 124 | 125 | public float getBorderRadius() { 126 | return borderRadius; 127 | } 128 | 129 | public void setBorderRadius(float borderRadius) { 130 | this.borderRadius = borderRadius; 131 | invalidate(); 132 | } 133 | 134 | public int getPasswordLength() { 135 | return passwordLength; 136 | } 137 | 138 | public void setPasswordLength(int passwordLength) { 139 | this.passwordLength = passwordLength; 140 | invalidate(); 141 | } 142 | 143 | public int getPasswordColor() { 144 | return passwordColor; 145 | } 146 | 147 | public void setPasswordColor(int passwordColor) { 148 | this.passwordColor = passwordColor; 149 | passwordPaint.setColor(passwordColor); 150 | invalidate(); 151 | } 152 | 153 | public float getPasswordWidth() { 154 | return passwordWidth; 155 | } 156 | 157 | public void setPasswordWidth(float passwordWidth) { 158 | this.passwordWidth = passwordWidth; 159 | passwordPaint.setStrokeWidth(passwordWidth); 160 | invalidate(); 161 | } 162 | 163 | public float getPasswordRadius() { 164 | return passwordRadius; 165 | } 166 | 167 | public void setPasswordRadius(float passwordRadius) { 168 | this.passwordRadius = passwordRadius; 169 | invalidate(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 27 | 28 |