├── .gitignore
├── .idea
├── caches
│ └── build_file_checksums.ser
├── codeStyles
│ └── Project.xml
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── markdown-doclet.xml
├── markdown-navigator
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── CN-README.md
├── GIF.gif
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── rokudol
│ │ └── com
│ │ └── pswedittext
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── rokudol
│ │ │ └── com
│ │ │ └── pswedittext
│ │ │ └── MainActivity.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
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── rokudol
│ └── com
│ └── pswedittext
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pswtext
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── rokudol
│ │ └── com
│ │ └── pswtext
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── rokudol
│ │ │ └── com
│ │ │ └── pswtext
│ │ │ ├── InputStatusListener.kt
│ │ │ ├── NumInputConnection.kt
│ │ │ ├── NumKeyListener.kt
│ │ │ ├── PswAttrsBean.kt
│ │ │ ├── PswConstants.kt
│ │ │ ├── PswText.java
│ │ │ ├── PwdText.kt
│ │ │ └── TextWatcher.kt
│ └── res
│ │ ├── drawable
│ │ ├── pic_dlzc_srk.png
│ │ └── pic_dlzc_srk1.png
│ │ └── values
│ │ ├── attrs.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── rokudol
│ └── com
│ └── pswtext
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | xmlns:android
11 |
12 | ^$
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | xmlns:.*
22 |
23 | ^$
24 |
25 |
26 | BY_NAME
27 |
28 |
29 |
30 |
31 |
32 |
33 | .*:id
34 |
35 | http://schemas.android.com/apk/res/android
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | .*:name
45 |
46 | http://schemas.android.com/apk/res/android
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | name
56 |
57 | ^$
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | style
67 |
68 | ^$
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | .*
78 |
79 | ^$
80 |
81 |
82 | BY_NAME
83 |
84 |
85 |
86 |
87 |
88 |
89 | .*
90 |
91 | http://schemas.android.com/apk/res/android
92 |
93 |
94 | ANDROID_ATTRIBUTE_ORDER
95 |
96 |
97 |
98 |
99 |
100 |
101 | .*
102 |
103 | .*
104 |
105 |
106 | BY_NAME
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/markdown-doclet.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/misc.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 | 1.8
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CN-README.md:
--------------------------------------------------------------------------------
1 | # PswText
2 | 简介:
3 | ====
4 | 
5 |
6 | 博客地址:[强大的密码输入框][1]
7 |
8 |
9 | 使用方法:
10 |
11 |
12 | Step 1. Add the JitPack repository to your build file
13 |
14 | gradle
15 | maven
16 | sbt
17 | leiningen
18 | Add it in your root build.gradle at the end of repositories:
19 |
20 | allprojects {
21 | repositories {
22 | ...
23 | maven { url 'https://jitpack.io' }
24 | }
25 | }Copy
26 |
27 | Step 2. Add the dependency
28 |
29 |
30 | dependencies {
31 | compile 'com.github.rokudol:PswText:v1.0.4'
32 | }
33 |
34 |
35 | 属性:
36 |
37 |
38 |
39 | | 属性名 | 值 | 作用 |
40 | | ------------------ | -------: | :----------------------------------------------------------: |
41 | | pswLength | integer | 规定密码长度,默认为6 |
42 | | delayTime | integer | 延迟绘制密码圆点的时间 默认1000,1000=1s |
43 | | borderColor | color | 初始化密码框颜色 |
44 | | pswColor | color | 密码颜色 |
45 | | inputBorder_color | color | 输入时密码框颜色 |
46 | | borderShadow_color | color | 输入时密码框阴影颜色 |
47 | | psw_textSize | sp | 明文密码大小 |
48 | | borderRadius | dp | 不使用图片时,密码框圆角大小 |
49 | | borderImg | drawable | 密码框图片 |
50 | | inputBorderImg | drawable | 输入时变化的密码框图片 |
51 | | isDrawBorderImg | boolean | 是否使用图片绘制密码框,为true时设置borderImg、inputBorderImg才有效,默认为false |
52 | | isShowTextPsw | boolean | 按下back键时是否需要绘制当前位置的明文密码,默认为false |
53 | | isShowBorderShadow | boolean | 输入密码时是否需要绘制阴影,为true时设置borderShadow_color才有效,默认为false |
54 | | clearTextPsw | boolean | 是否只绘制明文密码,默认为false |
55 | | darkPsw | boolean | 是否只绘制圆点,默认为false |
56 | | isChangeBorder | boolean | 是否在输入密码时不更改密码框颜色,默认为false |
57 |
58 | setTextWatcher可触发输入监听,textChanged可获取用户当前输入的密码及是否已输入完成的状态,true-输入完成,false-未输入完成
59 |
60 | 开发者可使用pwdText.getAttrBean().setXXX()方法设置控件属性
61 |
62 |
63 | 更新说明:
64 | ======
65 | ## v1.0.1:
66 |
67 | _修复bug:_
68 |
69 | 1. 重新计算高度,修复密码框上下两根线绘制不完全的问题
70 |
71 | _新增功能:_
72 |
73 | 1. 可选择在输入密码时不更改密码框颜色,xml属性:isChangeBorder,为true:输入时不更改密码框颜色,为false:输入时更改密码框颜色
74 |
75 | ## v1.0.2
76 |
77 | _修复bug:_
78 |
79 | 1. 确定高度时,宽度自适应导致宽度绘制不正确的问题
80 |
81 | ## v1.0.3
82 |
83 | _修复bug:_
84 |
85 | 1. 修复明文密码和密文密码绘制没有居中的问题
86 | 2. 新增各个属性的getter setter方法,可在java代码中直接用setter去设置各个属性
87 |
88 | ## v1.0.4
89 |
90 | _新增功能:_
91 | 1. 废弃InputCallBack,添加TextWatcher,textChanged回调将返回用户的当前密码以及是否已输入完成
92 |
93 | [1]: http://blog.rokudol.cn/%E8%87%AA%E5%AE%9A%E4%B9%89view---%E5%BC%BA%E5%A4%A7%E7%9A%84%E5%AF%86%E7%A0%81%E8%BE%93%E5%85%A5%E6%A1%86.html#more
94 |
95 | ## v2.0.0
96 |
97 | _重构:_
98 | 1. 使用kotlin重构PswText,废弃PswText,使用PwdText替代
99 |
--------------------------------------------------------------------------------
/GIF.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/GIF.gif
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PswText
2 | Introduction:
3 | ====
4 | 
5 |
6 | Blog Address:[Powerful password input box][1]
7 |
8 |
9 | [中文文档][2]
10 |
11 | How to use:
12 |
13 |
14 | Step 1. Add the JitPack repository to your build file
15 |
16 | gradle
17 | maven
18 | sbt
19 | leiningen
20 | Add it in your root build.gradle at the end of repositories:
21 |
22 | allprojects {
23 | repositories {
24 | ...
25 | maven { url 'https://jitpack.io' }
26 | }
27 | }Copy
28 |
29 | Step 2. Add the dependency
30 |
31 |
32 | dependencies {
33 | compile 'com.github.rokudol:PswText:v1.0.4'
34 | }
35 |
36 | attrs:
37 |
38 |
39 |
40 | | Attribute name | value | effect |
41 | | -------- | -----: | :----: |
42 | | pswLength | integer | the length of the password, the default is 6 |
43 | | delayTime | integer | delay the time to draw the password dot default 1000,1000=1s|
44 | | borderColor | color | initialize the password box color |
45 | | pswColor | color | password color |
46 | | inputBorder_color | color | when you enter the password box color |
47 | | borderShadow_color | color | when you enter the password box, the shadow color |
48 | | psw_textSize | sp | clear password textSize |
49 | | borderRadius | dp | when the picture is not used, the password box is rounded|
50 | | borderImg | drawable | password box picture |
51 | | inputBorderImg | drawable | enter the password box when changing the picture |
52 | | isDrawBorderImg | boolean | whether to use the picture to draw the password box, set the borderImg true, inputBorderImg only effective, the default is false |
53 | | isShowTextPsw | boolean | when you press the back key, you need to draw the plain text password for the current location. The default is false |
54 | | isShowBorderShadow | boolean | whether you need to draw a shadow when you enter a password, set borderShadow_color to true, the default is false |
55 | | clearTextPsw | boolean | whether to draw only plain text password, the default is false |
56 | | darkPsw | boolean | whether to draw only dots, the default is false |
57 | | isChangeBorder | boolean | whether to change the password box color when entering the password, the default is false |
58 |
59 | "setTextWatcher" can trigger input listener, textChanged can get the user's current input password and whether the status has been entered, true - input is completed, false - not entered completed
60 |
61 | Developers can use pwdText.getAttrBean().setXXX() to set properties
62 |
63 | Release Notes:
64 | ======
65 | ## v1.0.1:
66 |
67 | _fix bug:_
68 |
69 | 1. Recalculate height,Repair the password box up and down two lines to draw the incomplete problem
70 |
71 | _added function:_
72 |
73 | 1. You can choose not to change the password box color when entering a password
74 |
75 |
76 | The corresponding attribute:isChangeBorder.
77 |
78 | When isChangeBorder is true:do not change the password box color when typing
79 |
80 | When isChangeBorder is false:change the password box color when typing
81 |
82 | ## v1.0.2:
83 |
84 | _fix bug:_
85 |
86 | 1. when the height measure mode is EXACTLY and width measure mode is AT_MOST, width draw not correct
87 |
88 |
89 | ## v1.0.3:
90 |
91 | _fix bug:_
92 |
93 | 1. clear text passwrod and ciphr text password no drawn in the middle position
94 | 2. Add getter setter methods for each property
95 |
96 | ## v1.0.4:
97 |
98 | _added function:_
99 | 1. Discard InputCallBack, add TextWatcher, textChanged callback will return the user's current password and whether it has been entered completed
100 |
101 | [1]: http://blog.rokudol.cn/%E8%87%AA%E5%AE%9A%E4%B9%89view---%E5%BC%BA%E5%A4%A7%E7%9A%84%E5%AF%86%E7%A0%81%E8%BE%93%E5%85%A5%E6%A1%86.html#more
102 | [2]: https://github.com/rokudol/PswText/blob/master/CN-README.md
103 |
104 | ## v2.0.0:
105 |
106 | _restructure:_
107 | 1. use kotlin restructure PswText. PswText has been discarded and can be used with PwdText
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 28
7 | buildToolsVersion "28.0.3"
8 | defaultConfig {
9 | applicationId "rokudol.com.pswedittext"
10 | minSdkVersion 15
11 | targetSdkVersion 28
12 | versionCode 1
13 | versionName "1.0"
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | implementation fileTree(include: ['*.jar'], dir: 'libs')
26 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
27 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
28 | exclude group: 'com.android.support', module: 'support-annotations'
29 | })
30 | implementation 'com.android.support:appcompat-v7:28.0.0'
31 | testImplementation 'junit:junit:4.12'
32 | implementation project(':pswtext')
33 | }
34 |
--------------------------------------------------------------------------------
/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 D:\Users\Administrator\AppData\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/rokudol/com/pswedittext/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswedittext;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("rokudol.com.pswedittext", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/java/rokudol/com/pswedittext/MainActivity.java:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswedittext;
2 |
3 | import android.graphics.Color;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.widget.Toast;
7 |
8 | import rokudol.com.pswtext.PwdText;
9 | import rokudol.com.pswtext.TextWatcher;
10 |
11 | public class MainActivity extends AppCompatActivity {
12 | private PwdText pswText;
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_main);
18 | pswText = findViewById(R.id.psw);
19 | pswText.getAttrBean().setPswColor(Color.parseColor("#000000"));
20 | pswText.getAttrBean().setPswLength(6);
21 | pswText.setTextWatcher(new TextWatcher() {
22 | @Override
23 | public void textChanged(String password, boolean isFinishInput) {
24 | Toast.makeText(MainActivity.this, String.format("输入的密码:%s,是否输入完成:%s", password, isFinishInput), Toast.LENGTH_SHORT).show();
25 | }
26 | });
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
21 |
22 |
27 |
28 |
33 |
34 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PswEditText
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/rokudol/com/pswedittext/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswedittext;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.50'
5 | repositories {
6 | jcenter()
7 | google()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.1'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | classpath 'com.novoda:bintray-release:0.9.1'
13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | jcenter()
23 | google()
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Oct 11 14:04:21 CST 2019
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-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/pswtext/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/pswtext/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.novoda.bintray-release'
3 | apply plugin: 'com.github.dcendents.android-maven'
4 | apply plugin: 'kotlin-android'
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | group='com.github.rokudol'
8 |
9 | android {
10 | compileSdkVersion 28
11 | buildToolsVersion '28.0.3'
12 |
13 | defaultConfig {
14 | minSdkVersion 15
15 | targetSdkVersion 28
16 | versionCode 1
17 | versionName "1.0"
18 |
19 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
20 |
21 | }
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | }
29 |
30 | dependencies {
31 | implementation fileTree(dir: 'libs', include: ['*.jar'])
32 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
33 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
34 | exclude group: 'com.android.support', module: 'support-annotations'
35 | })
36 | implementation 'com.android.support:appcompat-v7:28.0.0'
37 | testImplementation 'junit:junit:4.12'
38 | }
39 |
40 | //添加
41 | publish {
42 | userOrg = 'rokudoll'
43 | groupId = 'com.rokudoll'
44 | artifactId = 'PswText'
45 | publishVersion = '2.0.0'
46 | desc = 'a powerful passwordEditText'
47 | website = 'https://github.com/rokudol/PswText.git'
48 | }
49 |
--------------------------------------------------------------------------------
/pswtext/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 D:\Users\Administrator\AppData\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 |
--------------------------------------------------------------------------------
/pswtext/src/androidTest/java/rokudol/com/pswtext/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("rokudol.com.pswtext.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/pswtext/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/pswtext/src/main/java/rokudol/com/pswtext/InputStatusListener.kt:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext
2 |
3 | interface InputStatusListener {
4 | fun add(num: Int)
5 |
6 | fun remove()
7 |
8 | fun finishInput()
9 | }
--------------------------------------------------------------------------------
/pswtext/src/main/java/rokudol/com/pswtext/NumInputConnection.kt:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext
2 |
3 | import android.view.KeyEvent
4 | import android.view.View
5 | import android.view.inputmethod.BaseInputConnection
6 |
7 |
8 | class NumInputConnection(val targetView: View, fullEditor: Boolean) : BaseInputConnection(targetView, fullEditor) {
9 | override fun commitText(text: CharSequence, newCursorPosition: Int): Boolean {
10 | //这里是接收文本的输入法,我们只允许输入数字,则不做任何处理
11 | return super.commitText(text, newCursorPosition)
12 | }
13 |
14 | override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
15 | //屏蔽返回键,发送自己的删除事件
16 | return if (beforeLength == 1 && afterLength == 0) {
17 | super.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) &&
18 | super.sendKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL))
19 | } else super.deleteSurroundingText(beforeLength, afterLength)
20 | }
21 | }
--------------------------------------------------------------------------------
/pswtext/src/main/java/rokudol/com/pswtext/NumKeyListener.kt:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext
2 |
3 | import android.view.KeyEvent
4 | import android.view.View
5 |
6 | class NumKeyListener(private val inputStatusListener: InputStatusListener) : View.OnKeyListener {
7 | override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
8 | when (event.action) {
9 | KeyEvent.ACTION_DOWN -> {
10 | //禁止输入特殊字符
11 | if (event.isShiftPressed) {
12 | return false
13 | }
14 | when (keyCode) {
15 | //只允许输入0-9的数字
16 | in KeyEvent.KEYCODE_0..KeyEvent.KEYCODE_9 -> {
17 | inputStatusListener.add(keyCode - 7)
18 | inputStatusListener.finishInput()
19 | return true
20 | }
21 | KeyEvent.KEYCODE_DEL -> {
22 | inputStatusListener.remove()
23 | }
24 | KeyEvent.KEYCODE_ENTER -> {
25 | inputStatusListener.finishInput()
26 | }
27 | }
28 | }
29 | }
30 | return false
31 | }
32 | }
--------------------------------------------------------------------------------
/pswtext/src/main/java/rokudol/com/pswtext/PswAttrsBean.kt:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext
2 |
3 | import android.util.TypedValue
4 |
5 | /**
6 | * @param view 密码输入框
7 | * @parm pswLength 密码长度
8 | * @param borderColor 密码框颜色
9 | * @param borderShadowColor 密码框阴影颜色
10 | * @param pswColor 明文密码颜色
11 | * @param borderImg 边框图片
12 | * @param inputBorderColor 输入时密码边框颜色
13 | * @param inputBorderImg 输入时边框图片
14 | * @param delayTime 延迟绘制圆点时间,1000 = 1s
15 | * @param isBorderImg 是否使用图片绘制边框
16 | * @param isShowTextPsw 是否在按返回键时绘制明文密码
17 | * @param isShowBorderShadow 是否绘制在输入时,密码框的阴影颜色
18 | * @param clearTextPsw 是否只绘制明文密码
19 | * @param darkPsw 是否只绘制圆点密码
20 | * @param changeBorder 是否在输入密码时不更改密码框颜色
21 | * @param pswTextSize 明文密码字体大小
22 | * @param borderRadius 边框圆角程度
23 | */
24 | data class PswAttrsBean(private val view: PwdText,
25 | private var pswLength: Int = PswConstants.PSW_LENGTH,
26 | private var borderColor: Int = PswConstants.BORDER_COLOR,
27 | private var borderShadowColor: Int = PswConstants.BORDER_SHADOW_COLOR,
28 | private var pswColor: Int = PswConstants.PSW_COLOR,
29 | private var inputBorderColor: Int = PswConstants.INPUT_BORDER_COLOR,
30 | private var inputBorderImg: Int = PswConstants.INPUT_BORDER_IMG,
31 | private var borderImg: Int = PswConstants.BORDER_IMG,
32 | private var delayTime: Int = PswConstants.DELAY_TIME,
33 | private var isBorderImg: Boolean = PswConstants.IS_BORDER_IMG,
34 | private var isShowTextPsw: Boolean = PswConstants.IS_SHOW_TEXT_PSW,
35 | private var isShowBorderShadow: Boolean = PswConstants.IS_SHOW_BORDER_SHADOW,
36 | private var clearTextPsw: Boolean = PswConstants.CLEAR_TEXT_PSW,
37 | private var darkPsw: Boolean = PswConstants.DARK_PSW,
38 | private var changeBorder: Boolean = PswConstants.IS_CHANGE_BORDER,
39 | private var pswTextSize: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 18f, view.resources.displayMetrics).toInt(),
40 | private var borderRadius: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, view.resources.displayMetrics).toInt()) {
41 |
42 | fun isClearTextPsw(): Boolean {
43 | return clearTextPsw
44 | }
45 |
46 | fun setClearTextPsw(clearTextPsw: Boolean) {
47 | this.clearTextPsw = clearTextPsw
48 | view.invalidate()
49 | }
50 |
51 | fun isDarkPsw(): Boolean {
52 | return darkPsw
53 | }
54 |
55 | fun setDarkPsw(darkPsw: Boolean) {
56 | this.darkPsw = darkPsw
57 | view.invalidate()
58 | }
59 |
60 | fun getPswLength(): Int {
61 | return pswLength
62 | }
63 |
64 | fun setPswLength(pswLength: Int) {
65 | this.pswLength = pswLength
66 | view.invalidate()
67 | }
68 |
69 | fun getBorderColor(): Int {
70 | return borderColor
71 | }
72 |
73 | fun setBorderColor(borderColor: Int) {
74 | this.borderColor = borderColor
75 | view.invalidate()
76 | }
77 |
78 | fun getBorderShadowColor(): Int {
79 | return borderShadowColor
80 | }
81 |
82 | fun setBorderShadowColor(borderShadowColor: Int) {
83 | this.borderShadowColor = borderShadowColor
84 | view.invalidate()
85 | }
86 |
87 | fun getPswColor(): Int {
88 | return pswColor
89 | }
90 |
91 | fun setPswColor(pswColor: Int) {
92 | this.pswColor = pswColor
93 | view.invalidate()
94 | }
95 |
96 | fun getPswTextSize(): Int {
97 | return pswTextSize
98 | }
99 |
100 | fun setPswTextSize(pswTextSize: Int) {
101 | this.pswTextSize = pswTextSize
102 | view.invalidate()
103 | }
104 |
105 | fun justSetPswTextSizeValue(pswTextSize: Int) {
106 | this.pswTextSize = pswTextSize
107 | }
108 |
109 | fun getInputBorderColor(): Int {
110 | return inputBorderColor
111 | }
112 |
113 | fun setInputBorderColor(inputBorderColor: Int) {
114 | this.inputBorderColor = inputBorderColor
115 | view.invalidate()
116 | }
117 |
118 | fun getBorderImg(): Int {
119 | return borderImg
120 | }
121 |
122 | fun setBorderImg(borderImg: Int) {
123 | this.borderImg = borderImg
124 | view.invalidate()
125 | }
126 |
127 | fun getInputBorderImg(): Int {
128 | return inputBorderImg
129 | }
130 |
131 | fun setInputBorderImg(inputBorderImg: Int) {
132 | this.inputBorderImg = inputBorderImg
133 | view.invalidate()
134 | }
135 |
136 | fun getDelayTime(): Int {
137 | return delayTime
138 | }
139 |
140 | fun setDelayTime(delayTime: Int) {
141 | this.delayTime = delayTime
142 | view.invalidate()
143 | }
144 |
145 | fun isBorderImg(): Boolean {
146 | return isBorderImg
147 | }
148 |
149 | fun setIsBorderImg(borderImg: Boolean) {
150 | isBorderImg = borderImg
151 | view.invalidate()
152 | }
153 |
154 | fun isShowTextPsw(): Boolean {
155 | return isShowTextPsw
156 | }
157 |
158 | fun setShowTextPsw(showTextPsw: Boolean) {
159 | isShowTextPsw = showTextPsw
160 | view.invalidate()
161 | }
162 |
163 | fun isShowBorderShadow(): Boolean {
164 | return isShowBorderShadow
165 | }
166 |
167 | fun setShowBorderShadow(showBorderShadow: Boolean) {
168 | isShowBorderShadow = showBorderShadow
169 | view.invalidate()
170 | }
171 |
172 | fun isChangeBorder(): Boolean {
173 | return changeBorder
174 | }
175 |
176 | fun setChangeBorder(changeBorder: Boolean) {
177 | this.changeBorder = changeBorder
178 | view.invalidate()
179 | }
180 |
181 | fun getBorderRadius(): Int {
182 | return borderRadius
183 | }
184 |
185 | fun setBorderRadius(borderRadius: Int) {
186 | this.borderRadius = borderRadius
187 | view.invalidate()
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/pswtext/src/main/java/rokudol/com/pswtext/PswConstants.kt:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext
2 |
3 | import android.graphics.Color
4 |
5 | object PswConstants {
6 | const val DEFAULT_STORK_WIDTH:Float = 3f
7 |
8 | const val PSW_LENGTH: Int = 6
9 | const val DELAY_TIME: Int = 1000
10 | const val CLEAR_TEXT_PSW: Boolean = false
11 | const val DARK_PSW: Boolean = false
12 | const val IS_BORDER_IMG: Boolean = false
13 | const val IS_SHOW_TEXT_PSW: Boolean = false
14 | const val IS_SHOW_BORDER_SHADOW: Boolean = false
15 | const val IS_CHANGE_BORDER: Boolean = false
16 |
17 | val PSW_COLOR: Int = Color.parseColor("#3779e3")
18 | val BORDER_COLOR: Int = Color.parseColor("#999999")
19 | val INPUT_BORDER_COLOR: Int = Color.parseColor("#3779e3")
20 | val BORDER_SHADOW_COLOR: Int = Color.parseColor("#3577e2")
21 | val BORDER_IMG: Int = R.drawable.pic_dlzc_srk1
22 | val INPUT_BORDER_IMG: Int = R.drawable.pic_dlzc_srk
23 | }
--------------------------------------------------------------------------------
/pswtext/src/main/java/rokudol/com/pswtext/PswText.java:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Paint;
10 | import android.graphics.Rect;
11 | import android.graphics.RectF;
12 | import android.os.Handler;
13 | import android.os.Message;
14 | import android.text.InputType;
15 | import android.util.AttributeSet;
16 | import android.util.TypedValue;
17 | import android.view.KeyEvent;
18 | import android.view.MotionEvent;
19 | import android.view.View;
20 | import android.view.inputmethod.BaseInputConnection;
21 | import android.view.inputmethod.EditorInfo;
22 | import android.view.inputmethod.InputConnection;
23 | import android.view.inputmethod.InputMethodManager;
24 |
25 | import java.util.ArrayList;
26 |
27 | /**
28 | * 请使用{@link PwdText}
29 | */
30 | @Deprecated
31 | public class PswText extends View {
32 | private InputMethodManager input;//输入法管理
33 | private ArrayList result;//保存当前输入的密码
34 | private int saveResult;//保存按下返回键时输入的密码总数
35 | private int pswLength;//密码长度
36 | private int borderColor;//密码框颜色
37 | private int borderShadowColor;//密码框阴影颜色
38 | private int pswColor;//明文密码颜色
39 | private int pswTextSize;//明文密码字体大小
40 | private int inputBorderColor;//输入时密码边框颜色
41 | private int borderImg;//边框图片
42 | private int inputBorderImg;//输入时边框图片
43 | private int delayTime;//延迟绘制圆点时间,1000 = 1s
44 | private boolean isBorderImg;//是否使用图片绘制边框
45 | private boolean isShowTextPsw;//是否在按返回键时绘制明文密码
46 | private boolean isShowBorderShadow;//是否绘制在输入时,密码框的阴影颜色
47 | private boolean clearTextPsw;//是否只绘制明文密码
48 | private boolean darkPsw;//是否只绘制圆点密码
49 | private boolean isChangeBorder;//是否在输入密码时不更改密码框颜色
50 | private Paint pswDotPaint;//密码圆点画笔
51 | private Paint pswTextPaint;//明文密码画笔
52 | private Paint borderPaint;//边框画笔
53 | private Paint inputBorderPaint;//输入时边框画笔
54 | private RectF borderRectF;//边框圆角矩形
55 | private int borderRadius;//边框圆角程度
56 | private int borderWidth;//边框宽度
57 | private int spacingWidth;//边框之间的间距宽度
58 | private InputCallBack inputCallBack;//输入完成时监听
59 | private TextWatcher textWatcher;
60 | private int height;//整个view的高度
61 |
62 | private static boolean invalidated = false;
63 | private Handler handler = new Handler() {
64 | @Override
65 | public void handleMessage(Message msg) {
66 | super.handleMessage(msg);
67 | switch (msg.what) {
68 | case 1:
69 | invalidated = true;
70 | invalidate();
71 | break;
72 | default:
73 | break;
74 | }
75 | }
76 | };
77 |
78 |
79 | @Deprecated
80 | public interface InputCallBack {
81 | void onInputFinish(String password);
82 | }
83 |
84 | /**
85 | * inputCallback was Deprecated,can use {@link #setTextWatcher}.
86 | */
87 | @Deprecated
88 | public void setInputCallBack(InputCallBack inputCallBack) {
89 | this.inputCallBack = inputCallBack;
90 | }
91 |
92 | public interface TextWatcher {
93 | void textChanged(String password, boolean isFinishInput);
94 | }
95 |
96 | public void setTextWatcher(TextWatcher textWatcher) {
97 | this.textWatcher = textWatcher;
98 | }
99 |
100 | public void setPswLength(int pswLength) {
101 | this.pswLength = pswLength;
102 | invalidate();
103 | }
104 |
105 | public int getPswLength() {
106 | return pswLength;
107 | }
108 |
109 | public void setBorderColor(int borderColor) {
110 | this.borderColor = borderColor;
111 | borderPaint.setColor(borderColor);
112 | invalidate();
113 | }
114 |
115 | public int getBorderColor() {
116 | return borderColor;
117 | }
118 |
119 | public void setPswColor(int pswColor) {
120 | this.pswColor = pswColor;
121 | pswDotPaint.setColor(pswColor);
122 | pswTextPaint.setColor(pswColor);
123 | invalidate();
124 | }
125 |
126 | public int getPswColor() {
127 | return pswColor;
128 | }
129 |
130 | public void setInputBorderColor(int inputBorderColor) {
131 | this.inputBorderColor = inputBorderColor;
132 | inputBorderPaint.setColor(inputBorderColor);
133 | invalidate();
134 | }
135 |
136 | public int getInputBorderColor() {
137 | return inputBorderColor;
138 | }
139 |
140 | public void setBorderShadowColor(int borderShadowColor) {
141 | this.borderShadowColor = borderShadowColor;
142 | inputBorderPaint.setShadowLayer(6, 0, 0, borderShadowColor);
143 | invalidate();
144 | }
145 |
146 | public int getBorderShadowColor() {
147 | return borderShadowColor;
148 | }
149 |
150 | public void setPswTextSize(int pswTextSize) {
151 | this.pswTextSize = pswTextSize;
152 | invalidate();
153 | }
154 |
155 | public int getPswTextSize() {
156 | return pswTextSize;
157 | }
158 |
159 | public void setBorderRadius(int borderRadius) {
160 | this.borderRadius = borderRadius;
161 | invalidate();
162 | }
163 |
164 | public int getBorderRadius() {
165 | return borderRadius;
166 | }
167 |
168 | public void setIsBorderImg(boolean borderImg) {
169 | isBorderImg = borderImg;
170 | invalidate();
171 | }
172 |
173 | public boolean isBorderImg() {
174 | return isBorderImg;
175 | }
176 |
177 | public void setIsShowTextPsw(boolean showTextPsw) {
178 | isShowTextPsw = showTextPsw;
179 | invalidate();
180 | }
181 |
182 | public boolean isShowTextPsw() {
183 | return isShowTextPsw;
184 | }
185 |
186 | public void setBorderImg(int borderImg) {
187 | this.borderImg = borderImg;
188 | invalidate();
189 | }
190 |
191 | public int getBorderImg() {
192 | return borderImg;
193 | }
194 |
195 | public void setInputBorderImg(int inputBorderImg) {
196 | this.inputBorderImg = inputBorderImg;
197 | invalidate();
198 | }
199 |
200 | public int getInputBorderImg() {
201 | return inputBorderImg;
202 | }
203 |
204 | public void setDelayTime(int delayTime) {
205 | this.delayTime = delayTime;
206 | invalidate();
207 | }
208 |
209 | public int getDelayTime() {
210 | return delayTime;
211 | }
212 |
213 | public void setClearTextPsw(boolean clearTextPsw) {
214 | this.clearTextPsw = clearTextPsw;
215 | invalidate();
216 | }
217 |
218 | public boolean isClearTextPsw() {
219 | return clearTextPsw;
220 | }
221 |
222 | public void setDarkPsw(boolean darkPsw) {
223 | this.darkPsw = darkPsw;
224 | invalidate();
225 | }
226 |
227 | public boolean isDarkPsw() {
228 | return darkPsw;
229 | }
230 |
231 | public void setIsChangeBorder(boolean changeBorder) {
232 | isChangeBorder = changeBorder;
233 | invalidate();
234 | }
235 |
236 | public boolean isChangeBorder() {
237 | return isChangeBorder;
238 | }
239 |
240 | public void setShowBorderShadow(boolean showBorderShadow) {
241 | isShowBorderShadow = showBorderShadow;
242 | invalidate();
243 | }
244 |
245 | public boolean isShowBorderShadow() {
246 | return isShowBorderShadow;
247 | }
248 |
249 | public PswText(Context context) {
250 | super(context);
251 | }
252 |
253 | public PswText(Context context, AttributeSet attrs) {
254 | super(context, attrs);
255 | initView(context, attrs);
256 | }
257 |
258 | public PswText(Context context, AttributeSet attrs, int defStyleAttr) {
259 | super(context, attrs, defStyleAttr);
260 | initView(context, attrs);
261 | }
262 |
263 | //初始化
264 | private void initView(Context context, AttributeSet attrs) {
265 | this.setOnKeyListener(new NumKeyListener());
266 | this.setFocusable(true);
267 | this.setFocusableInTouchMode(true);
268 | input = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
269 | result = new ArrayList<>();
270 |
271 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PwdText);
272 | if (array != null) {
273 | pswLength = array.getInt(R.styleable.PwdText_pswLength, 6);
274 | pswColor = array.getColor(R.styleable.PwdText_pswColor, Color.parseColor("#3779e3"));
275 | borderColor = array.getColor(R.styleable.PwdText_borderColor, Color.parseColor("#999999"));
276 | inputBorderColor = array.getColor(R.styleable.PwdText_inputBorder_color, Color.parseColor("#3779e3"));
277 | borderShadowColor = array.getColor(R.styleable.PwdText_borderShadow_color, Color.parseColor("#3577e2"));
278 | borderImg = array.getResourceId(R.styleable.PwdText_borderImg, R.drawable.pic_dlzc_srk1);
279 | inputBorderImg = array.getResourceId(R.styleable.PwdText_inputBorderImg, R.drawable.pic_dlzc_srk);
280 | isBorderImg = array.getBoolean(R.styleable.PwdText_isDrawBorderImg, false);
281 | isShowTextPsw = array.getBoolean(R.styleable.PwdText_isShowTextPsw, false);
282 | isShowBorderShadow = array.getBoolean(R.styleable.PwdText_isShowBorderShadow, false);
283 | clearTextPsw = array.getBoolean(R.styleable.PwdText_clearTextPsw, false);
284 | darkPsw = array.getBoolean(R.styleable.PwdText_darkPsw, false);
285 | isChangeBorder = array.getBoolean(R.styleable.PwdText_isChangeBorder, false);
286 | delayTime = array.getInt(R.styleable.PwdText_delayTime, 1000);
287 | pswTextSize = (int) array.getDimension(R.styleable.PwdText_psw_textSize,
288 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 18, getResources().getDisplayMetrics()));
289 | borderRadius = (int) array.getDimension(R.styleable.PwdText_borderRadius,
290 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()));
291 | } else {
292 | pswLength = 6;
293 | pswColor = Color.parseColor("#3779e3");
294 | borderColor = Color.parseColor("#999999");
295 | inputBorderColor = Color.parseColor("#3779e3");
296 | borderShadowColor = Color.parseColor("#3577e2");
297 | borderImg = R.drawable.pic_dlzc_srk1;
298 | inputBorderImg = R.drawable.pic_dlzc_srk;
299 | delayTime = 1000;
300 | clearTextPsw = false;
301 | darkPsw = false;
302 | isBorderImg = false;
303 | isShowTextPsw = false;
304 | isShowBorderShadow = false;
305 | isChangeBorder = false;
306 | //明文密码字体大小,初始化18sp
307 | pswTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 18, getResources().getDisplayMetrics());
308 | //边框圆角程度初始化8dp
309 | borderRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
310 | }
311 | //边框宽度初始化40dp
312 | borderWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());
313 | //边框之间的间距初始化10dp
314 | spacingWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
315 | borderRectF = new RectF();
316 | initPaint();
317 | }
318 |
319 | //初始化画笔
320 | private void initPaint() {
321 | //密码圆点画笔初始化
322 | pswDotPaint = new Paint();
323 | pswDotPaint.setAntiAlias(true);
324 | pswDotPaint.setStrokeWidth(3);
325 | pswDotPaint.setStyle(Paint.Style.FILL);
326 | pswDotPaint.setColor(pswColor);
327 |
328 | //明文密码画笔初始化
329 | pswTextPaint = new Paint();
330 | pswTextPaint.setAntiAlias(true);
331 | pswTextPaint.setFakeBoldText(true);
332 | pswTextPaint.setColor(pswColor);
333 |
334 | //边框画笔初始化
335 | borderPaint = new Paint();
336 | borderPaint.setAntiAlias(true);
337 | borderPaint.setColor(borderColor);
338 | borderPaint.setStyle(Paint.Style.STROKE);
339 | borderPaint.setStrokeWidth(3);
340 |
341 | //输入时边框画笔初始化
342 | inputBorderPaint = new Paint();
343 | inputBorderPaint.setAntiAlias(true);
344 | inputBorderPaint.setColor(inputBorderColor);
345 | inputBorderPaint.setStyle(Paint.Style.STROKE);
346 | inputBorderPaint.setStrokeWidth(3);
347 | //是否绘制边框阴影
348 | if (isShowBorderShadow) {
349 | inputBorderPaint.setShadowLayer(6, 0, 0, borderShadowColor);
350 | setLayerType(LAYER_TYPE_SOFTWARE, inputBorderPaint);
351 | }
352 | }
353 |
354 | @Override
355 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
356 | int widthSpec = MeasureSpec.getMode(widthMeasureSpec);
357 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
358 |
359 | int heightSpec = MeasureSpec.getMode(heightMeasureSpec);
360 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
361 |
362 | if (widthSpec == MeasureSpec.AT_MOST) {
363 | if (heightSpec != MeasureSpec.AT_MOST) {//高度已知但宽度未知时
364 | spacingWidth = heightSize / 4;
365 | widthSize = (heightSize * pswLength) + (spacingWidth * pswLength);
366 | borderWidth = heightSize;
367 | } else {//宽高都未知时
368 | widthSize = (borderWidth * pswLength) + (spacingWidth * pswLength);
369 | heightSize = (int) (borderWidth + ((borderPaint.getStrokeWidth()) * 2));
370 | }
371 | } else {
372 | //宽度已知但高度未知时
373 | if (heightSpec == MeasureSpec.AT_MOST) {
374 | borderWidth = (widthSize * 4) / (5 * pswLength);
375 | spacingWidth = borderWidth / 4;
376 | heightSize = (int) (borderWidth + ((borderPaint.getStrokeWidth()) * 2));
377 | }
378 | }
379 | height = heightSize;
380 | setMeasuredDimension(widthSize, heightSize);
381 | }
382 |
383 | @Override
384 | protected void onDraw(Canvas canvas) {
385 | super.onDraw(canvas);
386 | int dotRadius = borderWidth / 6;//密码圆点为边框宽度的六分之一
387 |
388 | /*
389 | * 如果明文密码字体大小为默认大小,则取边框宽度的八分之一,否则用自定义大小
390 | * */
391 | if (pswTextSize == (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 18, getResources().getDisplayMetrics())) {
392 | pswTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, borderWidth / 8, getResources().getDisplayMetrics());
393 | }
394 | pswTextPaint.setTextSize(pswTextSize);
395 |
396 | //绘制密码边框
397 | drawBorder(canvas, height);
398 | if (isChangeBorder) {
399 | if (clearTextPsw) {
400 | for (int i = 0; i < result.size(); i++) {
401 | String num = result.get(i) + "";
402 | drawText(canvas, num, i);
403 | }
404 | } else if (darkPsw) {
405 | for (int i = 0; i < result.size(); i++) {
406 | float circleX = (float) ((i * (borderWidth + spacingWidth)) + (borderWidth / 2) + (0.5 * spacingWidth));
407 | float circleY = height / 2;
408 | canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
409 | }
410 | } else {
411 | if (invalidated) {
412 | drawDelayCircle(canvas, height, dotRadius);
413 | return;
414 | }
415 | for (int i = 0; i < result.size(); i++) {
416 | //明文密码
417 | String num = result.get(i) + "";
418 | //圆点坐标
419 | float circleX = (float) (((i - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2) + (0.5 * spacingWidth));
420 | float circleY = height / 2;
421 | //密码框坐标
422 | drawText(canvas, num, i);
423 |
424 | /*
425 | * 当输入位置 = 输入长度时
426 | * 即判断当前绘制位置是否等于当前正在输入密码的位置
427 | * 若是则延迟delayTime时间后绘制为圆点
428 | * */
429 | if (i + 1 == result.size()) {
430 | handler.sendEmptyMessageDelayed(1, delayTime);
431 | }
432 | //若按下back键保存的密码 > 输入的密码长度,则只绘制圆点
433 | //即按下back键时,不绘制明文密码
434 | if (!isShowTextPsw) {
435 | if (saveResult > result.size()) {
436 | canvas.drawCircle((float) ((i * (borderWidth + spacingWidth)) + (borderWidth / 2 + (0.5 * spacingWidth))), circleY, dotRadius, pswDotPaint);
437 | }
438 | }
439 | //当输入第二个密码时,才开始从第一个位置绘制圆点
440 | if (i >= 1) {
441 | canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
442 | }
443 | }
444 | }
445 | } else {
446 | if (clearTextPsw) {
447 | for (int i = 0; i < result.size(); i++) {
448 | String num = result.get(i) + "";
449 | drawText(canvas, num, i);
450 | //计算密码边框坐标
451 | int left = (int) (i * (borderWidth + spacingWidth) + (0.5 * spacingWidth));
452 | int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.5 * spacingWidth));
453 |
454 | drawBitmapOrBorder(canvas, left, right, height);
455 | }
456 | } else if (darkPsw) {
457 | for (int i = 0; i < result.size(); i++) {
458 | float circleX = (float) ((i * (borderWidth + spacingWidth)) + (borderWidth / 2) + (0.5 * spacingWidth));
459 | float circleY = height / 2;
460 | int left = (int) (i * (borderWidth + spacingWidth) + (0.5 * spacingWidth));
461 | int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.5 * spacingWidth));
462 | drawBitmapOrBorder(canvas, left, right, height);
463 | canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
464 | }
465 | } else {
466 | if (invalidated) {
467 | drawDelayCircle(canvas, height, dotRadius);
468 | return;
469 | }
470 | for (int i = 0; i < result.size(); i++) {
471 | //明文密码
472 | String num = result.get(i) + "";
473 | //圆点坐标
474 | float circleX = (float) (((i - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2) + (0.5 * spacingWidth));
475 | float circleY = height / 2;
476 | //密码框坐标
477 | int left = (int) (i * (borderWidth + spacingWidth) + (0.5 * spacingWidth));
478 | int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.5 * spacingWidth));
479 |
480 | drawBitmapOrBorder(canvas, left, right, height);
481 |
482 | drawText(canvas, num, i);
483 |
484 | /*
485 | * 当输入位置 = 输入长度时
486 | * 即判断当前绘制位置是否等于当前正在输入密码的位置
487 | * 若是则延迟delayTime时间后绘制为圆点
488 | * */
489 | if (i + 1 == result.size()) {
490 | handler.sendEmptyMessageDelayed(1, delayTime);
491 | }
492 | //若按下back键保存的密码 > 输入的密码长度,则只绘制圆点
493 | //即按下back键时,不绘制明文密码
494 | if (!isShowTextPsw) {
495 | if (saveResult > result.size()) {
496 | canvas.drawCircle((float) ((i * (borderWidth + spacingWidth)) + (borderWidth / 2 + (0.5 * spacingWidth))), circleY, dotRadius, pswDotPaint);
497 | }
498 | }
499 | //当输入第二个密码时,才开始从第一个位置绘制圆点
500 | if (i >= 1) {
501 | canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
502 | }
503 | }
504 | }
505 | }
506 |
507 | }
508 |
509 | //绘制明文密码
510 | private void drawText(Canvas canvas, String num, int i) {
511 | Rect mTextBound = new Rect();
512 | pswTextPaint.getTextBounds(num, 0, num.length(), mTextBound);
513 | Paint.FontMetrics fontMetrics = pswTextPaint.getFontMetrics();
514 | float textX = (float) ((i * (borderWidth + spacingWidth)) + (borderWidth / 2 - mTextBound.width() / 2) + (0.45 * spacingWidth));
515 | float textY = (height - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
516 | if (saveResult != 0 || saveResult < result.size()) {
517 | canvas.drawText(num, textX, textY, pswTextPaint);
518 | }
519 | }
520 |
521 | //延迟delay时间后,将当前输入的明文密码绘制为圆点
522 | private void drawDelayCircle(Canvas canvas, int height, int dotRadius) {
523 | invalidated = false;
524 | if (isChangeBorder) {
525 | for (int i = 0; i < result.size(); i++) {
526 | float circleX = (float) (((i - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2) + (0.5 * spacingWidth));
527 | float circleY = height / 2;
528 | canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
529 | }
530 | canvas.drawCircle((float) ((float) (((result.size() - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2)) + (0.5 * spacingWidth)),
531 | height / 2, dotRadius, pswDotPaint);
532 | } else {
533 | for (int i = 0; i < result.size(); i++) {
534 | float circleX = (float) (((i - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2) + (0.5 * spacingWidth));
535 | float circleY = height / 2;
536 | int left = (int) (i * (borderWidth + spacingWidth) + (0.5 * spacingWidth));
537 | int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.5 * spacingWidth));
538 | canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
539 | drawBitmapOrBorder(canvas, left, right, height);
540 | }
541 | canvas.drawCircle((float) ((float) (((result.size() - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2)) + (0.5 * spacingWidth)),
542 | height / 2, dotRadius, pswDotPaint);
543 | }
544 | handler.removeMessages(1);
545 | }
546 |
547 | //绘制初始密码框时判断是否用图片绘制密码框
548 | private void drawBorder(Canvas canvas, int height) {
549 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), borderImg);
550 | Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
551 | for (int i = 0; i < pswLength; i++) {
552 | int left = (int) ((i * (borderWidth + spacingWidth)) + (0.5 * spacingWidth));
553 | int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.5 * spacingWidth));
554 | if (isBorderImg) {
555 | Rect dst = new Rect(left, (int) borderPaint.getStrokeWidth(), right, (int) (height - (borderPaint.getStrokeWidth())));
556 | canvas.drawBitmap(bitmap, src, dst, borderPaint);
557 | } else {
558 | borderRectF.set(left, borderPaint.getStrokeWidth(), right, height - (borderPaint.getStrokeWidth()));
559 | canvas.drawRoundRect(borderRectF, borderRadius, borderRadius, borderPaint);
560 | }
561 | }
562 | bitmap.recycle();
563 |
564 | }
565 |
566 | //是否使用图片绘制密码框
567 | private void drawBitmapOrBorder(Canvas canvas, int left, int right, int height) {
568 | if (isBorderImg) {
569 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), inputBorderImg);
570 | Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
571 | Rect dst = new Rect(left, (int) (0 + ((borderPaint.getStrokeWidth()))), right, (int) (height - (borderPaint.getStrokeWidth())));
572 | canvas.drawBitmap(bitmap, src, dst, inputBorderPaint);
573 | bitmap.recycle();
574 | } else {
575 | borderRectF.set(left, 0 + (borderPaint.getStrokeWidth()), right, height - (borderPaint.getStrokeWidth()));
576 | canvas.drawRoundRect(borderRectF, borderRadius, borderRadius, inputBorderPaint);
577 | }
578 | }
579 |
580 | //清除密码
581 | public void clearPsw() {
582 | result.clear();
583 | invalidate();
584 | }
585 |
586 | //获取密码
587 | public String getPsw() {
588 | StringBuffer sb = new StringBuffer();
589 | for (int i : result) {
590 | sb.append(i);
591 | }
592 | return sb.toString();
593 | }
594 |
595 | //隐藏键盘
596 | public void hideKeyBord() {
597 | input.hideSoftInputFromWindow(this.getWindowToken(), 0);
598 | }
599 |
600 | @Override
601 | public boolean onTouchEvent(MotionEvent event) {
602 | if (event.getAction() == MotionEvent.ACTION_DOWN) {//点击弹出键盘
603 | requestFocus();
604 | input.showSoftInput(this, InputMethodManager.SHOW_FORCED);
605 | return true;
606 | }
607 | return super.onTouchEvent(event);
608 | }
609 |
610 | @Override
611 | public void onWindowFocusChanged(boolean hasWindowFocus) {
612 | super.onWindowFocusChanged(hasWindowFocus);
613 | if (!hasWindowFocus) {
614 | input.hideSoftInputFromWindow(this.getWindowToken(), 0);
615 | }
616 | }
617 |
618 | @Override
619 | public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
620 | outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;//只允许输入数字
621 | outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
622 | return new NumInputConnection(this, false);
623 | }
624 |
625 | @Override
626 | public boolean onCheckIsTextEditor() {
627 | return true;
628 | }
629 |
630 | class NumInputConnection extends BaseInputConnection {
631 |
632 | public NumInputConnection(View targetView, boolean fullEditor) {
633 | super(targetView, fullEditor);
634 | }
635 |
636 | @Override
637 | public boolean commitText(CharSequence text, int newCursorPosition) {
638 | //这里是接收文本的输入法,我们只允许输入数字,则不做任何处理
639 | return super.commitText(text, newCursorPosition);
640 | }
641 |
642 | @Override
643 | public boolean deleteSurroundingText(int beforeLength, int afterLength) {
644 | //屏蔽返回键,发送自己的删除事件
645 | if (beforeLength == 1 && afterLength == 0) {
646 | return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
647 | && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
648 | }
649 | return super.deleteSurroundingText(beforeLength, afterLength);
650 | }
651 | }
652 |
653 | /**
654 | * 输入监听
655 | */
656 | class NumKeyListener implements OnKeyListener {
657 | @Override
658 | public boolean onKey(View v, int keyCode, KeyEvent event) {
659 | if (event.getAction() == KeyEvent.ACTION_DOWN) {
660 | if (event.isShiftPressed()) {//处理*#等键
661 | return false;
662 | }
663 | if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {//只处理数字
664 | if (result.size() < pswLength) {
665 | result.add(keyCode - 7);
666 | invalidate();
667 | FinishInput();
668 | }
669 | return true;
670 | }
671 | if (keyCode == KeyEvent.KEYCODE_DEL) {
672 | if (!result.isEmpty()) {//不为空时,删除最后一个数字
673 | saveResult = result.size();
674 | result.remove(result.size() - 1);
675 | invalidate();
676 | FinishInput();
677 | }
678 | return true;
679 | }
680 | if (keyCode == KeyEvent.KEYCODE_ENTER) {
681 | FinishInput();
682 | return true;
683 | }
684 | }
685 | return false;
686 | }
687 |
688 | /**
689 | * 输入完成后调用的方法
690 | */
691 | void FinishInput() {
692 | StringBuffer sb = new StringBuffer();
693 | for (int i : result) {
694 | sb.append(i);
695 | }
696 | if (result.size() == pswLength) {//输入已完成
697 | if (inputCallBack != null) {
698 | inputCallBack.onInputFinish(sb.toString());
699 | }
700 | if (textWatcher != null) {
701 | textWatcher.textChanged(sb.toString(), true);
702 | }
703 | InputMethodManager imm = (InputMethodManager) PswText.this.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
704 | imm.hideSoftInputFromWindow(PswText.this.getWindowToken(), 0); //输入完成后隐藏键盘
705 | } else {
706 | if (textWatcher != null) {
707 | textWatcher.textChanged(sb.toString(), false);
708 | }
709 | }
710 | }
711 | }
712 | }
--------------------------------------------------------------------------------
/pswtext/src/main/java/rokudol/com/pswtext/PwdText.kt:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.*
6 | import android.os.Handler
7 | import android.os.Message
8 | import android.text.InputType
9 | import android.util.AttributeSet
10 | import android.util.TypedValue
11 | import android.view.MotionEvent
12 | import android.view.View
13 | import android.view.inputmethod.EditorInfo
14 | import android.view.inputmethod.InputConnection
15 | import android.view.inputmethod.InputMethodManager
16 |
17 | class PwdText : View {
18 | /**
19 | * 输入法管理
20 | */
21 | private var input: InputMethodManager? = null
22 | /**
23 | * 当前输入的密码
24 | */
25 | private var results: ArrayList = ArrayList()
26 |
27 | private lateinit var pswAttrsBean: PswAttrsBean
28 | private var borderWidth: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40f, resources.displayMetrics).toInt()
29 | private var spacingWidth: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics).toInt()
30 |
31 | /**
32 | * 密码圆点画笔
33 | */
34 | private lateinit var pswDotPaint: Paint
35 |
36 | /**
37 | * 明文密码画笔
38 | */
39 | private lateinit var pswTextPaint: Paint
40 |
41 | /**
42 | * 边框画笔
43 | */
44 | private lateinit var borderPaint: Paint
45 |
46 | /**
47 | * 输入时边框画笔
48 | */
49 | private lateinit var inputBorderPaint: Paint
50 |
51 | /**
52 | * 边框圆角矩形
53 | */
54 | private lateinit var borderRectF: RectF
55 |
56 | /**
57 | * 控件高度
58 | */
59 | private var mHeight: Int = 0
60 |
61 | /**
62 | * 保存按下返回键时输入的密码总数
63 | */
64 | private var saveResult: Int = 0
65 |
66 | private var textWatcher: TextWatcher? = null
67 |
68 | private var invalidated: Boolean = false
69 | private val mHandler = object : Handler() {
70 | override fun handleMessage(msg: Message) {
71 | super.handleMessage(msg)
72 | when (msg.what) {
73 | 1 -> {
74 | invalidated = true
75 | invalidate()
76 | }
77 | else -> {
78 | }
79 | }
80 | }
81 | }
82 |
83 | constructor(context: Context) : super(context)
84 | constructor(context: Context, attr: AttributeSet) : super(context, attr) {
85 | initView(context, attr)
86 | }
87 |
88 | constructor(context: Context, attr: AttributeSet, defStyleAttr: Int) : super(context, attr, defStyleAttr) {
89 | initView(context, attr)
90 | }
91 |
92 | private fun initView(context: Context, attrs: AttributeSet) {
93 | setOnKeyListener(NumKeyListener(object : InputStatusListener {
94 | override fun add(num: Int) {
95 | if (results.size < pswAttrsBean.getPswLength()) {
96 | results.add(num)
97 | invalidate()
98 | }
99 | }
100 |
101 | override fun remove() {
102 | if (results.isNotEmpty()) {//不为空时,删除最后一个数字
103 | saveResult = results.size
104 | results.removeAt(results.size - 1)
105 | invalidate()
106 | }
107 | }
108 |
109 | override fun finishInput() {
110 | val sb = StringBuffer()
111 | for (i in results) {
112 | sb.append(i)
113 | }
114 | if (results.size == pswAttrsBean.getPswLength()) {//输入已完成
115 | if (getTextWatcher() != null) {
116 | getTextWatcher()?.textChanged(sb.toString(), true)
117 | }
118 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
119 | imm.hideSoftInputFromWindow(windowToken, 0) //输入完成后隐藏键盘
120 | } else {
121 | if (textWatcher != null) {
122 | getTextWatcher()?.textChanged(sb.toString(), false)
123 | }
124 | }
125 | }
126 | }))
127 | isFocusable = true
128 | isFocusableInTouchMode = true
129 | input = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
130 | val array: TypedArray? = context.obtainStyledAttributes(attrs, R.styleable.PwdText)
131 | if (array != null) {
132 | pswAttrsBean = PswAttrsBean(this,
133 | array.getInt(R.styleable.PwdText_pswLength, PswConstants.PSW_LENGTH),
134 | array.getColor(R.styleable.PwdText_borderColor, PswConstants.BORDER_COLOR),
135 | array.getColor(R.styleable.PwdText_borderShadow_color, PswConstants.BORDER_SHADOW_COLOR),
136 | array.getColor(R.styleable.PwdText_pswColor, PswConstants.PSW_COLOR),
137 | array.getColor(R.styleable.PwdText_inputBorder_color, PswConstants.INPUT_BORDER_COLOR),
138 | array.getResourceId(R.styleable.PwdText_inputBorderImg, PswConstants.INPUT_BORDER_IMG),
139 | array.getResourceId(R.styleable.PwdText_borderImg, PswConstants.BORDER_IMG),
140 | array.getInt(R.styleable.PwdText_delayTime, PswConstants.DELAY_TIME),
141 | array.getBoolean(R.styleable.PwdText_isDrawBorderImg, PswConstants.IS_BORDER_IMG),
142 | array.getBoolean(R.styleable.PwdText_isShowTextPsw, PswConstants.IS_SHOW_TEXT_PSW),
143 | array.getBoolean(R.styleable.PwdText_isShowBorderShadow, PswConstants.IS_SHOW_BORDER_SHADOW),
144 | array.getBoolean(R.styleable.PwdText_clearTextPsw, PswConstants.CLEAR_TEXT_PSW),
145 | array.getBoolean(R.styleable.PwdText_darkPsw, PswConstants.DARK_PSW),
146 | array.getBoolean(R.styleable.PwdText_isChangeBorder, PswConstants.IS_CHANGE_BORDER),
147 | array.getInt(R.styleable.PwdText_psw_textSize, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 18f, resources.displayMetrics).toInt()),
148 | array.getInt(R.styleable.PwdText_borderRadius, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics).toInt()))
149 | array.recycle()
150 | } else {
151 | pswAttrsBean = PswAttrsBean(this)
152 | }
153 | initPaint()
154 | }
155 |
156 | private fun initPaint() {
157 | borderRectF = RectF()
158 | //密码圆点画笔初始化
159 | pswDotPaint = Paint()
160 | pswDotPaint.isAntiAlias = true
161 | pswDotPaint.strokeWidth = PswConstants.DEFAULT_STORK_WIDTH
162 | pswDotPaint.style = Paint.Style.FILL
163 | pswDotPaint.color = pswAttrsBean.getPswColor()
164 |
165 | //明文密码画笔初始化
166 | pswTextPaint = Paint()
167 | pswTextPaint.isAntiAlias = true
168 | pswTextPaint.isFakeBoldText = true
169 | pswTextPaint.color = pswAttrsBean.getPswColor()
170 |
171 | //边框画笔初始化
172 | borderPaint = Paint()
173 | borderPaint.isAntiAlias = true
174 | borderPaint.color = pswAttrsBean.getBorderColor()
175 | borderPaint.style = Paint.Style.STROKE
176 | borderPaint.strokeWidth = PswConstants.DEFAULT_STORK_WIDTH
177 |
178 | //输入时边框画笔初始化
179 | inputBorderPaint = Paint()
180 | inputBorderPaint.isAntiAlias = true
181 | inputBorderPaint.color = pswAttrsBean.getInputBorderColor()
182 | inputBorderPaint.style = Paint.Style.STROKE
183 | inputBorderPaint.strokeWidth = PswConstants.DEFAULT_STORK_WIDTH
184 |
185 | //是否绘制边框阴影
186 | if (pswAttrsBean.isShowBorderShadow()) {
187 | inputBorderPaint.setShadowLayer(6f, 0f, 0f, pswAttrsBean.getBorderShadowColor())
188 | setLayerType(LAYER_TYPE_SOFTWARE, inputBorderPaint)
189 | }
190 | }
191 |
192 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
193 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
194 | var widthSize = MeasureSpec.getSize(widthMeasureSpec)
195 | var widthSpec = MeasureSpec.getMode(widthMeasureSpec)
196 |
197 | var heightSize = MeasureSpec.getSize(heightMeasureSpec)
198 | var heightSpec = MeasureSpec.getMode(heightMeasureSpec)
199 |
200 | if (widthSpec == MeasureSpec.AT_MOST) {
201 | //高度已知但宽度未知时
202 | if (heightSpec != MeasureSpec.AT_MOST) {
203 | spacingWidth = heightSize / 4
204 | widthSize = heightSize * pswAttrsBean.getPswLength() + spacingWidth * pswAttrsBean.getPswLength()
205 | borderWidth = heightSize
206 | } else {
207 | //宽高都未知时
208 | widthSize = borderWidth * pswAttrsBean.getPswLength() + spacingWidth * pswAttrsBean.getPswLength()
209 | heightSize = (borderWidth + borderPaint.strokeWidth * 2).toInt()
210 | }
211 | } else {
212 | //宽度已知但高度未知时
213 | if (heightSpec == View.MeasureSpec.AT_MOST) {
214 | borderWidth = widthSize * 4 / (5 * pswAttrsBean.getPswLength())
215 | spacingWidth = borderWidth / 4
216 | heightSize = (borderWidth + borderPaint.strokeWidth * 2).toInt()
217 | }
218 | }
219 |
220 | mHeight = heightSize
221 | setMeasuredDimension(widthSize, heightSize)
222 | }
223 |
224 | private fun initColor() {
225 | pswDotPaint.color = pswAttrsBean.getPswColor()
226 | pswTextPaint.color = pswAttrsBean.getPswColor()
227 | borderPaint.color = pswAttrsBean.getBorderColor()
228 | inputBorderPaint.color = pswAttrsBean.getInputBorderColor()
229 | }
230 |
231 | override fun onDraw(canvas: Canvas) {
232 | super.onDraw(canvas)
233 | initColor()
234 | //密码圆点为边框宽度的六分之一
235 | val dotRadius = borderWidth / 6
236 |
237 | //如果明文密码字体大小为默认大小,则取边框宽度的八分之一,否则用自定义大小
238 | if (pswAttrsBean.getPswTextSize() == TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 18f, resources.displayMetrics).toInt()) {
239 | pswAttrsBean.justSetPswTextSizeValue(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, (borderWidth / 8).toFloat(), resources.displayMetrics).toInt())
240 | }
241 | pswTextPaint.textSize = pswAttrsBean.getPswTextSize().toFloat()
242 | drawBorder(canvas, mHeight)
243 | if (pswAttrsBean.isChangeBorder()) {
244 | when {
245 | pswAttrsBean.isClearTextPsw() -> for ((index: Int, num: Int) in results.withIndex()) {
246 | drawText(canvas, num.toString(), index)
247 | }
248 | pswAttrsBean.isDarkPsw() -> for (index in results.indices) {
249 | val circleX = ((index * (borderWidth + spacingWidth)).toDouble() + (borderWidth / 2).toDouble() + 0.5 * spacingWidth).toFloat()
250 | val circleY = (mHeight / 2).toFloat()
251 | canvas.drawCircle(circleX, circleY, dotRadius.toFloat(), pswDotPaint)
252 | }
253 | else -> {
254 | if (invalidated) {
255 | drawDelayCircle(canvas, height, dotRadius)
256 | return
257 | }
258 | for ((index: Int, num: Int) in results.withIndex()) {
259 | //圆点坐标
260 | val circleX = (((index - 1) * (borderWidth + spacingWidth)).toDouble() + (borderWidth / 2).toDouble() + 0.5 * spacingWidth).toFloat()
261 | val circleY = (mHeight / 2).toFloat()
262 | //密码框坐标
263 | drawText(canvas, num.toString(), index)
264 | /*
265 | * 当输入位置 = 输入长度时
266 | * 即判断当前绘制位置是否等于当前正在输入密码的位置
267 | * 若是则延迟delayTime时间后绘制为圆点
268 | * */
269 | if (index + 1 == results.size) {
270 | mHandler.sendEmptyMessageDelayed(1, pswAttrsBean.getDelayTime().toLong())
271 | }
272 | //若按下back键保存的密码 > 输入的密码长度,则只绘制圆点
273 | //即按下back键时,不绘制明文密码
274 | if (!pswAttrsBean.isShowTextPsw()) {
275 | if (saveResult > results.size) {
276 | canvas.drawCircle((index * (borderWidth + spacingWidth) + (borderWidth / 2 + 0.5 * spacingWidth)).toFloat(), circleY, dotRadius.toFloat(), pswDotPaint)
277 | }
278 | }
279 | //当输入第二个密码时,才开始从第一个位置绘制圆点
280 | if (index >= 1) {
281 | canvas.drawCircle(circleX, circleY, dotRadius.toFloat(), pswDotPaint)
282 | }
283 | }
284 | }
285 | }
286 | } else {
287 | when {
288 | pswAttrsBean.isClearTextPsw() -> for ((index: Int, num: Int) in results.withIndex()) {
289 | //计算密码边框坐标
290 | val left = (index * (borderWidth + spacingWidth) + 0.5 * spacingWidth).toInt()
291 | val right = (((index + 1) * borderWidth).toDouble() + (index * spacingWidth).toDouble() + 0.5 * spacingWidth).toInt()
292 |
293 | drawBitmapOrBorder(canvas, left, right, height)
294 | drawText(canvas, num.toString(), index)
295 | }
296 | pswAttrsBean.isDarkPsw() -> for (index in results.indices) {
297 | val circleX = ((index * (borderWidth + spacingWidth)).toDouble() + (borderWidth / 2).toDouble() + 0.5 * spacingWidth).toFloat()
298 | val circleY = (height / 2).toFloat()
299 | val left = (index * (borderWidth + spacingWidth) + 0.5 * spacingWidth).toInt()
300 | val right = (((index + 1) * borderWidth).toDouble() + (index * spacingWidth).toDouble() + 0.5 * spacingWidth).toInt()
301 | drawBitmapOrBorder(canvas, left, right, height)
302 | canvas.drawCircle(circleX, circleY, dotRadius.toFloat(), pswDotPaint)
303 | }
304 | else -> {
305 | if (invalidated) {
306 | drawDelayCircle(canvas, height, dotRadius)
307 | return
308 | }
309 | for ((index: Int, num: Int) in results.withIndex()) {
310 | //圆点坐标
311 | val circleX = (((index - 1) * (borderWidth + spacingWidth)).toDouble() + (borderWidth / 2).toDouble() + 0.5 * spacingWidth).toFloat()
312 | val circleY = (height / 2).toFloat()
313 | //密码框坐标
314 | val left = (index * (borderWidth + spacingWidth) + 0.5 * spacingWidth).toInt()
315 | val right = (((index + 1) * borderWidth).toDouble() + (index * spacingWidth).toDouble() + 0.5 * spacingWidth).toInt()
316 |
317 | drawBitmapOrBorder(canvas, left, right, height)
318 |
319 | drawText(canvas, num.toString(), index)
320 |
321 | /**
322 | * 当输入位置 = 输入长度时
323 | * 即判断当前绘制位置是否等于当前正在输入密码的位置
324 | * 若是则延迟delayTime时间后绘制为圆点
325 | */
326 | if (index + 1 == results.size) {
327 | mHandler.sendEmptyMessageDelayed(1, pswAttrsBean.getDelayTime().toLong())
328 | }
329 | //若按下back键保存的密码 > 输入的密码长度,则只绘制圆点
330 | //即按下back键时,不绘制明文密码
331 | if (!pswAttrsBean.isShowTextPsw()) {
332 | if (saveResult > results.size) {
333 | canvas.drawCircle((index * (borderWidth + spacingWidth) + (borderWidth / 2 + 0.5 * spacingWidth)).toFloat(), circleY, dotRadius.toFloat(), pswDotPaint)
334 | }
335 | }
336 | //当输入第二个密码时,才开始从第一个位置绘制圆点
337 | if (index >= 1) {
338 | canvas.drawCircle(circleX, circleY, dotRadius.toFloat(), pswDotPaint)
339 | }
340 | }
341 | }
342 | }
343 | }
344 | }
345 |
346 |
347 | /**
348 | * 绘制初始密码框时判断是否用图片绘制密码框
349 | */
350 | private fun drawBorder(canvas: Canvas, height: Int) {
351 | val bitmap: Bitmap? = BitmapFactory.decodeResource(resources, pswAttrsBean.getBorderImg())
352 | if(bitmap != null) {
353 | val src = Rect(0, 0, bitmap.width, bitmap.height)
354 | for (i in 0 until pswAttrsBean.getPswLength()) {
355 | val left = (i * (borderWidth + spacingWidth) + 0.5 * spacingWidth).toInt()
356 | val right = (((i + 1) * borderWidth).toDouble() + (i * spacingWidth).toDouble() + 0.5 * spacingWidth).toInt()
357 | if (pswAttrsBean.isBorderImg()) {
358 | val dst = Rect(left, borderPaint.strokeWidth.toInt(), right, (height - borderPaint.strokeWidth).toInt())
359 | canvas.drawBitmap(bitmap, src, dst, borderPaint)
360 | } else {
361 | borderRectF.set(left.toFloat(), borderPaint.strokeWidth, right.toFloat(), height - borderPaint.strokeWidth)
362 | canvas.drawRoundRect(borderRectF, pswAttrsBean.getBorderRadius().toFloat(), pswAttrsBean.getBorderRadius().toFloat(), borderPaint)
363 | }
364 | }
365 | bitmap.recycle()
366 | }
367 | }
368 |
369 | /**
370 | * 绘制明文密码
371 | */
372 | private fun drawText(canvas: Canvas, num: String, i: Int) {
373 | val mTextBound = Rect()
374 | pswTextPaint.getTextBounds(num, 0, num.length, mTextBound)
375 | val fontMetrics = pswTextPaint.fontMetrics
376 | val textX = ((i * (borderWidth + spacingWidth)).toDouble() + (borderWidth / 2 - mTextBound.width() / 2).toDouble() + 0.45 * spacingWidth).toFloat()
377 | val textY = (mHeight - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top
378 | if (saveResult != 0 || saveResult < results.size) {
379 | canvas.drawText(num, textX, textY, pswTextPaint)
380 | }
381 | }
382 |
383 | /**
384 | * 延迟delay时间后,将当前输入的明文密码绘制为圆点
385 | */
386 | private fun drawDelayCircle(canvas: Canvas, height: Int, dotRadius: Int) {
387 | invalidated = false
388 | if (pswAttrsBean.isChangeBorder()) {
389 | for (i in results.indices) {
390 | val circleX = (((i - 1) * (borderWidth + spacingWidth)).toDouble() + (borderWidth / 2).toDouble() + 0.5 * spacingWidth).toFloat()
391 | val circleY = (height / 2).toFloat()
392 | canvas.drawCircle(circleX, circleY, dotRadius.toFloat(), pswDotPaint)
393 | }
394 | canvas.drawCircle((((results.size - 1) * (borderWidth + spacingWidth) + borderWidth / 2).toFloat() + 0.5 * spacingWidth).toFloat(),
395 | (height / 2).toFloat(), dotRadius.toFloat(), pswDotPaint)
396 | } else {
397 | for (i in results.indices) {
398 | val circleX = (((i - 1) * (borderWidth + spacingWidth)).toDouble() + (borderWidth / 2).toDouble() + 0.5 * spacingWidth).toFloat()
399 | val circleY = (height / 2).toFloat()
400 | val left = (i * (borderWidth + spacingWidth) + 0.5 * spacingWidth).toInt()
401 | val right = (((i + 1) * borderWidth).toDouble() + (i * spacingWidth).toDouble() + 0.5 * spacingWidth).toInt()
402 | canvas.drawCircle(circleX, circleY, dotRadius.toFloat(), pswDotPaint)
403 | drawBitmapOrBorder(canvas, left, right, height)
404 | }
405 | canvas.drawCircle((((results.size - 1) * (borderWidth + spacingWidth) + borderWidth / 2).toFloat() + 0.5 * spacingWidth).toFloat(),
406 | (height / 2).toFloat(), dotRadius.toFloat(), pswDotPaint)
407 | }
408 | }
409 |
410 | /**
411 | * 是否使用图片绘制密码框
412 | */
413 | private fun drawBitmapOrBorder(canvas: Canvas, left: Int, right: Int, height: Int) {
414 | if (pswAttrsBean.isBorderImg()) {
415 | val bitmap = BitmapFactory.decodeResource(resources, pswAttrsBean.getInputBorderImg())
416 | val src = Rect(0, 0, bitmap.width, bitmap.height)
417 | val dst = Rect(left, (0 + borderPaint.strokeWidth).toInt(), right, (height - borderPaint.strokeWidth).toInt())
418 | canvas.drawBitmap(bitmap, src, dst, inputBorderPaint)
419 | bitmap.recycle()
420 | } else {
421 | borderRectF.set(left.toFloat(), 0 + borderPaint.strokeWidth, right.toFloat(), height - borderPaint.strokeWidth)
422 | canvas.drawRoundRect(borderRectF, pswAttrsBean.getBorderRadius().toFloat(), pswAttrsBean.getBorderRadius().toFloat(), inputBorderPaint)
423 | }
424 | }
425 |
426 | public fun getAttrBean(): PswAttrsBean {
427 | return pswAttrsBean
428 | }
429 |
430 | public fun setTextWatcher(textWatcher: TextWatcher) {
431 | this.textWatcher = textWatcher
432 | }
433 |
434 | public fun getTextWatcher(): TextWatcher? {
435 | return textWatcher
436 | }
437 |
438 | override fun onTouchEvent(event: MotionEvent): Boolean {
439 | if (event.action == MotionEvent.ACTION_DOWN) {//点击弹出键盘
440 | requestFocus()
441 | input?.showSoftInput(this, InputMethodManager.SHOW_FORCED)
442 | return true
443 | }
444 | return super.onTouchEvent(event)
445 | }
446 |
447 | override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
448 | super.onWindowFocusChanged(hasWindowFocus)
449 | if (!hasWindowFocus) {
450 | input?.hideSoftInputFromWindow(this.windowToken, 0)
451 | mHandler.removeCallbacksAndMessages(null)
452 | }
453 | }
454 |
455 | override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
456 | outAttrs.inputType = InputType.TYPE_CLASS_NUMBER//只允许输入数字
457 | outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE
458 | return NumInputConnection(this, false)
459 | }
460 |
461 | override fun onCheckIsTextEditor(): Boolean {
462 | return true
463 | }
464 | }
--------------------------------------------------------------------------------
/pswtext/src/main/java/rokudol/com/pswtext/TextWatcher.kt:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext
2 |
3 | interface TextWatcher {
4 | fun textChanged(password: String, isFinishInput: Boolean)
5 | }
--------------------------------------------------------------------------------
/pswtext/src/main/res/drawable/pic_dlzc_srk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/pswtext/src/main/res/drawable/pic_dlzc_srk.png
--------------------------------------------------------------------------------
/pswtext/src/main/res/drawable/pic_dlzc_srk1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rokudol/PswText/1eda41e2930b7d27d7bf37512b4727eded8a6421/pswtext/src/main/res/drawable/pic_dlzc_srk1.png
--------------------------------------------------------------------------------
/pswtext/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 |
--------------------------------------------------------------------------------
/pswtext/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PswText
3 |
4 |
--------------------------------------------------------------------------------
/pswtext/src/test/java/rokudol/com/pswtext/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package rokudol.com.pswtext;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':pswtext'
2 |
--------------------------------------------------------------------------------