├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── layout
│ │ │ ├── activity_main.xml
│ │ │ ├── activity_custom_style.xml
│ │ │ └── activity_simple.xml
│ │ └── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ └── com
│ │ │ └── lwjfork
│ │ │ └── example
│ │ │ ├── style
│ │ │ ├── CustomTextDrawer.java
│ │ │ └── CustomeBlockDrawer.java
│ │ │ ├── MainActivity.java
│ │ │ ├── SimpleActivity.java
│ │ │ └── CustomStyleActivity.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── library
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── attrs.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── lwjfork
│ │ └── code
│ │ ├── text
│ │ ├── NoneTextDrawer.java
│ │ ├── PasswordTextDrawer.java
│ │ ├── TextDrawer.java
│ │ └── BaseTextDrawer.java
│ │ ├── style
│ │ ├── CodeInputType.java
│ │ └── BlockShape.java
│ │ ├── cusor
│ │ ├── CursorDrawable.java
│ │ └── CursorDrawer.java
│ │ ├── block
│ │ ├── NoneBlockDrawer.java
│ │ ├── StrokeBlockDrawer.java
│ │ ├── SolidBlockDrawer.java
│ │ ├── UnderlineBlockDrawer.java
│ │ └── BaseBlockDrawer.java
│ │ ├── base
│ │ └── BaseDrawer.java
│ │ └── CodeEditText.java
├── gradle.properties
├── proguard-rules.pro
└── build.gradle
├── example.jpg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle
├── .gitignore
├── gradle.properties
├── .github
└── workflows
│ └── android.yml
├── README.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/example.jpg
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CodeEditText
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | library
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 | def submoduleProject = project(':library')
3 | submoduleProject.name = 'CodeEditText'
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwjfork/CodeEditText/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed May 13 13:42:37 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/text/NoneTextDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.text;
2 |
3 | import android.graphics.Rect;
4 |
5 | /**
6 | * Created by lwj on 2019/1/12.
7 | * lwjfork@gmail.com
8 | * 什么都不绘制
9 | */
10 | public class NoneTextDrawer extends BaseTextDrawer {
11 |
12 |
13 | public NoneTextDrawer(int codeInputType, int codeTextColor, int codeTextSize, int dotRadius) {
14 | super(codeInputType, codeTextColor, codeTextSize, dotRadius);
15 | }
16 |
17 | @Override
18 | protected void drawText(Rect rect, char c) {
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/text/PasswordTextDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.text;
2 |
3 | import android.graphics.Rect;
4 |
5 | /**
6 | * Created by lwj on 2019/1/12.
7 | * lwjfork@gmail.com
8 | * 密文绘制
9 | */
10 | public class PasswordTextDrawer extends BaseTextDrawer {
11 |
12 |
13 | public PasswordTextDrawer(int codeInputType, int codeTextColor, int codeTextSize, int dotRadius) {
14 | super(codeInputType, codeTextColor, codeTextSize, dotRadius);
15 | }
16 |
17 | @Override
18 | protected void drawText(Rect rect, char c) {
19 | canvas.drawCircle(rect.centerX(), rect.centerY(), dotRadius, textPaint);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse
2 | .project
3 | .classpath
4 | .settings
5 | .checkstyle
6 |
7 | # IntelliJ IDEA
8 | .idea
9 | *.iml
10 |
11 | *.ipr
12 | *.iws
13 | classes
14 | gen-external-apklibs
15 | *.class
16 | out/
17 |
18 | # Gradle
19 | .gradle
20 | #gradle
21 | /.idea/workspace.xml
22 | /.idea/libraries/*
23 | build
24 |
25 | # Maven
26 | target
27 | release.properties
28 | pom.xml.*
29 |
30 | # Ant
31 | bin
32 | gen
33 | build.xml
34 | ant.properties
35 | local.properties
36 | proguard.cfg
37 | proguard-project.txt
38 | /local.properties
39 |
40 | # Other
41 | tmp
42 | .DS_Store
43 | *.hprof
44 |
45 | # custom
46 | /apks
47 | *.apk
48 | app/mirror
49 |
50 | # Android
51 | *.li
52 | *.log
53 | captures
54 | /mapping*.txt
55 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/style/CodeInputType.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.style;
2 |
3 | import android.support.annotation.IntDef;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 |
8 | import static com.lwjfork.code.style.CodeInputType.NONE;
9 | import static com.lwjfork.code.style.CodeInputType.PASSWORD;
10 | import static com.lwjfork.code.style.CodeInputType.TEXT;
11 |
12 | /**
13 | * Created by lwj on 2019/1/17.
14 | * lwjfork@gmail.com
15 | */
16 | @Retention(RetentionPolicy.SOURCE)
17 | @IntDef({PASSWORD, TEXT, NONE})
18 | public @interface CodeInputType {
19 | int PASSWORD = 1; // 密码样式
20 | int TEXT = 2; // 明文
21 | int NONE = -1; // 什么都不画
22 | }
23 |
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | PUBLISH_GROUP_ID=io.github.lwjfork
2 | PUBLISH_ARTIFACT_ID=CodeEditText
3 | PUBLISH_VERSION=1.0.7
4 |
5 | # pom
6 | POM_NAME=CodeEditText
7 | POM_DESCRIPTION=CodeEditText
8 | POM_URL=https://github.com/lwjfork/CodeEditText
9 | ## pom.scm
10 | POM_SCM_CONNECTION=scm:git@github.com:lwjfork/CodeEditText.git
11 | POM_SCM_DEV_CONNECTION=scm:git@github.com:lwjfork/CodeEditText.git
12 | POM_SCM_URL=https://github.com/lwjfork/CodeEditText.git
13 |
14 | ## pom.licenses
15 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
16 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
17 |
18 | ## pom.developers
19 | POM_DEVELOPER_ID=lwjfork
20 | POM_DEVELOPER_NAME=lwjfork
21 | POM_DEVELOPER_EMAIL=lwjfork@gmail.com
22 |
23 | PROJECT_NAME=CodeEditText
24 |
25 |
26 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/cusor/CursorDrawable.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.cusor;
2 |
3 | import android.graphics.drawable.ShapeDrawable;
4 |
5 | /**
6 | * Created by lwj on 2019/1/12.
7 | * lwjfork@gmail.com
8 | * 光标
9 | */
10 | public class CursorDrawable extends ShapeDrawable {
11 | private int mHeight;
12 |
13 | public CursorDrawable(int cursorColor, int cursorWidth, int cursorHeight) {
14 | mHeight = cursorHeight;
15 | setDither(false);
16 | getPaint().setColor(cursorColor);
17 | setIntrinsicWidth(cursorWidth);
18 | }
19 |
20 | public void setBounds(int paramInt1, int paramInt2, int paramInt3, int paramInt4) {
21 | super.setBounds(paramInt1, paramInt2, paramInt3, this.mHeight + paramInt2);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | DEFAULT_GRADLE_VERSION=3.1.2
15 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/style/BlockShape.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.style;
2 |
3 | import android.support.annotation.IntDef;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 |
8 | import static com.lwjfork.code.style.BlockShape.NONE;
9 | import static com.lwjfork.code.style.BlockShape.SOLID;
10 | import static com.lwjfork.code.style.BlockShape.STROKE;
11 | import static com.lwjfork.code.style.BlockShape.UNDERLINE;
12 |
13 | /**
14 | * Created by lwj on 2019/1/17.
15 | * lwjfork@gmail.com
16 | */
17 | @Retention(RetentionPolicy.SOURCE)
18 | @IntDef({STROKE, SOLID, UNDERLINE, NONE})
19 | public @interface BlockShape {
20 | int STROKE = 1; // 边框
21 | int SOLID = 2; // 填充
22 | int UNDERLINE = 3; // 下划线
23 | int NONE = -1; // 什么都不画
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/block/NoneBlockDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.block;
2 |
3 | import android.graphics.RectF;
4 |
5 | /**
6 | * Created by lwj on 2019/1/12.
7 | * lwjfork@gmail.com
8 | * 任性,就是什么都不画
9 | */
10 | public class NoneBlockDrawer extends BaseBlockDrawer {
11 |
12 | public NoneBlockDrawer(int blockNormalColor, int blockFocusedColor, int blockErrorColor, int blockShape, int blockLineWidth, int blockCorner) {
13 | super(blockNormalColor, blockFocusedColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
14 | }
15 |
16 | @Override
17 | protected void drawFocusedBlock(RectF rectF) {
18 |
19 | }
20 |
21 | @Override
22 | protected void drawNormalBlock(RectF rectF) {
23 |
24 | }
25 |
26 | @Override
27 | protected void drawErrorBlock(RectF rectF) {
28 |
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/block/StrokeBlockDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.block;
2 |
3 | import android.graphics.Paint;
4 |
5 | /**
6 | * Created by lwj on 2019/1/12.
7 | * lwjfork@gmail.com
8 | * 边框绘制者
9 | */
10 | public class StrokeBlockDrawer extends SolidBlockDrawer {
11 |
12 |
13 | public StrokeBlockDrawer() {
14 | super();
15 | }
16 |
17 |
18 | public StrokeBlockDrawer(int blockNormalColor, int blockFocusedColor, int blockErrorColor, int blockShape, int blockLineWidth, int blockCorner) {
19 | super(blockNormalColor, blockFocusedColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
20 | }
21 |
22 | @Override
23 | protected void initPaint() {
24 | super.initPaint();
25 | blockPaint.setStyle(Paint.Style.STROKE);
26 | blockPaint.setStrokeWidth(blockLineWidth);
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lwjfork/example/style/CustomTextDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.example.style;
2 |
3 | import android.graphics.Paint;
4 | import android.graphics.Rect;
5 |
6 | import com.lwjfork.code.text.BaseTextDrawer;
7 |
8 | /**
9 | * Created by lwj on 2019/1/17.
10 | * lwjfork@gmail.com
11 | */
12 | public class CustomTextDrawer extends BaseTextDrawer {
13 |
14 | private int textBaseLineY;
15 |
16 | public CustomTextDrawer() {
17 | super();
18 | }
19 |
20 | @Override
21 | protected void drawText(Rect rect, char c) {
22 | Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
23 | float top = fontMetrics.top;// 基线到字体上边框的距离
24 | float bottom = fontMetrics.bottom;// 基线到字体下边框的距离
25 | textBaseLineY = (int) (rect.centerY() - top / 2 - bottom / 2);
26 | canvas.drawText("X", rect.centerX(), textBaseLineY, textPaint);
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | def SLVersion = rootProject.ext.supportLibVersion
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.ext.minSdkVersion
10 | targetSdkVersion rootProject.ext.targetSdkVersion
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | lintOptions {
24 | abortOnError false
25 | }
26 |
27 | }
28 |
29 |
30 | dependencies {
31 | implementation fileTree(include: ['*.jar'], dir: 'libs')
32 | implementation "com.android.support:appcompat-v7:$SLVersion"
33 | implementation 'io.github.lwjfork:CodeEditText:1.0.5'
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lwjfork/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.example;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.view.View;
6 |
7 |
8 | public class MainActivity extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_main);
14 |
15 | findViewById(R.id.btn_simple).setOnClickListener(new View.OnClickListener() {
16 | @Override
17 | public void onClick(View v) {
18 | SimpleActivity.launch(MainActivity.this);
19 | }
20 | });
21 | findViewById(R.id.btn_custom_style).setOnClickListener(new View.OnClickListener() {
22 | @Override
23 | public void onClick(View v) {
24 | CustomStyleActivity.launch(MainActivity.this);
25 | }
26 | });
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/text/TextDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.text;
2 |
3 | import android.graphics.Paint;
4 | import android.graphics.Rect;
5 |
6 | import com.lwjfork.code.style.CodeInputType;
7 |
8 | /**
9 | * Created by lwj on 2019/1/12.
10 | * lwjfork@gmail.com
11 | * 明文绘制
12 | */
13 | public class TextDrawer extends BaseTextDrawer {
14 | private int textBaseLineY;
15 | public TextDrawer(@CodeInputType int codeInputType, int codeTextColor, int codeTextSize, int dotRadius) {
16 | super(codeInputType, codeTextColor, codeTextSize, dotRadius);
17 | }
18 |
19 | @Override
20 | protected void drawText(Rect rect, char c) {
21 | Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
22 | float top = fontMetrics.top;// 基线到字体上边框的距离
23 | float bottom = fontMetrics.bottom;// 基线到字体下边框的距离
24 | textBaseLineY = (int) (rect.centerY() - top / 2 - bottom / 2);
25 | canvas.drawText(c + "", rect.centerX(), textBaseLineY, textPaint);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
15 |
16 |
22 |
23 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | def SLVersion = rootProject.ext.supportLibVersion
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.ext.minSdkVersion
10 | targetSdkVersion rootProject.ext.targetSdkVersion
11 | versionCode 1
12 | versionName version
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 |
20 | }
21 | lintOptions {
22 | abortOnError false
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 |
29 | dexOptions {
30 | javaMaxHeapSize "8g"
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation fileTree(dir: 'libs', include: ['*.jar'])
36 | compileOnly "com.android.support:appcompat-v7:$SLVersion"
37 | }
38 |
39 |
40 | apply from: "https://raw.githubusercontent.com/lwjfork/scriptlib/master/gradle/publish/maven/aar/publish-no-kotlin.gradle"
--------------------------------------------------------------------------------
/app/src/main/java/com/lwjfork/example/style/CustomeBlockDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.example.style;
2 |
3 | import android.graphics.Color;
4 | import android.graphics.Paint;
5 | import android.graphics.RectF;
6 |
7 | import com.lwjfork.code.block.BaseBlockDrawer;
8 |
9 | /**
10 | * Created by lwj on 2019/1/17.
11 | * lwjfork@gmail.com
12 | */
13 | public class CustomeBlockDrawer extends BaseBlockDrawer {
14 |
15 |
16 | public CustomeBlockDrawer() {
17 | super();
18 | }
19 |
20 | @Override
21 | protected void initPaint() {
22 | super.initPaint();
23 | blockPaint.setStyle(Paint.Style.FILL_AND_STROKE);
24 | }
25 |
26 |
27 | @Override
28 | protected void drawFocusedBlock(RectF rectF) {
29 | drawRect(Color.BLUE, rectF);
30 | }
31 |
32 | @Override
33 | protected void drawNormalBlock(RectF rectF) {
34 | drawRect(Color.GREEN, rectF);
35 | }
36 |
37 | @Override
38 | protected void drawErrorBlock(RectF rectF) {
39 | drawRect(Color.RED, rectF);
40 | }
41 |
42 | private void drawRect(int color, RectF rectF) {
43 | if (rectF == null) {
44 | return;
45 | }
46 | blockPaint.setColor(color);
47 | canvas.drawRoundRect(rectF, blockCorner, blockCorner, blockPaint);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lwjfork/example/SimpleActivity.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.example;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 |
9 | import com.lwjfork.code.CodeEditText;
10 |
11 | /**
12 | * Created by lwj on 2019/1/17.
13 | * lwjfork@gmail.com
14 | */
15 | public class SimpleActivity extends Activity {
16 |
17 |
18 | public static void launch(Context context) {
19 | Intent intent = new Intent();
20 | intent.setClass(context, SimpleActivity.class);
21 | context.startActivity(intent);
22 | }
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_simple);
28 |
29 | CodeEditText et_test = findViewById(R.id.et_test);
30 | et_test.setOnTextChangedListener(new CodeEditText.OnTextChangedListener() {
31 | @Override
32 | public void onCodeChanged(CharSequence changeText) {
33 | Log.e("SimpleActivity", String.format("onCodeChanged -- %s", changeText + ""));
34 | }
35 |
36 | @Override
37 | public void onInputCompleted(CharSequence text) {
38 | Log.e("SimpleActivity", String.format("onInputCompleted -- %s", text + ""));
39 | }
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/block/SolidBlockDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.block;
2 |
3 | import android.graphics.Paint;
4 | import android.graphics.RectF;
5 |
6 | import com.lwjfork.code.style.BlockShape;
7 |
8 | /**
9 | * Created by lwj on 2019/1/12.
10 | * lwjfork@gmail.com
11 | * 填充色绘制者
12 | */
13 | public class SolidBlockDrawer extends BaseBlockDrawer {
14 |
15 | public SolidBlockDrawer() {
16 | super();
17 | }
18 |
19 | public SolidBlockDrawer(int blockNormalColor, int blockFocusColor, int blockErrorColor, @BlockShape int blockShape, int blockLineWidth, int blockCorner) {
20 | super(blockNormalColor, blockFocusColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
21 | }
22 |
23 | @Override
24 | protected void initPaint() {
25 | super.initPaint();
26 | blockPaint.setStyle(Paint.Style.FILL);
27 | }
28 |
29 |
30 | @Override
31 | protected void drawFocusedBlock(RectF rectF) {
32 | drawRect(blockFocusColor, rectF);
33 | }
34 |
35 | @Override
36 | protected void drawNormalBlock(RectF rectF) {
37 | drawRect(blockNormalColor, rectF);
38 | }
39 |
40 | @Override
41 | protected void drawErrorBlock(RectF rectF) {
42 | drawRect(blockErrorColor, rectF);
43 | }
44 |
45 | private void drawRect(int color, RectF rectF) {
46 | if (rectF == null) {
47 | return;
48 | }
49 | blockPaint.setColor(color);
50 | canvas.drawRoundRect(rectF, blockCorner, blockCorner, blockPaint);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/block/UnderlineBlockDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.block;
2 |
3 | import android.graphics.Paint;
4 | import android.graphics.RectF;
5 |
6 | /**
7 | * Created by lwj on 2019/1/12.
8 | * lwjfork@gmail.com
9 | * 下划线绘制者
10 | */
11 | public class UnderlineBlockDrawer extends BaseBlockDrawer {
12 |
13 | public UnderlineBlockDrawer() {
14 | super();
15 | }
16 |
17 | public UnderlineBlockDrawer(int blockNormalColor, int blockFocusedColor, int blockErrorColor, int blockShape, int blockLineWidth, int blockCorner) {
18 | super(blockNormalColor, blockFocusedColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
19 | }
20 |
21 | protected void initPaint() {
22 | super.initPaint();
23 | blockPaint.setStyle(Paint.Style.FILL);
24 | blockPaint.setStrokeWidth(blockLineWidth);
25 | }
26 |
27 |
28 | @Override
29 | protected void drawFocusedBlock(RectF rectF) {
30 | drawLine(blockFocusColor, rectF);
31 | }
32 |
33 | @Override
34 | protected void drawNormalBlock(RectF rectF) {
35 | drawLine(blockNormalColor, rectF);
36 | }
37 |
38 | @Override
39 | protected void drawErrorBlock(RectF rectF) {
40 | drawLine(blockErrorColor, rectF);
41 | }
42 |
43 | private void drawLine(int color, RectF rectF) {
44 | if (rectF == null) {
45 | return;
46 | }
47 | blockPaint.setColor(color);
48 | canvas.drawLine(rectF.left, rectF.bottom - blockLineWidth, rectF.right, rectF.bottom - blockLineWidth, blockPaint);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | # We'll run this workflow when a new GitHub release is created
6 | types: [released]
7 | jobs:
8 | publish:
9 | name: Release build and publish
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Check out code
13 | uses: actions/checkout@v2
14 | - name: Set up JDK 1.8
15 | uses: actions/setup-java@v1
16 | with:
17 | java-version: 1.8
18 |
19 | # Base64 decodes and pipes the GPG key content into the secret file
20 | - name: Prepare environment
21 | env:
22 | GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }}
23 | SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }}
24 | run: |
25 | git fetch --unshallow
26 | sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'"
27 |
28 | # Builds the release artifacts of the library
29 | - name: Release build
30 | run: ./gradlew :CodeEditText:assembleRelease
31 |
32 | # Generates other artifacts (javadocJar is optional)
33 | - name: Source jar and dokka
34 | run: ./gradlew :CodeEditText:androidSourcesJar :CodeEditText:javadocJar
35 |
36 | # Runs upload, and then closes & releases the repository
37 | - name: Publish to MavenCentral
38 | run: ./gradlew :CodeEditText:publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository
39 | env:
40 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
41 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
42 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
43 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
44 | SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }}
45 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/base/BaseDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.base;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.PorterDuff;
7 | import android.graphics.Rect;
8 |
9 | import java.util.ArrayList;
10 |
11 | /**
12 | * Created by lwj on 2019/1/12.
13 | * lwjfork@gmail.com
14 | */
15 | public abstract class BaseDrawer {
16 |
17 | protected Canvas canvas;
18 | protected ArrayList blockRects;
19 | protected int currentBlockIndex;
20 | private boolean isFocused = false;
21 |
22 |
23 | public Bitmap createBitmapAndCanvas(int measureWidth, int measureHeight) {
24 | Bitmap blockBitmap = Bitmap.createBitmap(measureWidth, measureHeight, Bitmap.Config.ARGB_8888);
25 | canvas = new Canvas();
26 | canvas.setBitmap(blockBitmap);
27 | return blockBitmap;
28 | }
29 |
30 | public void setBlockRects(ArrayList blockRects) {
31 | this.blockRects = blockRects;
32 | }
33 |
34 | public void setCurrentBlockIndex(int currentBlockIndex) {
35 | this.currentBlockIndex = currentBlockIndex;
36 | }
37 |
38 | public void setFocused(boolean isFocused) {
39 | this.isFocused = isFocused;
40 | }
41 |
42 |
43 | public boolean isFocused() {
44 | return isFocused;
45 | }
46 |
47 | protected void clearCanvas(Canvas canvas) {
48 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // 清空
49 | }
50 |
51 |
52 | public Canvas getCanvas() {
53 | return canvas;
54 | }
55 |
56 | public void setCanvas(Canvas canvas) {
57 | this.canvas = canvas;
58 | }
59 |
60 | public ArrayList getBlockRects() {
61 | return blockRects;
62 | }
63 |
64 | public int getCurrentBlockIndex() {
65 | return currentBlockIndex;
66 | }
67 |
68 | public abstract void drawCanvas();
69 | }
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CodeEditText
2 | 验证码,密码输入框,文本支持明文和密码及透明三种形式展示,背景支持下划线,填充色块,边框,透明四种
3 |
4 |
5 | ## 引入
6 |
7 | ```
8 | implementation 'io.github.lwjfork:CodeEditText:1.0.5'
9 |
10 | <<<<<<< HEAD
11 | =======
12 |
13 | >>>>>>> 4ebe4af283c3e57d3b746e6a0f575a5edaece24d
14 | ```
15 |
16 |
17 | ## 原理
18 | >> 继承EditText,测量宽高后,根据每块间距、EditText的宽度以及可输入最大长度计算出每块区域的left top right bottom
19 |
20 | >> 将EditText的背景取消、字体颜色和光标透明不显示,监听输入并根据计算的每块的left top right bottom 自行绘制文本和背景
21 |
22 | ## 注意事项(优点)
23 | 1. 此EditText 在输入文本方面没有对除最大长度以外做任何限制,即需要自己控制输入数字、汉字、空格等内容限制
24 | 2. 此EditText 键盘的控制需要自行控制,可以使用系统键盘,也可以自定义键盘
25 |
26 |
27 | # 属性讲解
28 | ## 背景样式设置
29 |
30 | 属性名 | 属性说明
31 | :---:|:---:
32 | blockNormalColor | 正常状态下的颜色 默认为字体颜色
33 | blockFocusColor | 待输入且EditText获取了焦点时颜色 默认值为blockNormalColor
34 | blockErrorColor | 错误状态下颜色 默认值为blockNormalColor
35 | blockLineWidth | 绘制边框或者下划线的线的宽度 默认1dp
36 | blockSpace | 块与块之间的间距 默认0
37 | blockShape | 块的样式暂时支持四种 none solid stroke underline 默认值为none
38 |
39 |
40 | blockShape 属性值 | 属性说明
41 | :---:|:---:
42 | none | 什么都不绘制,空白
43 | solid | 填充色块
44 | stroke | 绘制边框
45 | underline | 下划线
46 |
47 | ## 文本样式设置
48 |
49 |
50 | 属性名 | 属性说明
51 | :---:|:---:
52 | codeTextColor | 文本或者密码圆点颜色
53 | codeTextSize | 文本尺寸,当 codeInputType 为 text 时生效。 默认为12sp
54 | maxCodeLength | 输入的最大长度 默认为6
55 | dotRadius | 密码圆点半径,当codeInputType为 password 生效。默认为5dp
56 | codeInputType | password (密码圆点展示)text 明文展示。 默认text
57 |
58 | codeInputType 属性值 | 属性说明
59 | :---:|:---:
60 | none | 什么都不绘制,空白
61 | text | 明文
62 | password | 密码圆点展示
63 |
64 | ## 代码混淆
65 |
66 | ```
67 | -keep class com.lwjfork.code.** { *;}
68 | -dontwarn com.lwjfork.code.**
69 | ```
70 |
71 | ## 效果图
72 |
73 |
74 |
75 |
76 | ## Change list
77 |
78 | ## 1.0.4
79 | 1. fix 当code长度超过6位时,绘制错误
80 |
81 |
82 | ## 1.0.2
83 | 1. 支持自定义样式,具体参考demo里的 CustomStyleActivity
84 | 2. 添加addCharSequence 方法
85 |
86 | ## 1.0.1
87 | 1. 添加 delete 和 addChar 方法 方便自定义键盘
88 |
89 |
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lwjfork/example/CustomStyleActivity.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.example;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 | import android.view.View;
9 |
10 | import com.lwjfork.code.CodeEditText;
11 | import com.lwjfork.example.style.CustomTextDrawer;
12 | import com.lwjfork.example.style.CustomeBlockDrawer;
13 |
14 | /**
15 | * Created by lwj on 2019/1/17.
16 | * lwjfork@gmail.com
17 | */
18 | public class CustomStyleActivity extends Activity {
19 |
20 |
21 | public static void launch(Context context) {
22 | Intent intent = new Intent();
23 | intent.setClass(context, CustomStyleActivity.class);
24 | context.startActivity(intent);
25 | }
26 |
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.activity_custom_style);
32 | final CodeEditText et_test = (CodeEditText) findViewById(R.id.et_test);
33 |
34 | et_test.setOnTextChangedListener(new CodeEditText.OnTextChangedListener() {
35 | @Override
36 | public void onCodeChanged(CharSequence changeText) {
37 | Log.e(" onCodeChanged Text ", changeText + "");
38 | }
39 |
40 | @Override
41 | public void onInputCompleted(CharSequence text) {
42 | Log.e("onInputCompleted Text ", text + "");
43 | }
44 | });
45 | findViewById(R.id.btn_chang_block).setOnClickListener(new View.OnClickListener() {
46 | @Override
47 | public void onClick(View v) {
48 | et_test.setBlockShape(new CustomeBlockDrawer());
49 | }
50 | });
51 | findViewById(R.id.btn_chang_text).setOnClickListener(new View.OnClickListener() {
52 | @Override
53 | public void onClick(View v) {
54 | et_test.setCodeInputType(new CustomTextDrawer());
55 | }
56 | });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_custom_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
16 |
41 |
42 |
43 |
48 |
49 |
55 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/text/BaseTextDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.text;
2 |
3 | import android.graphics.Paint;
4 | import android.graphics.Rect;
5 | import android.support.annotation.ColorInt;
6 |
7 | import com.lwjfork.code.base.BaseDrawer;
8 | import com.lwjfork.code.style.CodeInputType;
9 |
10 |
11 | /**
12 | * Created by lwj on 2019/1/12.
13 | * lwjfork@gmail.com
14 | */
15 | public abstract class BaseTextDrawer extends BaseDrawer {
16 |
17 | @CodeInputType
18 | protected int codeInputType; // 显示样式 支持明文和密码两种,密码时画圆点
19 | @ColorInt
20 | protected int codeTextColor;// 显示圆点、明文时的字体颜色
21 | protected int codeTextSize;// 明文时字体大小
22 | protected int dotRadius;// 密码样式时圆点的半径
23 |
24 | protected Paint textPaint; // 文字
25 |
26 | protected String content = new String();
27 |
28 | public BaseTextDrawer() {
29 | initPaint();
30 | }
31 |
32 | public BaseTextDrawer(@CodeInputType int codeInputType, int codeTextColor, int codeTextSize, int dotRadius) {
33 | this.codeInputType = codeInputType;
34 | this.codeTextColor = codeTextColor;
35 | this.codeTextSize = codeTextSize;
36 | this.dotRadius = dotRadius;
37 | initPaint();
38 | }
39 |
40 |
41 | public void initPaint() {
42 | textPaint = new Paint();
43 | textPaint.setColor(codeTextColor);
44 | textPaint.setAntiAlias(true);
45 | textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
46 | textPaint.setTextAlign(Paint.Align.CENTER);
47 | textPaint.setTextSize(codeTextSize);
48 | }
49 |
50 | @Override
51 | public void drawCanvas() {
52 | if (canvas == null) {
53 | return;
54 | }
55 | if (textPaint == null) {
56 | initPaint();
57 | }
58 | clearCanvas(canvas);
59 | int length = content.length();
60 | for (int i = 0; i < length; i++) {
61 | drawText(blockRects.get(i), content.charAt(i));
62 | }
63 | }
64 |
65 |
66 | protected abstract void drawText(Rect rect, char c);
67 |
68 | public String getContent() {
69 | return content;
70 | }
71 |
72 | public void setContent(String content) {
73 | this.content = content;
74 | drawCanvas();
75 | }
76 |
77 |
78 | @CodeInputType
79 | public int getCodeInputType() {
80 | return codeInputType;
81 | }
82 |
83 | public void setCodeInputType(@CodeInputType int codeInputType) {
84 | this.codeInputType = codeInputType;
85 | }
86 |
87 | public int getCodeTextColor() {
88 | return codeTextColor;
89 | }
90 |
91 | public void setCodeTextColor(int codeTextColor) {
92 | this.codeTextColor = codeTextColor;
93 | textPaint.setColor(codeTextColor);
94 | drawCanvas();
95 | }
96 |
97 | public int getCodeTextSize() {
98 | return codeTextSize;
99 | }
100 |
101 | public void setCodeTextSize(int codeTextSize) {
102 | this.codeTextSize = codeTextSize;
103 | textPaint.setTextSize(codeTextSize);
104 | if (codeInputType == CodeInputType.TEXT) {
105 | drawCanvas();
106 | }
107 | }
108 |
109 | public int getDotRadius() {
110 | return dotRadius;
111 | }
112 |
113 | public void setDotRadius(int dotRadius) {
114 | this.dotRadius = dotRadius;
115 | if (codeInputType == CodeInputType.PASSWORD) {
116 | drawCanvas();
117 | }
118 | }
119 |
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/cusor/CursorDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.cusor;
2 |
3 | import android.graphics.Paint;
4 | import android.graphics.Rect;
5 | import android.support.annotation.ColorInt;
6 |
7 | import com.lwjfork.code.base.BaseDrawer;
8 |
9 |
10 | /**
11 | * Created by lwj on 2019/1/12.
12 | * lwjfork@gmail.com
13 | * 光标绘制者
14 | */
15 | public class CursorDrawer extends BaseDrawer {
16 | private boolean showCursor;// 是否显示光标
17 | private int cursorDuration;// 光标闪烁间隔
18 | private int cursorWidth;// 光标宽度
19 | private int cursorHeight;// 光标高度
20 | @ColorInt
21 | private int cursorColor;// 光标颜色 没有设置时默认黑色
22 | private Paint cursorPaint;
23 |
24 | public CursorDrawer(boolean showCursor, int cursorDuration, int cursorWidth, int cursorHeight, int cursorColor) {
25 | this.showCursor = showCursor;
26 | this.cursorDuration = cursorDuration;
27 | this.cursorWidth = cursorWidth;
28 | this.cursorColor = cursorColor;
29 | this.cursorHeight = cursorHeight;
30 | iniPaint();
31 | }
32 |
33 | private void iniPaint() {
34 | cursorPaint = new Paint();
35 | cursorPaint.setAntiAlias(true);
36 | cursorPaint.setColor(cursorColor);
37 | cursorPaint.setStyle(Paint.Style.FILL);
38 | cursorPaint.setStrokeWidth(cursorWidth);
39 | }
40 |
41 | @Override
42 | public void setFocused(boolean isFocused) {
43 | super.setFocused(isFocused);
44 | }
45 |
46 | @Override
47 | public void drawCanvas() {
48 | drawCursor();
49 | }
50 |
51 |
52 | private void drawCursorLine() {
53 | Rect rect = blockRects.get(currentBlockIndex);
54 | int startX = rect.centerX() - cursorWidth / 2;
55 | int endX = startX;
56 | int padding = Math.max((rect.height() - cursorHeight) / 2, 0);
57 |
58 | int startY = padding;
59 | int endY = startY + cursorHeight;
60 | canvas.drawLine(startX, startY, endX, endY, cursorPaint);
61 | }
62 |
63 | private void clearCursor() {
64 | clearCanvas(canvas);
65 | }
66 |
67 | public void drawCursor() {
68 | if (!showCursor) { // 不显示光标
69 | clearCursor();
70 | return;
71 | }
72 | if (!isFocused()) { // 失去焦点
73 | clearCursor();
74 | return;
75 | }
76 | if (currentBlockIndex >= blockRects.size()) { // 已经输入完成
77 | clearCursor();
78 | return;
79 | }
80 | clearCanvas(canvas);
81 | drawCursorLine();
82 | }
83 |
84 | public void cancelCursor() {
85 | clearCanvas(canvas);
86 | }
87 |
88 |
89 | public boolean isShowCursor() {
90 | return showCursor && isFocused() && currentBlockIndex < blockRects.size();
91 | }
92 |
93 | public void setShowCursor(boolean showCursor) {
94 | this.showCursor = showCursor;
95 | }
96 |
97 | public int getCursorDuration() {
98 | return cursorDuration;
99 | }
100 |
101 | public void setCursorDuration(int cursorDuration) {
102 | this.cursorDuration = cursorDuration;
103 | }
104 |
105 | public int getCursorWidth() {
106 | return cursorWidth;
107 | }
108 |
109 | public void setCursorWidth(int cursorWidth) {
110 | this.cursorWidth = cursorWidth;
111 | }
112 |
113 | public int getCursorColor() {
114 | return cursorColor;
115 | }
116 |
117 | public void setCursorColor(int cursorColor) {
118 | this.cursorColor = cursorColor;
119 | }
120 |
121 | @Override
122 | public void setCurrentBlockIndex(int currentBlockIndex) {
123 | super.setCurrentBlockIndex(currentBlockIndex);
124 | }
125 |
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/block/BaseBlockDrawer.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code.block;
2 |
3 | import android.graphics.Paint;
4 | import android.graphics.Rect;
5 | import android.graphics.RectF;
6 | import android.support.annotation.ColorInt;
7 |
8 | import com.lwjfork.code.base.BaseDrawer;
9 | import com.lwjfork.code.style.BlockShape;
10 |
11 |
12 | /**
13 | * Created by lwj on 2019/1/12.
14 | * lwjfork@gmail.com
15 | * 区域块绘制者
16 | */
17 | public abstract class BaseBlockDrawer extends BaseDrawer {
18 | @ColorInt
19 | protected int blockNormalColor; // 正常边框、填充、下划线颜色
20 | @ColorInt
21 | protected int blockFocusColor; // 获取焦点时边框、填充、下划线颜色
22 | @ColorInt
23 | protected int blockErrorColor; // 输入错误时边框、填充、下划线颜色
24 | @BlockShape
25 | protected int blockShape;
26 | protected int blockLineWidth; // 正常边框、下划线宽度
27 | protected boolean isErrorState;
28 | protected Paint blockPaint;
29 | protected int blockCorner; // 画边框及填充色的圆角
30 |
31 |
32 | public BaseBlockDrawer() {
33 | initPaint();
34 | }
35 |
36 | public BaseBlockDrawer(int blockNormalColor, int blockFocusColor, int blockErrorColor, @BlockShape int blockShape, int blockLineWidth, int blockCorner) {
37 | this.blockNormalColor = blockNormalColor;
38 | this.blockFocusColor = blockFocusColor;
39 | this.blockErrorColor = blockErrorColor;
40 | this.blockShape = blockShape;
41 | this.blockLineWidth = blockLineWidth;
42 | this.blockCorner = blockCorner;
43 | initPaint();
44 | }
45 |
46 | protected void initPaint() {
47 | blockPaint = new Paint();
48 | blockPaint.setAntiAlias(true);
49 | blockPaint.setColor(blockNormalColor);
50 | }
51 |
52 |
53 | @Override
54 | public final void drawCanvas() {
55 | if (canvas == null) {
56 | return;
57 | }
58 | clearCanvas(canvas);
59 | int size = blockRects.size();
60 | for (int i = 0; i < size; i++) {
61 | if (i == currentBlockIndex && isFocused()) {
62 | continue;
63 | } else {
64 | RectF rectF = fixPosition(i);
65 | if (isErrorState) {
66 | drawErrorBlock(rectF);
67 | } else {
68 | drawNormalBlock(rectF);
69 | }
70 | }
71 | }
72 | if (isFocused()) {
73 | drawFocusedBlock(fixPosition(currentBlockIndex));
74 | }
75 | }
76 |
77 | // 焦点获取绘制
78 | protected abstract void drawFocusedBlock(RectF rectF);
79 |
80 | // 常态绘制
81 | protected abstract void drawNormalBlock(RectF rectF);
82 |
83 | // 错误态绘制
84 | protected abstract void drawErrorBlock(RectF rectF);
85 |
86 | public int getBlockNormalColor() {
87 | return blockNormalColor;
88 | }
89 |
90 | @BlockShape
91 | public int getBlockShape() {
92 | return blockShape;
93 | }
94 |
95 | public void seBlockShape(@BlockShape int blockShape) {
96 | this.blockShape = blockShape;
97 | drawCanvas();
98 | }
99 |
100 | public int getBlockLineWidth() {
101 | return blockLineWidth;
102 | }
103 |
104 | public void setBlockLineWidth(int blockLineWidth) {
105 | this.blockLineWidth = blockLineWidth;
106 | if (blockShape == BlockShape.STROKE || blockShape == BlockShape.UNDERLINE) {
107 | blockPaint.setStrokeWidth(blockLineWidth);
108 | drawCanvas();
109 | }
110 | }
111 |
112 | public void setBlockNormalColor(int blockNormalColor) {
113 | this.blockNormalColor = blockNormalColor;
114 | drawCanvas();
115 | }
116 |
117 | public int getBlockFocusColor() {
118 | return blockFocusColor;
119 | }
120 |
121 | public void setBlockFocusColor(int blockFocusColor) {
122 | this.blockFocusColor = blockFocusColor;
123 | drawCanvas();
124 | }
125 |
126 | public int getBlockErrorColor() {
127 | return blockErrorColor;
128 | }
129 |
130 | public void setBlockErrorColor(int blockErrorColor) {
131 | this.blockErrorColor = blockErrorColor;
132 | drawCanvas();
133 | }
134 |
135 | public int getBlockCorner() {
136 | return blockCorner;
137 | }
138 |
139 | public void setBlockCorner(int blockCorner) {
140 | this.blockCorner = blockCorner;
141 | if (blockShape == BlockShape.STROKE || blockShape == BlockShape.SOLID) {
142 | drawCanvas();
143 | }
144 | }
145 |
146 | /**
147 | * fix 线的绘制宽度不一致问题
148 | *
149 | * @param i
150 | * @return
151 | */
152 | protected RectF fixPosition(int i) {
153 | if (i >= 0 && i < blockRects.size()) {
154 | Rect rect = blockRects.get(i);
155 | if (blockShape == BlockShape.UNDERLINE) {
156 | return new RectF(rect);
157 | }
158 | int left = rect.left;
159 | int top = rect.top;
160 | int right = rect.right;
161 | int bottom = rect.bottom;
162 | if (i == 0) { // 开头或者结尾
163 | left = left + blockLineWidth / 2;
164 | } else if (i == blockRects.size() - 1) {
165 | right = right - blockLineWidth / 2;
166 | }
167 | top = top + blockLineWidth / 2;
168 | bottom = bottom - blockLineWidth;
169 | return new RectF(left, top, right, bottom);
170 | }
171 | return null;
172 | }
173 |
174 | public boolean isErrorState() {
175 | return isErrorState;
176 | }
177 |
178 | public void setErrorState(boolean errorState) {
179 | isErrorState = errorState;
180 | drawCanvas();
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
16 |
42 |
43 |
44 |
67 |
68 |
93 |
94 |
118 |
119 |
120 |
145 |
146 |
147 |
171 |
172 |
195 |
196 |
197 |
220 |
221 |
222 |
223 |
224 |
--------------------------------------------------------------------------------
/library/src/main/java/com/lwjfork/code/CodeEditText.java:
--------------------------------------------------------------------------------
1 | package com.lwjfork.code;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Bitmap;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Rect;
10 | import android.support.annotation.ColorInt;
11 | import android.support.annotation.Px;
12 | import android.text.Editable;
13 | import android.text.InputFilter;
14 | import android.util.AttributeSet;
15 | import android.util.DisplayMetrics;
16 | import android.view.ActionMode;
17 | import android.view.Menu;
18 | import android.view.MenuItem;
19 | import android.view.MotionEvent;
20 | import android.widget.EditText;
21 | import android.widget.TextView;
22 |
23 | import com.lwjfork.code.block.BaseBlockDrawer;
24 | import com.lwjfork.code.block.NoneBlockDrawer;
25 | import com.lwjfork.code.block.SolidBlockDrawer;
26 | import com.lwjfork.code.block.StrokeBlockDrawer;
27 | import com.lwjfork.code.block.UnderlineBlockDrawer;
28 | import com.lwjfork.code.cusor.CursorDrawer;
29 | import com.lwjfork.code.style.BlockShape;
30 | import com.lwjfork.code.style.CodeInputType;
31 | import com.lwjfork.code.text.BaseTextDrawer;
32 | import com.lwjfork.code.text.NoneTextDrawer;
33 | import com.lwjfork.code.text.PasswordTextDrawer;
34 | import com.lwjfork.code.text.TextDrawer;
35 |
36 | import java.lang.reflect.Field;
37 | import java.util.ArrayList;
38 |
39 | /**
40 | * Created by lwj on 2019/1/11.
41 | * lwjfork@gmail.com
42 | * 验证码输入框、密码输入框
43 | */
44 | @SuppressWarnings("AppcompatCustomView")
45 | public class CodeEditText extends EditText {
46 | @ColorInt
47 | protected int blockNormalColor; // 正常边框、填充、下划线颜色
48 | @ColorInt
49 | protected int blockFocusColor; // 获取焦点时边框、填充、下划线颜色
50 | @ColorInt
51 | protected int blockErrorColor; // 输入错误时边框、填充、下划线颜色
52 | @BlockShape
53 | protected int blockShape;
54 | protected int blockLineWidth; // 正常边框、下划线宽度
55 | @CodeInputType
56 | protected int codeInputType; // 显示样式 支持明文和密码两种,密码时画圆点
57 | @ColorInt
58 | protected int codeTextColor;// 显示圆点、明文时的字体颜色
59 | protected int codeTextSize;// 明文时字体大小
60 | protected int dotRadius;// 密码样式时圆点的半径
61 | protected int blockCorner; // 画边框及填充色的圆角
62 | private int blockSpace; // 输入框间距
63 | private int maxCodeLength;// 输入码的最大长度
64 |
65 | private boolean showCursor;// 是否显示光标
66 | private int cursorDuration;// 光标闪烁间隔
67 | private int cursorWidth;// 光标宽度
68 | private int cursorHeight;// 光标高度
69 | @ColorInt
70 | private int cursorColor;// 光标颜色 没有设置时默认字体颜色
71 |
72 | private Bitmap blockBitmap;
73 | private Bitmap textBitmap;
74 | private Bitmap cursorBitmap;
75 | private ArrayList blockRects; // 每块所占区域
76 | private BaseBlockDrawer blockDrawer; // 方框绘制者
77 | private BaseTextDrawer textDrawer; // 文本绘制者
78 | private CursorDrawer cursorDrawer; // 光标绘制者
79 |
80 | protected Context mContext;
81 | private DisplayMetrics metrics;
82 |
83 |
84 | public CodeEditText(Context context) {
85 | this(context, null);
86 | }
87 |
88 | public CodeEditText(Context context, AttributeSet attrs) {
89 | this(context, attrs, 0);
90 | }
91 |
92 | public CodeEditText(Context context, AttributeSet attrs, int defStyleAttr) {
93 | super(context, attrs, defStyleAttr);
94 | init(context, attrs, defStyleAttr, 0);
95 | }
96 |
97 | @TargetApi(21)
98 | public CodeEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
99 | super(context, attrs, defStyleAttr, defStyleRes);
100 | init(context, attrs, defStyleAttr, defStyleRes);
101 | }
102 |
103 | private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
104 | this.mContext = context;
105 | metrics = mContext.getResources().getDisplayMetrics();
106 | parseAttrs(context, attrs, defStyleAttr, defStyleRes);
107 | forbidCopyAndPaste();
108 | initEditText();
109 | }
110 |
111 |
112 | // 解析属性
113 | private void parseAttrs(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
114 | final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CodeEditText, defStyleAttr, defStyleRes);
115 | maxCodeLength = typedArray.getInteger(R.styleable.CodeEditText_maxCodeLength, 6);
116 | codeTextColor = typedArray.getColor(R.styleable.CodeEditText_codeTextColor, Color.BLACK);
117 | codeTextSize = typedArray.getDimensionPixelSize(R.styleable.CodeEditText_codeTextSize, sp2px(12));
118 | dotRadius = typedArray.getDimensionPixelSize(R.styleable.CodeEditText_dotRadius, dp2px(5));
119 | codeInputType = typedArray.getInteger(R.styleable.CodeEditText_codeInputType, CodeInputType.NONE);
120 | textDrawer = createTextDrawer(codeInputType, codeTextColor, codeTextSize, dotRadius);
121 |
122 | blockNormalColor = typedArray.getColor(R.styleable.CodeEditText_blockNormalColor, codeTextColor);
123 | blockFocusColor = typedArray.getColor(R.styleable.CodeEditText_blockFocusColor, blockNormalColor);
124 | blockErrorColor = typedArray.getColor(R.styleable.CodeEditText_blockErrorColor, blockNormalColor);
125 | blockLineWidth = typedArray.getDimensionPixelSize(R.styleable.CodeEditText_blockLineWidth, dp2px(1));
126 | blockCorner = typedArray.getDimensionPixelSize(R.styleable.CodeEditText_blockCorner, 0);
127 | blockSpace = typedArray.getDimensionPixelSize(R.styleable.CodeEditText_blockSpace, 0);
128 | blockShape = typedArray.getInteger(R.styleable.CodeEditText_blockShape, BlockShape.NONE);
129 | blockDrawer = createBlockDrawer(blockNormalColor, blockFocusColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
130 |
131 | showCursor = typedArray.getBoolean(R.styleable.CodeEditText_showCursor, false);
132 | cursorDuration = typedArray.getInteger(R.styleable.CodeEditText_cursorDuration, 500);
133 | cursorWidth = typedArray.getDimensionPixelSize(R.styleable.CodeEditText_cursorWidth, dp2px(1));
134 | cursorHeight = typedArray.getDimensionPixelSize(R.styleable.CodeEditText_cursorHeight, (int) (codeTextSize * 1.25f));
135 | cursorColor = typedArray.getColor(R.styleable.CodeEditText_cursorColor, blockNormalColor);
136 | cursorDrawer = createCursorDrawer(showCursor, cursorDuration, cursorWidth, cursorHeight, cursorColor);
137 | typedArray.recycle();
138 | }
139 |
140 | /**
141 | * 光标绘制者
142 | *
143 | * @param showCursor
144 | * @param cursorDuration
145 | * @param cursorWidth
146 | * @param cursorColor
147 | * @return
148 | */
149 | private CursorDrawer createCursorDrawer(boolean showCursor, int cursorDuration, int cursorWidth, int cursorHeight, int cursorColor) {
150 | return new CursorDrawer(showCursor, cursorDuration, cursorWidth, cursorHeight, cursorColor);
151 | }
152 |
153 | /**
154 | * 方框绘制者
155 | *
156 | * @param blockNormalColor
157 | * @param blockFocusColor
158 | * @param blockErrorColor
159 | * @param blockShape
160 | * @param blockLineWidth
161 | * @param blockCorner
162 | * @return
163 | */
164 | private BaseBlockDrawer createBlockDrawer(int blockNormalColor, int blockFocusColor, int blockErrorColor, @BlockShape int blockShape, int blockLineWidth, int blockCorner) {
165 | switch (blockShape) {
166 | case BlockShape.SOLID:
167 | return new SolidBlockDrawer(blockNormalColor, blockFocusColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
168 | case BlockShape.STROKE:
169 | return new StrokeBlockDrawer(blockNormalColor, blockFocusColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
170 | case BlockShape.UNDERLINE:
171 | return new UnderlineBlockDrawer(blockNormalColor, blockFocusColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
172 | default:
173 | return new NoneBlockDrawer(blockNormalColor, blockFocusColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
174 | }
175 | }
176 |
177 | /**
178 | * 文本绘制者
179 | *
180 | * @param codeInputType
181 | * @param codeTextColor
182 | * @param codeTextSize
183 | * @param dotRadius
184 | * @return
185 | */
186 | private BaseTextDrawer createTextDrawer(@CodeInputType int codeInputType, int codeTextColor, int codeTextSize, int dotRadius) {
187 | switch (codeInputType) {
188 | case CodeInputType.TEXT:
189 | return new TextDrawer(codeInputType, codeTextColor, codeTextSize, dotRadius);
190 | case CodeInputType.PASSWORD:
191 | return new PasswordTextDrawer(codeInputType, codeTextColor, codeTextSize, dotRadius);
192 | default:
193 | return new NoneTextDrawer(codeInputType, codeTextColor, codeTextSize, dotRadius);
194 | }
195 | }
196 |
197 | private void initEditText() {
198 | setCursorVisible(false); // 不显示原来的光标
199 | setTextColor(Color.TRANSPARENT); // 本身的字体颜色透明
200 | setBackgroundDrawable(null);
201 | setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxCodeLength)}); // 设置最大长度
202 | setSingleLine();
203 | }
204 |
205 | @Override
206 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
207 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
208 | int measureWidth = getMeasuredWidth();
209 | int measureHeight = getMeasuredHeight();
210 | initRect(measureWidth, measureHeight);
211 | initBitmapAndCanvas(measureWidth, measureHeight);
212 | }
213 |
214 | /**
215 | * 计算每个输入框的位置,并初始化绘制者
216 | *
217 | * @param measureWidth
218 | * @param measureHeight
219 | */
220 | private void initRect(int measureWidth, int measureHeight) {
221 | if (blockRects == null) {
222 | blockRects = new ArrayList<>();
223 | }
224 | blockRects.clear();
225 | int startX = 0;
226 | int startY = 0;
227 | int endX = 0;
228 | int endY = 0;
229 | int blockWidth = (measureWidth - blockSpace * (maxCodeLength - 1)) / maxCodeLength;
230 | int blockHeight = measureHeight;
231 | for (int i = 0; i < maxCodeLength; i++) {
232 | endX = startX + blockWidth;
233 | endY = startY + blockHeight;
234 | Rect blockRect = new Rect(startX, startY, endX, endY);
235 | blockRects.add(blockRect);
236 | startX = endX + blockSpace;
237 | startY = 0;
238 | }
239 | blockDrawer.setBlockRects(blockRects);
240 | textDrawer.setBlockRects(blockRects);
241 | cursorDrawer.setBlockRects(blockRects);
242 | }
243 |
244 | /**
245 | * 创建Bitmap 和 Canvas
246 | *
247 | * @param measureWidth
248 | * @param measureHeight
249 | */
250 | private void initBitmapAndCanvas(int measureWidth, int measureHeight) {
251 | blockBitmap = blockDrawer.createBitmapAndCanvas(measureWidth, measureHeight);
252 | textBitmap = textDrawer.createBitmapAndCanvas(measureWidth, measureHeight);
253 | cursorBitmap = cursorDrawer.createBitmapAndCanvas(measureWidth, measureHeight);
254 | }
255 |
256 |
257 | @Override
258 | protected void onDraw(Canvas canvas) {
259 | // super.onDraw(canvas);
260 | canvas.drawBitmap(blockBitmap, 0, 0, null);
261 | canvas.drawBitmap(textBitmap, 0, 0, null);
262 | canvas.drawBitmap(cursorBitmap, 0, 0, null);
263 | blockDrawer.drawCanvas();
264 | textDrawer.drawCanvas();
265 | cursorDrawer.drawCanvas();
266 | if (isInEditMode()) {
267 | textDrawer.setContent("111");
268 | }
269 | }
270 |
271 |
272 | private void forbidCopyAndPaste() {
273 | setLongClickable(false);
274 | setTextIsSelectable(false);
275 | setCustomSelectionActionModeCallback(new ActionMode.Callback() {
276 | @Override
277 | public boolean onCreateActionMode(ActionMode mode, Menu menu) {
278 | return false;
279 | }
280 |
281 | @Override
282 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
283 | return false;
284 | }
285 |
286 | @Override
287 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
288 | return false;
289 | }
290 |
291 | @Override
292 | public void onDestroyActionMode(ActionMode mode) {
293 |
294 | }
295 | });
296 | }
297 |
298 | @Override
299 | public boolean onTouchEvent(MotionEvent event) {
300 | if (event.getAction() == MotionEvent.ACTION_DOWN) {
301 | // setInsertionDisabled when user touches the view
302 | setInsertionDisabled();
303 | }
304 | return super.onTouchEvent(event);
305 | }
306 |
307 | @Override
308 | public boolean onTextContextMenuItem(int id) {
309 | if (id == android.R.id.paste) return false;
310 | return super.onTextContextMenuItem(id);
311 | }
312 |
313 | private void setInsertionDisabled() {
314 | try {
315 | Field editorField = TextView.class.getDeclaredField("mEditor");
316 | editorField.setAccessible(true);
317 | Object editorObject = editorField.get(this);
318 |
319 | Class editorClass = Class.forName("android.widget.Editor");
320 | Field mInsertionControllerEnabledField = editorClass.getDeclaredField("mInsertionControllerEnabled");
321 | mInsertionControllerEnabledField.setAccessible(true);
322 | mInsertionControllerEnabledField.set(editorObject, false);
323 |
324 | Field mSelectionControllerEnabledField = editorClass.getDeclaredField("mSelectionControllerEnabled");
325 | mSelectionControllerEnabledField.setAccessible(true);
326 | mSelectionControllerEnabledField.set(editorObject, false);
327 | } catch (Exception ignored) {
328 | // ignore exception here
329 | }
330 | }
331 |
332 | @Override
333 | public boolean isSuggestionsEnabled() {
334 | return false;
335 | }
336 |
337 |
338 | /***
339 | * 删除单个字符
340 | */
341 | public void delete() {
342 | Editable editable = getText();
343 | if (editable.length() > 0) {
344 | editable.delete(editable.length() - 1, editable.length());
345 | }
346 | }
347 |
348 | /***
349 | * 添加一个字符
350 | * @param c
351 | */
352 | public void addChar(char c) {
353 | Editable editable = getText();
354 | editable.append(c);
355 | }
356 |
357 | /***
358 | * 添加多个字符
359 | * @param sequence
360 | */
361 | public void addCharSequence(CharSequence sequence) {
362 | Editable editable = getText();
363 | editable.append(sequence);
364 | }
365 |
366 |
367 | OnTextChangedListener onTextChangedListener;
368 |
369 | public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) {
370 | this.onTextChangedListener = onTextChangedListener;
371 | }
372 |
373 | /**
374 | * 密码监听者
375 | */
376 | public interface OnTextChangedListener {
377 | /**
378 | * 输入/删除监听
379 | *
380 | * @param changeText 输入/删除的字符
381 | */
382 | void onCodeChanged(CharSequence changeText);
383 |
384 | /**
385 | * input complete
386 | */
387 | void onInputCompleted(CharSequence text);
388 | }
389 |
390 | /**
391 | * update UI
392 | *
393 | * @param text
394 | * @param start
395 | * @param lengthBefore
396 | * @param lengthAfter
397 | */
398 | @Override
399 | protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
400 | if (isInEditMode()) {
401 | return;
402 | }
403 | if (lengthBefore == lengthAfter && lengthBefore == 0) {
404 | return;
405 | }
406 | int currentBlockIndex = text.toString().length();
407 | if (onTextChangedListener != null) {
408 | onTextChangedListener.onCodeChanged(text.toString());
409 | if (text.length() == maxCodeLength) {
410 | onTextChangedListener.onInputCompleted(text.toString());
411 | }
412 | }
413 | blockDrawer.setCurrentBlockIndex(currentBlockIndex);
414 | textDrawer.setCurrentBlockIndex(currentBlockIndex);
415 | cursorDrawer.setCurrentBlockIndex(currentBlockIndex);
416 | textDrawer.setContent(getText().toString());
417 | }
418 |
419 | @Override
420 | protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
421 | super.onFocusChanged(focused, direction, previouslyFocusedRect);
422 | blockDrawer.setFocused(focused);
423 | textDrawer.setFocused(focused);
424 | cursorDrawer.setFocused(focused);
425 | }
426 |
427 |
428 | @Override
429 | protected void onDetachedFromWindow() {
430 | super.onDetachedFromWindow();
431 | recycleBitmap(blockBitmap);
432 | recycleBitmap(textBitmap);
433 | recycleBitmap(cursorBitmap);
434 | }
435 |
436 | /**
437 | * bitmap recycle
438 | *
439 | * @param bitmap
440 | */
441 | private void recycleBitmap(Bitmap bitmap) {
442 | if (bitmap != null && !bitmap.isRecycled()) {
443 | bitmap.recycle();
444 | }
445 | }
446 |
447 | /**
448 | * clear Text
449 | */
450 | public void clearContent() {
451 | getEditableText().clear();
452 | }
453 |
454 | boolean errorState;
455 |
456 | public boolean isErrorState() {
457 | return errorState;
458 | }
459 |
460 | public void setErrorState(boolean errorState) {
461 | this.errorState = errorState;
462 | blockDrawer.setErrorState(errorState);
463 | }
464 |
465 |
466 | public int getBlockNormalColor() {
467 | return blockNormalColor;
468 | }
469 |
470 | public void setBlockNormalColor(@ColorInt int blockNormalColor) {
471 | this.blockNormalColor = blockNormalColor;
472 | if (blockDrawer != null) {
473 | blockDrawer.setBlockNormalColor(blockNormalColor);
474 | }
475 | }
476 |
477 | public int getBlockFocusColor() {
478 | return blockFocusColor;
479 | }
480 |
481 | public void setBlockFocusColor(@ColorInt int blockFocusColor) {
482 | this.blockFocusColor = blockFocusColor;
483 | if (blockDrawer != null) {
484 | blockDrawer.setBlockFocusColor(blockFocusColor);
485 | }
486 | }
487 |
488 | public int getBlockErrorColor() {
489 | return blockErrorColor;
490 | }
491 |
492 | public void setBlockErrorColor(@ColorInt int blockErrorColor) {
493 | this.blockErrorColor = blockErrorColor;
494 | if (blockDrawer != null) {
495 | blockDrawer.setBlockErrorColor(blockErrorColor);
496 | }
497 | }
498 |
499 | public int getBlockLineWidth() {
500 | return blockLineWidth;
501 | }
502 |
503 | public void setBlockLineWidth(int blockLineWidth) {
504 | this.blockLineWidth = blockLineWidth;
505 | if (blockDrawer != null) {
506 | blockDrawer.setBlockLineWidth(blockLineWidth);
507 | }
508 | }
509 |
510 | public int getCorner() {
511 | return blockCorner;
512 | }
513 |
514 | public void setBlockCorner(int blockCorner) {
515 | this.blockCorner = blockCorner;
516 | if (blockDrawer != null) {
517 | blockDrawer.setBlockCorner(blockCorner);
518 | }
519 | }
520 |
521 | public int getBlockSpace() {
522 | return blockSpace;
523 | }
524 |
525 | public void setBlockSpace(int blockSpace) {
526 | this.blockSpace = blockSpace;
527 | requestLayout();
528 | }
529 |
530 | public int getCodeTextColor() {
531 | return codeTextColor;
532 | }
533 |
534 | public void setCodeTextColor(@ColorInt int codeTextColor) {
535 | this.codeTextColor = codeTextColor;
536 | if (textDrawer != null) {
537 | textDrawer.setCodeTextColor(codeTextColor);
538 | }
539 | }
540 |
541 | public int getCodeTextSize() {
542 | return codeTextSize;
543 | }
544 |
545 | public void setCodeTextSize(@Px int codeTextSize) {
546 | this.codeTextSize = codeTextSize;
547 | if (textDrawer != null) {
548 | textDrawer.setCodeTextSize(codeTextSize);
549 | }
550 | }
551 |
552 | public int getDotRadius() {
553 | return dotRadius;
554 | }
555 |
556 | public void setDotRadius(@Px int dotRadius) {
557 | this.dotRadius = dotRadius;
558 | if (textDrawer != null) {
559 | textDrawer.setDotRadius(dotRadius);
560 | }
561 | }
562 |
563 | public int getMaxCodeLength() {
564 | return maxCodeLength;
565 | }
566 |
567 | public void setMaxCodeLength(int maxCodeLength) {
568 | this.maxCodeLength = maxCodeLength;
569 | requestLayout();
570 | }
571 |
572 | public boolean isShowCursor() {
573 | return cursorDrawer.isShowCursor();
574 | }
575 |
576 | public void setShowCursor(boolean showCursor) {
577 | cursorDrawer.setShowCursor(showCursor);
578 | }
579 |
580 | public int getCursorDuration() {
581 | return 1000;
582 | }
583 |
584 | public void setCursorDuration(int cursorDuration) {
585 | cursorDrawer.setCursorDuration(cursorDuration);
586 | }
587 |
588 | public int getCursorWidth() {
589 | return cursorDrawer.getCursorWidth();
590 | }
591 |
592 | public void setCursorWidth(@Px int cursorWidth) {
593 | cursorDrawer.setCursorWidth(cursorWidth);
594 | }
595 |
596 | public int getCursorColor() {
597 | return cursorDrawer.getCursorColor();
598 | }
599 |
600 | public void setCursorColor(@ColorInt int cursorColor) {
601 | cursorDrawer.setCursorColor(cursorColor);
602 | }
603 |
604 | /**
605 | * @see BlockShape
606 | */
607 | @BlockShape
608 | public int getBlockShape() {
609 | return blockDrawer.getBlockShape();
610 | }
611 |
612 |
613 | // 可以自定义BlockDrawer
614 | public void setBlockShape(BaseBlockDrawer newBlockDrawer) {
615 | if (newBlockDrawer == null) {
616 | return;
617 | }
618 | if (blockDrawer != null) {
619 | newBlockDrawer.setFocused(blockDrawer.isFocused());
620 | newBlockDrawer.setBlockRects(blockDrawer.getBlockRects());
621 | newBlockDrawer.setErrorState(blockDrawer.isErrorState());
622 | newBlockDrawer.setCurrentBlockIndex(blockDrawer.getCurrentBlockIndex());
623 |
624 | newBlockDrawer.setBlockCorner(blockDrawer.getBlockCorner());
625 | newBlockDrawer.setBlockErrorColor(blockDrawer.getBlockErrorColor());
626 | newBlockDrawer.setBlockFocusColor(blockDrawer.getBlockFocusColor());
627 | newBlockDrawer.setBlockNormalColor(blockDrawer.getBlockNormalColor());
628 | newBlockDrawer.setBlockLineWidth(blockDrawer.getBlockLineWidth());
629 |
630 | newBlockDrawer.setCanvas(blockDrawer.getCanvas());
631 | blockDrawer = newBlockDrawer;
632 | invalidate();
633 | } else {
634 | blockDrawer = newBlockDrawer;
635 | blockDrawer.setErrorState(errorState);
636 | blockDrawer.setBlockRects(blockRects);
637 | requestLayout();
638 | }
639 | }
640 |
641 | /**
642 | * @param blockShape
643 | * @see BlockShape
644 | */
645 | public void setBlockShape(@BlockShape int blockShape) {
646 | if (blockShape == blockDrawer.getBlockShape()) {
647 | return;
648 | }
649 | BaseBlockDrawer newBlockDrawer = createBlockDrawer(blockNormalColor, blockFocusColor, blockErrorColor, blockShape, blockLineWidth, blockCorner);
650 | setBlockShape(newBlockDrawer);
651 | }
652 |
653 | /**
654 | * @see CodeInputType
655 | */
656 | @CodeInputType
657 | public int getCodeInputType() {
658 | return textDrawer.getCodeInputType();
659 | }
660 |
661 | /**
662 | * @see CodeInputType
663 | */
664 | public void setCodeInputType(@CodeInputType int codeInputType) {
665 | if (codeInputType == textDrawer.getCodeInputType()) {
666 | return;
667 | }
668 |
669 | BaseTextDrawer newTextDrawer = createTextDrawer(codeInputType, codeTextColor, codeTextSize, dotRadius);
670 | setCodeInputType(newTextDrawer);
671 | }
672 |
673 | public void setCodeInputType(BaseTextDrawer newTextDrawer) {
674 | if (newTextDrawer == null) {
675 | return;
676 | }
677 | if (textDrawer != null) {
678 | newTextDrawer.setFocused(textDrawer.isFocused());
679 | newTextDrawer.setBlockRects(textDrawer.getBlockRects());
680 | newTextDrawer.setCurrentBlockIndex(textDrawer.getCurrentBlockIndex());
681 | newTextDrawer.setContent(textDrawer.getContent());
682 |
683 | newTextDrawer.setCodeTextSize(textDrawer.getCodeTextSize());
684 | newTextDrawer.setCodeTextColor(textDrawer.getCodeTextColor());
685 | newTextDrawer.setDotRadius(textDrawer.getDotRadius());
686 |
687 |
688 | newTextDrawer.setCanvas(textDrawer.getCanvas());
689 | textDrawer = newTextDrawer;
690 | invalidate();
691 | } else {
692 | textDrawer = newTextDrawer;
693 | textDrawer.setBlockRects(blockRects);
694 | requestLayout();
695 | }
696 | }
697 |
698 |
699 | public int dp2px(float dp) {
700 | float density = metrics.density;
701 | return (int) (dp * density + 0.5f);
702 | }
703 |
704 | public int sp2px(float sp) {
705 | return (int) (sp * metrics.scaledDensity + 0.5f);
706 | }
707 | }
708 |
--------------------------------------------------------------------------------