├── app
├── .gitignore
├── img
│ ├── img01.jpg
│ ├── img02.jpg
│ ├── img03.jpg
│ └── img04.jpg
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── dimens.xml
│ │ │ ├── colors.xml
│ │ │ ├── style_fonts.xml
│ │ │ └── styles.xml
│ │ ├── drawable-xxhdpi
│ │ │ ├── clear_btn.png
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_nav_back.png
│ │ │ ├── ic_nav_share.png
│ │ │ ├── loading_bg.9.png
│ │ │ ├── pwd_eye_close.png
│ │ │ ├── pwd_eye_open.png
│ │ │ ├── bg_key_normal.9.png
│ │ │ ├── bg_key_pressed.9.png
│ │ │ ├── ic_launcher_round.png
│ │ │ ├── ic_delete_key_normal.png
│ │ │ ├── ic_delete_key_pressed.png
│ │ │ ├── img_keyboard_normal.9.png
│ │ │ ├── libcredit_send_sms_bg.png
│ │ │ └── img_keyboard_pressed.9.png
│ │ ├── drawable
│ │ │ ├── white_card_bg.xml
│ │ │ ├── btn_keyboard_key.xml
│ │ │ ├── cmb_checked_bg.xml
│ │ │ ├── kb_num_bg.xml
│ │ │ ├── bg_circle_red.xml
│ │ │ └── btn_bg.xml
│ │ ├── anim
│ │ │ ├── actionsheet_dialog_in.xml
│ │ │ └── actionsheet_dialog_out.xml
│ │ ├── layout
│ │ │ ├── activity_title_base.xml
│ │ │ ├── kb_amout_layout.xml
│ │ │ ├── customkeyboard.xml
│ │ │ ├── activity_main.xml
│ │ │ ├── find_paypwd_activity.xml
│ │ │ ├── paypwd_input_password_dialog.xml
│ │ │ ├── common_title.xml
│ │ │ └── c_edit_text.xml
│ │ └── xml
│ │ │ ├── kb_pwd.xml
│ │ │ ├── kb_idcert.xml
│ │ │ └── kb_number.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── weioule
│ │ └── inputkeyboarddemo
│ │ ├── MyApplication.java
│ │ ├── act
│ │ ├── SecondActivity.java
│ │ ├── MainActivity.java
│ │ ├── FindPayPwdActivity.java
│ │ └── BaseTitleActivity.java
│ │ ├── view
│ │ ├── AsyncImageView.java
│ │ ├── CEditTextSms.java
│ │ ├── CmbEditText.java
│ │ └── CEditText.java
│ │ ├── util
│ │ ├── CountDown.java
│ │ ├── AsyncImageLoaderProxy.java
│ │ ├── LoaderImpl.java
│ │ └── IdCardUtil.java
│ │ └── keyboard
│ │ ├── InputPayPwdDialog.java
│ │ ├── CustomKeyboardView.java
│ │ ├── InputView.java
│ │ └── CumKeyboardContainer.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── caches
│ └── build_file_checksums.ser
├── vcs.xml
├── runConfigurations.xml
├── gradle.xml
├── misc.xml
└── codeStyles
│ └── Project.xml
├── .gitignore
├── gradle.properties
├── README.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/app/img/img01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/img/img01.jpg
--------------------------------------------------------------------------------
/app/img/img02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/img/img02.jpg
--------------------------------------------------------------------------------
/app/img/img03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/img/img03.jpg
--------------------------------------------------------------------------------
/app/img/img04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/img/img04.jpg
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | InputKeyboardDemo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/clear_btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/clear_btn.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_nav_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/ic_nav_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_nav_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/ic_nav_share.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/loading_bg.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/loading_bg.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/pwd_eye_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/pwd_eye_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/pwd_eye_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/pwd_eye_open.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/bg_key_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/bg_key_normal.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/bg_key_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/bg_key_pressed.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_delete_key_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/ic_delete_key_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_delete_key_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/ic_delete_key_pressed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/img_keyboard_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/img_keyboard_normal.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/libcredit_send_sms_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/libcredit_send_sms_bg.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/img_keyboard_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weioule/KeyboardInputDemo/HEAD/app/src/main/res/drawable-xxhdpi/img_keyboard_pressed.9.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/white_card_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/actionsheet_dialog_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/actionsheet_dialog_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/btn_keyboard_key.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Nov 15 15:17:58 CST 2018
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-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/cmb_checked_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/kb_num_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_circle_red.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/btn_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 45.0dip
4 | 41.0dip
5 | 7.0dip
6 | 16dp
7 | 37.0dip
8 |
9 | 4dp
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_title_base.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
15 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #ffffffff
8 | #F6F6F6
9 |
10 | #ffdbe6ee
11 | #D2D5DB
12 | #4A4A4A
13 |
14 | #FD5E02
15 |
16 | #fd6e5f
17 | #fb5859
18 | #ffE01A38
19 | #FFB5B5B5
20 |
21 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/kb_amout_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/customkeyboard.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.example.weioule.inputkeyboarddemo"
7 | minSdkVersion 15
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation 'com.android.support:appcompat-v7:28.0.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * Author by weioule.
7 | * Date on 2018/11/19.
8 | */
9 | public class MyApplication extends Application {
10 | protected static MyApplication instance;
11 |
12 | @Override
13 | public void onCreate() {
14 | super.onCreate();
15 | instance = this;
16 | }
17 |
18 | public static MyApplication instance() {
19 | return instance;
20 | }
21 |
22 | public String getCachePath() {
23 | return "/Android/data/" + getPackageName() + "/cache/";
24 | }
25 |
26 | public String getCacheForeverPath() {
27 | return "/Android/data/" + getPackageName() + "/cache_forever/";
28 | }
29 |
30 | /**
31 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
32 | */
33 | public int dip2px(float dpValue) {
34 | final float scale = getResources().getDisplayMetrics().density;
35 | return (int) (dpValue * scale + 0.5f);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/res/values/style_fonts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
13 |
14 |
18 |
19 |
23 |
24 |
28 |
29 |
33 |
34 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/kb_pwd.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
12 |
15 |
19 |
20 |
21 |
24 |
27 |
31 |
32 |
33 |
36 |
39 |
43 |
44 |
45 |
48 |
51 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/kb_idcert.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
12 |
15 |
19 |
20 |
21 |
24 |
27 |
31 |
32 |
33 |
36 |
39 |
43 |
44 |
45 |
49 |
52 |
56 |
57 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.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 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
23 |
24 |
36 |
37 |
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/act/SecondActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.act;
2 |
3 | import android.content.Intent;
4 | import android.text.TextUtils;
5 | import android.view.View;
6 | import android.widget.Toast;
7 |
8 | import com.example.weioule.inputkeyboarddemo.keyboard.CumKeyboardContainer;
9 | import com.example.weioule.inputkeyboarddemo.keyboard.InputPayPwdDialog;
10 |
11 | /**
12 | * Author by weioule.
13 | * Date on 2018/11/19.
14 | */
15 | public class SecondActivity extends MainActivity {
16 |
17 | private InputPayPwdDialog inputPayPwdDialog;
18 |
19 | @Override
20 | protected String getInputType() {
21 | addTitleText("结算中心");
22 | mIndentityCard.setHit("请输入金额");
23 | mIndentityCard.setLeftText("金额");
24 | Intent intent = getIntent();
25 | mIdInfo.setText("性别:" + intent.getStringExtra(ID_INFO_SEX) + "\n" + "出生日期:" + intent.getStringExtra(ID_INFO_BIRTHDAY));
26 | mIdInfo.setVisibility(View.VISIBLE);
27 | okBtn.setText("确认支付");
28 | return CumKeyboardContainer.AMOUT_TYPE;
29 | }
30 |
31 | @Override
32 | protected void clickBtn() {
33 | if (TextUtils.isEmpty(mIndentityCard.getText())) {
34 | mIndentityCard.showErrNote("金额不能为空!");
35 | return;
36 | }
37 | showInputPaypwdDialog();
38 | hintKeyBoard();
39 | }
40 |
41 | private void showInputPaypwdDialog() {
42 | inputPayPwdDialog = new InputPayPwdDialog(this);
43 | inputPayPwdDialog.setInputPasswordListener(new InputPayPwdDialog.PasswordListener() {
44 | @Override
45 | public void onSubmitPwd(String password) {
46 | Toast.makeText(SecondActivity.this, "成功支付" + mIndentityCard.getText() + "元", Toast.LENGTH_SHORT).show();
47 | }
48 | });
49 | inputPayPwdDialog.show();
50 | }
51 |
52 | @Override
53 | public void onBackPressed() {
54 | if (hintKeyBoard()) return;
55 | finish();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/view/AsyncImageView.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.view;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.support.v7.widget.AppCompatImageView;
7 | import android.util.AttributeSet;
8 |
9 | import com.example.weioule.inputkeyboarddemo.util.AsyncImageLoaderProxy;
10 |
11 | /**
12 | * Author by weioule.
13 | * Date on 2018/11/19.
14 | */
15 | public class AsyncImageView extends AppCompatImageView {
16 | AsyncImageLoaderProxy loader;
17 |
18 | public AsyncImageView(Context context) {
19 | super(context);
20 | loader = new AsyncImageLoaderProxy(getContext().getApplicationContext());
21 | }
22 |
23 | public AsyncImageView(Context context, AttributeSet attrs) {
24 | super(context, attrs);
25 | loader = new AsyncImageLoaderProxy(getContext().getApplicationContext());
26 | }
27 |
28 | /**
29 | * 缓存文件到指定路径;当缓存超过5M时,将清空缓存
30 | */
31 | public void downloadCache2Sd(String url) {
32 | loader.downloadCache2Sd(url, new CallBack());
33 | }
34 |
35 | /**
36 | * 图片缓存到内存,只做soft缓存
37 | */
38 | public void downloadCache2memory(String url) {
39 | loader.downloadCache2memory(url, new CallBack());
40 | }
41 |
42 | /**
43 | * 缓存文件到指定路径;程序不会主动清除缓存
44 | */
45 | public void downloadCacheForever(String url) {
46 | loader.downloadCacheForever(url, new CallBack());
47 | }
48 |
49 | class CallBack implements AsyncImageLoaderProxy.ImageCallback {
50 | @Override
51 | public void onImageLoaded(Bitmap bitmap, String imageUrl) {
52 | if (mOnLoaderListener == null) {
53 | if (bitmap != null) {
54 | setImageBitmap(bitmap);
55 | }
56 | } else {
57 | mOnLoaderListener.loaded(bitmap);
58 | }
59 | }
60 | }
61 |
62 | public OnLoadedListener mOnLoaderListener;
63 |
64 | public void setLoadedListener(OnLoadedListener back) {
65 | this.mOnLoaderListener = mOnLoaderListener;
66 | }
67 |
68 | public interface OnLoadedListener {
69 | void loaded(Bitmap bitmap);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/kb_number.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
13 |
14 |
17 |
18 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
39 |
40 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
56 |
57 |
60 |
61 |
66 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
78 |
79 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/find_paypwd_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
27 |
28 |
33 |
34 |
46 |
47 |
48 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KeyboardInputDemo
2 | 这是一个仿支付宝自定义软键盘的demo,分别为身份证号码、支付金额、支付密码输入定制的软键盘,以及EditText文本输入框与密码输入框的定制。
3 |
4 | 先上效果图:
5 |
6 | 
7 | 
8 | 
9 | 
10 |
11 |
12 |
13 | 一 身份证键盘
14 | 1 身份证键盘在MainActivity页面,使用时再布局中添加配置
15 |
16 |
28 |
29 | 要使用自定义的键盘记得配置:app:show_custom_keyboard="true"
30 |
31 |
32 | 2 在Activity中创建 CumKeyboardContainer:
33 |
34 |
35 | mCustomKeyboardView = new CumKeyboardContainer(this, getInputType());
36 | mCustomKeyboardView.attachKeyBoardView();
37 |
38 |
39 |
40 | 3 并为输入框CEditText配置
41 |
42 | mIndentityCard = findViewById(R.id.indentity_card);
43 | mIndentityCard.setCustomKeyboardView(mCustomKeyboardView);
44 |
45 | 4 在onBackPressed中配置回退键盘,若键盘显示则先隐藏
46 |
47 | if (mCustomKeyboardView != null && mCustomKeyboardView.getVisibility() == View.VISIBLE) {
48 | mCustomKeyboardView.setCmbVisibility(View.GONE);
49 | }
50 | 二 数字键盘
51 |
52 | 数字键盘在SecondActivity页面,使用方法同上,只是在第二步中修改类型为金额输入:
53 |
54 | mCustomKeyboardView = new CumKeyboardContainer(this, CumKeyboardContainer.AMOUT_TYPE);
55 |
56 | 三 支付密码对话框
57 |
58 | 1 创建输入框和回调方法
59 |
60 | inputPayPwdDialog = new InputPayPwdDialog(this);
61 | inputPayPwdDialog.setInputPasswordListener(new InputPayPwdDialog.PasswordListener() {
62 | @Override
63 | public void onSubmitPwd(String password) {
64 | Toast.makeText(SecondActivity.this, "成功支付" + mIndentityCard.getText() + "元", Toast.LENGTH_SHORT).show();
65 | }
66 | });
67 |
68 | 2 在显示的时候调用
69 |
70 | inputPayPwdDialog.show();
71 |
72 | 3 获取密码
73 |
74 | inputPayPwdDialog.getPassword()
75 |
76 |
77 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/paypwd_input_password_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
21 |
22 |
28 |
29 |
30 |
31 |
35 |
36 |
45 |
46 |
57 |
58 |
62 |
63 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/view/CEditTextSms.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.Gravity;
6 | import android.view.View;
7 | import android.widget.TextView;
8 |
9 | import com.example.weioule.inputkeyboarddemo.util.CountDown;
10 | import com.example.weioule.inputkeyboarddemo.R;
11 |
12 |
13 | /**
14 | * 发送验证码控件
15 | * author weioule
16 | * Created on 2018/3/6.
17 | */
18 | public class CEditTextSms extends CEditText implements CountDown.OnCountDownListener {
19 | private TextView smsButton;
20 | private CountDown countDown;
21 | private SendSmsListener mSendSmsListener;
22 | private boolean externalStatus = true; //保存外部状态
23 |
24 | public CEditTextSms(Context context, AttributeSet attrs) {
25 | super(context, attrs);
26 | initSmsButton();
27 | initCountDown();
28 | }
29 |
30 | private void initSmsButton() {
31 | smsButton = findViewById(R.id.right_iamge);
32 | smsButton.setVisibility(View.VISIBLE);
33 | smsButton.setTextAppearance(getContext(), R.style.font_red_fd6e5f_15);
34 | smsButton.setGravity(Gravity.CENTER);
35 | smsButton.setText("发送验证码");
36 | smsButton.setOnClickListener(new OnClickListener() {
37 |
38 | @Override
39 | public void onClick(View paramView) {
40 | sendSms();
41 | }
42 | });
43 | }
44 |
45 | public void sendSms() {
46 | if (mSendSmsListener != null) {
47 | mSendSmsListener.onSendSms();
48 | }
49 | }
50 |
51 | //短信发送成功
52 | public void onSendSms() {
53 | countDown.runTime(CountDown.INTER_S);
54 | }
55 |
56 | private void initCountDown() {
57 | countDown = new CountDown(60);
58 | countDown.setListener(this);
59 | }
60 |
61 | @Override
62 | public void onCountDownFinished() {
63 | setSmsEnable(externalStatus);
64 | }
65 |
66 | @Override
67 | public void onCountDownRun(int maxNum, int remainNum) {
68 | smsButton.setText("" + remainNum + "s后重发");
69 | smsButton.setTextAppearance(getContext(), R.style.font_gray_9_15);
70 | smsButton.setEnabled(false);
71 | }
72 |
73 | public void setSendSmsListener(SendSmsListener mSendSmsListener) {
74 | this.mSendSmsListener = mSendSmsListener;
75 | }
76 |
77 | public void setSmsEnable(boolean isEnable) {
78 | externalStatus = isEnable;
79 | if (countDown.isRunning()) {
80 | return;
81 | }
82 |
83 | if (isEnable) {
84 | smsButton.setText("发送验证码");
85 | smsButton.setTextAppearance(getContext(), R.style.font_red_fd6e5f_15);
86 | smsButton.setEnabled(true);
87 | } else {
88 | smsButton.setText("发送验证码");
89 | smsButton.setTextAppearance(getContext(), R.style.font_red_fd6e5f_15);
90 | smsButton.setEnabled(false);
91 | }
92 |
93 | }
94 |
95 | public interface SendSmsListener {
96 | void onSendSms();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/util/CountDown.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.util;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 |
6 | import java.util.Timer;
7 | import java.util.TimerTask;
8 |
9 | public class CountDown {
10 | private static final int COUNT = 0000;
11 | private static final int FINISH = 0001;
12 | public static final int INTER_S = 1000;
13 | private int count;
14 | private int maxNum;
15 | private Timer timer;
16 | private TimerTask task;
17 | private OnCountDownListener listener;
18 |
19 | private Handler handler = new Handler() {
20 | @Override
21 | public synchronized void handleMessage(Message msg) {
22 | switch (msg.what) {
23 | case COUNT:
24 | if (listener != null) {
25 | listener.onCountDownRun(maxNum, maxNum - count);
26 | }
27 | break;
28 | case FINISH:
29 | if (listener != null) {
30 | listener.onCountDownFinished();
31 | }
32 | break;
33 | default:
34 | break;
35 | }
36 | }
37 | };
38 |
39 | public CountDown(int maxNum) {
40 | this.maxNum = maxNum;
41 | }
42 |
43 | public void runTime(int interval) {
44 | stopTime();
45 | count = 0;
46 | timer = new Timer(true);
47 | task = new TimerTask() {
48 | @Override
49 | public void run() {
50 | count++;
51 | if (maxNum - count <= 0) {
52 | stopTime();
53 | handler.sendEmptyMessage(FINISH);
54 | } else {
55 | handler.sendEmptyMessage(COUNT);
56 | }
57 | }
58 | };
59 | timer.scheduleAtFixedRate(task, 100, interval);
60 | }
61 |
62 | public void runTime(int interval, int delay) {
63 | stopTime();
64 | count = 0;
65 | timer = new Timer(true);
66 | task = new TimerTask() {
67 | @Override
68 | public void run() {
69 | count++;
70 | if (maxNum - count <= 0) {
71 | stopTime();
72 | handler.sendEmptyMessage(FINISH);
73 | } else {
74 | handler.sendEmptyMessage(COUNT);
75 | }
76 | }
77 | };
78 | timer.scheduleAtFixedRate(task, delay, interval);
79 | }
80 |
81 | public void stopTime() {
82 | if (task != null) {
83 | task.cancel();
84 | task = null;
85 | }
86 |
87 | if (timer != null) {
88 | timer.cancel();
89 | timer.purge();
90 | timer = null;
91 | }
92 | }
93 |
94 | public boolean isRunning() {
95 | return timer != null && task != null;
96 | }
97 |
98 | public void setListener(OnCountDownListener listener) {
99 | this.listener = listener;
100 | }
101 |
102 | public interface OnCountDownListener {
103 | void onCountDownFinished();
104 |
105 | void onCountDownRun(int maxNum, int remainNum);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
63 |
64 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/common_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
22 |
23 |
38 |
39 |
50 |
51 |
63 |
64 |
74 |
75 |
88 |
89 |
96 |
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/act/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.act;
2 |
3 | import android.content.Intent;
4 | import android.view.View;
5 | import android.widget.TextView;
6 | import android.widget.Toast;
7 |
8 | import com.example.weioule.inputkeyboarddemo.R;
9 | import com.example.weioule.inputkeyboarddemo.keyboard.CumKeyboardContainer;
10 | import com.example.weioule.inputkeyboarddemo.util.IdCardUtil;
11 | import com.example.weioule.inputkeyboarddemo.view.CEditText;
12 |
13 | import java.util.Timer;
14 | import java.util.TimerTask;
15 |
16 | /**
17 | * Author by weioule.
18 | * Date on 2018/11/19.
19 | */
20 | public class MainActivity extends BaseTitleActivity {
21 |
22 | protected static final String ID_INFO_BIRTHDAY = "id_info_birthday";
23 | protected static final String ID_INFO_SEX = "id_info_sex";
24 | protected CumKeyboardContainer mCustomKeyboardView;
25 | private boolean mBackKeyPressed = false;
26 | protected CEditText mIndentityCard;
27 | protected TextView mIdInfo, okBtn;
28 |
29 | @Override
30 | protected void initView() {
31 | addTitleText("实名认证");
32 | mIndentityCard = findViewById(R.id.indentity_card);
33 | mIdInfo = findViewById(R.id.input_id_info);
34 | okBtn = findViewById(R.id.ok_button);
35 |
36 | mCustomKeyboardView = new CumKeyboardContainer(this, getInputType());
37 | mCustomKeyboardView.attachKeyBoardView();
38 | mIndentityCard.setCustomKeyboardView(mCustomKeyboardView);
39 |
40 | okBtn.setOnClickListener(new View.OnClickListener() {
41 | @Override
42 | public void onClick(View v) {
43 | clickBtn();
44 | }
45 | });
46 | findViewById(R.id.content).setOnClickListener(new View.OnClickListener() {
47 | @Override
48 | public void onClick(View v) {
49 | hintKeyBoard();
50 | }
51 | });
52 | }
53 |
54 | protected void clickBtn() {
55 | IdCardUtil idCardUtil = new IdCardUtil(mIndentityCard.getText());
56 | int correct = idCardUtil.isCorrect();
57 | if (0 == correct) {
58 | Intent intent = new Intent(MainActivity.this, SecondActivity.class);
59 | intent.putExtra(ID_INFO_BIRTHDAY, idCardUtil.getBirthday());
60 | intent.putExtra(ID_INFO_SEX, idCardUtil.getSex());
61 | startActivity(intent);
62 | } else {
63 | mIndentityCard.showErrNote(idCardUtil.getErrMsg());
64 | }
65 | }
66 |
67 | @Override
68 | protected void initData() {
69 | }
70 |
71 | @Override
72 | protected int getLayoutId() {
73 | return R.layout.activity_main;
74 | }
75 |
76 | protected boolean hintKeyBoard() {
77 | if (mCustomKeyboardView != null && mCustomKeyboardView.getVisibility() == View.VISIBLE) {
78 | mCustomKeyboardView.setCmbVisibility(View.GONE);
79 | return true;
80 | }
81 | return false;
82 | }
83 |
84 | protected String getInputType() {
85 | return CumKeyboardContainer.IDCERT_TYPE;
86 | }
87 |
88 | @Override
89 | public void onBackPressed() {
90 | if (hintKeyBoard()) return;
91 | if (!mBackKeyPressed) {
92 | Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
93 | mBackKeyPressed = true;
94 | new Timer().schedule(new TimerTask() {// 延时两秒,如果超出则取消取消上一次的记录
95 |
96 | @Override
97 | public void run() {
98 | mBackKeyPressed = false;
99 | }
100 | }, 2000);
101 | } else {// 退出程序
102 | this.finish();
103 | System.exit(0);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/act/FindPayPwdActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.act;
2 |
3 | import android.text.TextUtils;
4 | import android.view.View;
5 | import android.view.inputmethod.EditorInfo;
6 | import android.widget.TextView;
7 | import android.widget.Toast;
8 |
9 | import com.example.weioule.inputkeyboarddemo.R;
10 | import com.example.weioule.inputkeyboarddemo.view.CEditText;
11 | import com.example.weioule.inputkeyboarddemo.view.CEditTextSms;
12 |
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 |
16 |
17 | /**
18 | * author weioule
19 | * Created on 2018/3/6.
20 | */
21 | public class FindPayPwdActivity extends BaseTitleActivity implements View.OnClickListener, CEditTextSms.SendSmsListener {
22 |
23 | private CEditTextSms mVerificationCode;
24 | private CEditText mEtphone;
25 | private TextView mSubmit;
26 |
27 | @Override
28 | protected void initView() {
29 | addTitleText("找回密码");
30 | mEtphone = findViewById(R.id.et_phone);
31 | mVerificationCode = findViewById(R.id.et_verification_code);
32 | mVerificationCode.setIme(EditorInfo.IME_ACTION_DONE, "");
33 | mVerificationCode.setSendSmsListener(this);
34 | mSubmit = findViewById(R.id.submit_btn);
35 | mSubmit.setOnClickListener(this);
36 | }
37 |
38 | @Override
39 | protected void initData() {
40 | }
41 |
42 | @Override
43 | protected int getLayoutId() {
44 | return R.layout.find_paypwd_activity;
45 | }
46 |
47 | @Override
48 | public void onClick(View v) {
49 | if (TextUtils.isEmpty(mVerificationCode.getText())) {
50 | mVerificationCode.showErrNote("验证码为空!");
51 | } else if (checkSmsCode(mVerificationCode.getText())) {
52 | Toast.makeText(this, "密码找回成功", Toast.LENGTH_SHORT).show();
53 | finish();
54 | } else {
55 | mVerificationCode.showErrNote("验证码有误!");
56 | }
57 | }
58 |
59 | @Override
60 | public void onSendSms() {
61 | if (TextUtils.isEmpty(mEtphone.getText())) {
62 | mEtphone.showErrNote("手机号为空!");
63 | } else if (isMobileNO(mEtphone.getText())) {
64 | /**模拟发送验证码成功,开启倒计时*/
65 | mVerificationCode.onSendSms();
66 | } else {
67 | mEtphone.showErrNote("手机号不正确");
68 | }
69 | }
70 |
71 | protected void onCheckButtonStatusChanged() {
72 | String verificationCode = mEtphone.getText().trim();
73 | if (!TextUtils.isEmpty(verificationCode)) {
74 | mSubmit.setEnabled(true);
75 | } else {
76 | mSubmit.setEnabled(false);
77 | }
78 | }
79 |
80 | public static boolean isMobileNO(String mobileNums) {
81 | /**
82 | * 判断字符串是否符合手机号码格式
83 | * 移动号段: 134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188
84 | * 联通号段: 130,131,132,145,155,156,170,171,175,176,185,186
85 | * 电信号段: 133,149,153,170,173,177,180,181,189
86 | * @param str
87 | * @return 待检测的字符串
88 | */
89 | // "[1]"代表下一位为数字可以是几,"[0-9]"代表可以为0-9中的一个,"[5,7,9]"表示可以是5,7,9中的任意一位,[^4]表示除4以外的任何一个,\\d{9}"代表后面是可以是0~9的数字,有9位。
90 | String telRegex = "^((13[0-9])|(14[5,7,9])|(15[^4])|(18[0-9])|(17[0,1,3,5,6,7,8]))\\d{8}$";
91 | if (TextUtils.isEmpty(mobileNums))
92 | return false;
93 | else
94 | return mobileNums.matches(telRegex);
95 | }
96 |
97 | /**
98 | * 验证验证码
99 | *
100 | * @param smsCode
101 | * @return
102 | */
103 | public static boolean checkSmsCode(String smsCode) {
104 | if (smsCode == null || "".equals(smsCode)) {
105 | return false;
106 | }
107 | Pattern p = Pattern.compile("^([0-9]{6})$");// ^([0-9]{6})$,^\\d{6,8}
108 | Matcher m = p.matcher(smsCode);
109 | return m.matches();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/view/CmbEditText.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.view;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.text.InputType;
7 | import android.util.AttributeSet;
8 | import android.view.MotionEvent;
9 | import android.view.View;
10 | import android.view.WindowManager;
11 | import android.view.inputmethod.InputMethodManager;
12 | import android.widget.EditText;
13 |
14 | import com.example.weioule.inputkeyboarddemo.keyboard.CumKeyboardContainer;
15 |
16 | import java.lang.reflect.InvocationTargetException;
17 | import java.lang.reflect.Method;
18 |
19 |
20 | /**
21 | * Author by weioule.
22 | * Date on 2018/11/19.
23 | */
24 | @SuppressLint("AppCompatCustomView")
25 | public class CmbEditText extends EditText implements View.OnFocusChangeListener, View.OnTouchListener {
26 | private CumKeyboardContainer mCmbView;
27 | private boolean mIsCustomKeyboardEnable = false;
28 |
29 | public CmbEditText(Context context, AttributeSet attrs) {
30 | super(context, attrs);
31 | }
32 |
33 | public void setCustomKeyboardEnable(boolean isCustomKeyboardEnable) {
34 | mIsCustomKeyboardEnable = isCustomKeyboardEnable;
35 |
36 | if (mIsCustomKeyboardEnable) {
37 | noRelateIM();
38 |
39 | this.setOnFocusChangeListener(this);
40 | this.setOnTouchListener(this);
41 | }
42 | }
43 |
44 | public void setCmbView(CumKeyboardContainer cmbView) {
45 | if (!mIsCustomKeyboardEnable) {
46 | return;
47 | }
48 | mCmbView = cmbView;
49 | }
50 |
51 | public void showCMBKeyboardWindow() {
52 | if (mIsCustomKeyboardEnable) {
53 | //显示自定义键盘
54 | hideIM();
55 | setFocusable(true);
56 | requestFocus();
57 | setCursorVisible(true);
58 |
59 | mCmbView.setEditText(this);
60 | mCmbView.setCmbVisibility(View.VISIBLE);
61 | } else {
62 | //显示系统键盘
63 | setFocusable(true);
64 | setFocusableInTouchMode(true);
65 | requestFocus();
66 | InputMethodManager inputManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
67 | inputManager.showSoftInput(this, 0);
68 | }
69 | }
70 |
71 | @Override
72 | public void onFocusChange(View v, boolean hasFocus) {
73 | if (!hasFocus) {
74 | closeCMBKeyboardWindow();
75 | }
76 | }
77 |
78 | @Override
79 | public boolean onTouch(View v, MotionEvent event) {
80 | showCMBKeyboardWindow();
81 | return false;
82 | }
83 |
84 | private void closeCMBKeyboardWindow() {
85 |
86 | }
87 |
88 | private void hideIM() {
89 | if (getWindowToken() != null) {
90 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
91 | imm.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
92 | }
93 | }
94 |
95 | private void noRelateIM() {
96 | ((Activity) getContext()).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
97 |
98 | int currentVersion = android.os.Build.VERSION.SDK_INT;
99 | String methodName = null;
100 | if (currentVersion >= 16) {
101 | // 4.2
102 | methodName = "setShowSoftInputOnFocus";
103 | } else if (currentVersion >= 14) {
104 | // 4.0
105 | methodName = "setSoftInputShownOnFocus";
106 | }
107 |
108 | if (methodName == null) {
109 | this.setInputType(InputType.TYPE_NULL);
110 | } else {
111 | Class cls = EditText.class;
112 | Method setShowSoftInputOnFocus;
113 | try {
114 | setShowSoftInputOnFocus = cls.getMethod(methodName, boolean.class);
115 | setShowSoftInputOnFocus.setAccessible(true);
116 | setShowSoftInputOnFocus.invoke(this, false);
117 | } catch (NoSuchMethodException e) {
118 | this.setInputType(InputType.TYPE_NULL);
119 | e.printStackTrace();
120 | } catch (IllegalAccessException e) {
121 | // TODO Auto-generated catch block
122 | e.printStackTrace();
123 | } catch (IllegalArgumentException e) {
124 | // TODO Auto-generated catch block
125 | e.printStackTrace();
126 | } catch (InvocationTargetException e) {
127 | // TODO Auto-generated catch block
128 | e.printStackTrace();
129 | }
130 | }
131 | }
132 |
133 | @Override
134 | protected void onDetachedFromWindow() {
135 | super.onDetachedFromWindow();
136 | mCmbView = null;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/c_edit_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
24 |
25 |
34 |
35 |
47 |
48 |
60 |
61 |
70 |
71 |
83 |
84 |
96 |
97 |
108 |
109 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/util/AsyncImageLoaderProxy.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.util;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.os.Environment;
6 | import android.os.Handler;
7 |
8 |
9 | import com.example.weioule.inputkeyboarddemo.MyApplication;
10 |
11 | import java.lang.ref.SoftReference;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 | import java.util.concurrent.ExecutorService;
15 | import java.util.concurrent.Executors;
16 |
17 | /**
18 | * Author by weioule.
19 | * Date on 2018/11/19.
20 | */
21 | public class AsyncImageLoaderProxy {
22 | // 软引用内存缓存
23 | private static Map> sImageCache;
24 | // 图片三种获取方式管理者,网络URL获取、内存缓存获取、外部文件缓存获取
25 | private static LoaderImpl impl;
26 | // 图片获取完成通知UI线程
27 | private Handler handler;
28 | // 线程池相关
29 | private static ExecutorService sExecutorService;
30 |
31 | /**
32 | * 异步加载图片完毕的回调接口
33 | */
34 | public interface ImageCallback {
35 | /**
36 | * 回调函数
37 | *
38 | * @param bitmap : may be null!
39 | * @param imageUrl
40 | */
41 | void onImageLoaded(Bitmap bitmap, String imageUrl);
42 | }
43 |
44 | static {
45 | sImageCache = new HashMap<>();
46 | impl = new LoaderImpl(sImageCache);
47 | }
48 |
49 | public AsyncImageLoaderProxy(Context context) {
50 | handler = new Handler();
51 | startThreadPoolIfNecessary();
52 |
53 | String defaultDir = context.getCacheDir().getAbsolutePath();
54 | setCachedDir(defaultDir);
55 | }
56 |
57 | /**
58 | * 是否缓存图片至文件系统 默认不缓存
59 | */
60 | public void setCache2File(boolean flag) {
61 | impl.setCache2File(flag);
62 | }
63 |
64 | /**
65 | * 设置缓存路径,setCache2File(true)时有效
66 | */
67 | public void setCachedDir(String dir) {
68 | impl.setCachedDir(dir);
69 | }
70 |
71 | /**
72 | * 开启线程池
73 | */
74 | public static void startThreadPoolIfNecessary() {
75 | if (sExecutorService == null || sExecutorService.isShutdown()
76 | || sExecutorService.isTerminated()) {
77 | sExecutorService = Executors.newFixedThreadPool(3);
78 | }
79 | }
80 |
81 | /**
82 | * 异步下载图片,并缓存到memory中
83 | *
84 | * @param url
85 | * @param callback see ImageCallback interface
86 | */
87 | public void downloadImage(final String url, final ImageCallback callback) {
88 | downloadImage(url, true, callback);
89 | }
90 |
91 | /**
92 | * 缓存文件到指定路径;当缓存超过5M时,将清空缓存
93 | */
94 | public void downloadCache2Sd(final String url, final ImageCallback callback) {
95 | impl.setCachedDir(Environment.getExternalStorageDirectory() + MyApplication.instance().getCachePath());
96 | impl.setCache2File(true);
97 | downloadImage(url, true, callback);
98 | }
99 |
100 | /**
101 | * 缓存文件到指定路径;程序不会主动清除缓存
102 | */
103 | public void downloadCacheForever(final String url, final ImageCallback callback) {
104 | impl.setCachedDir(Environment.getExternalStorageDirectory() + MyApplication.instance().getCacheForeverPath());
105 | impl.setCache2File(true);
106 | downloadImage(url, true, callback);
107 | }
108 |
109 | /**
110 | * 图片缓存到内存,只做soft缓存
111 | */
112 | public void downloadCache2memory(final String url, final ImageCallback callback) {
113 | impl.setCache2File(false);
114 | downloadImage(url, true, callback);
115 | }
116 |
117 | /**
118 | * @param url
119 | * @param cache2Memory 是否缓存至memory中
120 | * @param callback
121 | */
122 | public void downloadImage(final String url, final boolean cache2Memory,
123 | final ImageCallback callback) {
124 | Bitmap bitmap = impl.getBitmapFromMemory(url);
125 | if (bitmap != null) {
126 | if (callback != null) {
127 | callback.onImageLoaded(bitmap, url);
128 | }
129 | } else {
130 | // 从网络端下载图片
131 | sExecutorService.submit(new Runnable() {
132 | @Override
133 | public void run() {
134 | synchronized (sExecutorService) {
135 | final Bitmap bitmap = impl.getBitmapFromUrl(url,
136 | cache2Memory);
137 | handler.post(new Runnable() {
138 | @Override
139 | public void run() {
140 | if (callback != null) {
141 | callback.onImageLoaded(bitmap, url);
142 | }
143 | }
144 | });
145 | }
146 | }
147 | });
148 | }
149 | }
150 |
151 | /**
152 | * 预加载下一张图片,缓存至memory中
153 | *
154 | * @param url
155 | */
156 | public void preLoadNextImage(final String url) {
157 | // 将callback置为空,只将bitmap缓存到memory即可。
158 | downloadImage(url, null);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/act/BaseTitleActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.act;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.graphics.drawable.Drawable;
6 | import android.net.ConnectivityManager;
7 | import android.net.NetworkInfo;
8 | import android.os.Build;
9 | import android.os.Bundle;
10 | import android.support.annotation.DrawableRes;
11 | import android.support.annotation.NonNull;
12 | import android.support.annotation.Nullable;
13 | import android.support.v7.app.AppCompatActivity;
14 | import android.view.LayoutInflater;
15 | import android.view.View;
16 | import android.view.ViewGroup;
17 | import android.view.WindowManager;
18 | import android.widget.ImageView;
19 | import android.widget.RelativeLayout;
20 | import android.widget.TextView;
21 |
22 | import com.example.weioule.inputkeyboarddemo.R;
23 |
24 |
25 | /**
26 | * Author by weioule.
27 | * Date on 2018/10/29.
28 | */
29 | @SuppressWarnings({"unchecked", "deprecation"})
30 | public abstract class BaseTitleActivity extends AppCompatActivity {
31 |
32 | protected RelativeLayout titleBar;
33 | private ImageView mRightImage;
34 | private ImageView mLeftImage;
35 | private TextView mRightText;
36 | private TextView mTitleText;
37 | private TextView mLeftText;
38 |
39 | @Override
40 | protected void onCreate(@Nullable Bundle savedInstanceState) {
41 | super.onCreate(savedInstanceState);
42 | setContentView(R.layout.activity_title_base);
43 |
44 | titleBar = findViewById(R.id.title_layout);
45 | mTitleText = titleBar.findViewById(R.id.title_tv_title);
46 | mLeftImage = titleBar.findViewById(R.id.title_iv_left);
47 | mLeftText = titleBar.findViewById(R.id.title_tv_left);
48 | mRightImage = titleBar.findViewById(R.id.title_iv_right);
49 | mRightText = titleBar.findViewById(R.id.title_tv_right);
50 |
51 | mLeftImage.setVisibility(View.VISIBLE);
52 | mLeftImage.setOnClickListener(new View.OnClickListener() {
53 | @Override
54 | public void onClick(View view) {
55 | finish();
56 | }
57 | });
58 |
59 | int body = this.getLayoutId();
60 | if (body != 0) {
61 | LayoutInflater.from(this).inflate(body, (ViewGroup) findViewById(R.id.container), true);
62 | }
63 | if (Build.VERSION.SDK_INT >= 21) {
64 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
65 | }
66 | initView();
67 | initData();
68 | }
69 |
70 | protected abstract void initView();
71 |
72 | protected abstract void initData();
73 |
74 | protected abstract int getLayoutId();
75 |
76 | protected TextView addLeftText(String text) {
77 | mLeftText.setText(text);
78 | return mLeftText;
79 | }
80 |
81 | protected void addLeftImage(@DrawableRes int resid) {
82 | addLeftImage(getResources().getDrawable(resid));
83 | }
84 |
85 | protected ImageView addLeftImage(Drawable drawable) {
86 | mLeftImage.setImageDrawable(drawable);
87 | return mLeftImage;
88 | }
89 |
90 | protected void addTitleText(String text) {
91 | mTitleText.setText(text);
92 | }
93 |
94 | protected void addRightImage(@DrawableRes int resid) {
95 | addRightImage(getResources().getDrawable(resid));
96 | }
97 |
98 | protected ImageView addRightImage(Drawable drawable) {
99 | mRightImage.setImageDrawable(drawable);
100 | mRightImage.setVisibility(View.VISIBLE);
101 | return mLeftImage;
102 | }
103 |
104 | protected TextView addRightText(String text) {
105 | mRightText.setText(text);
106 | return mRightText;
107 | }
108 |
109 | protected void forwardAndFinish(Class> cls) {
110 | forward(cls);
111 | finish();
112 | }
113 |
114 | protected void forward(Class> cls) {
115 | startActivity(new Intent(this, cls));
116 | }
117 |
118 | /**
119 | * 获取导航栏高度
120 | *
121 | * @param context
122 | * @return
123 | */
124 | protected int getNavigationBarHeight(Context context) {
125 | int resourceId;
126 | int rid = context.getResources().getIdentifier("config_showNavigationBar", "bool", "android");
127 | if (rid != 0) {
128 | resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
129 | return context.getResources().getDimensionPixelSize(resourceId);
130 | } else
131 | return 0;
132 | }
133 |
134 | /**
135 | * 判断网络
136 | */
137 | public boolean isNetworkAvailable(@NonNull Context context) {
138 | ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
139 | if (connectivity == null) {
140 | return false;
141 | } else {
142 | NetworkInfo[] info = connectivity.getAllNetworkInfo();
143 | if (info != null) {
144 | for (int i = 0; i < info.length; i++) {
145 | if (info[i].getState() == NetworkInfo.State.CONNECTED) {
146 | return true;
147 | }
148 | }
149 | }
150 | }
151 | return false;
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/keyboard/InputPayPwdDialog.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.keyboard;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.inputmethodservice.Keyboard;
7 | import android.inputmethodservice.KeyboardView;
8 | import android.os.Build;
9 | import android.support.annotation.RequiresApi;
10 | import android.view.Display;
11 | import android.view.Gravity;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.Window;
15 | import android.view.WindowManager;
16 |
17 | import com.example.weioule.inputkeyboarddemo.act.FindPayPwdActivity;
18 | import com.example.weioule.inputkeyboarddemo.R;
19 |
20 |
21 | /**
22 | * author weioule
23 | * Create on 2018/3/13.
24 | */
25 | public class InputPayPwdDialog implements View.OnClickListener, KeyboardView.OnKeyboardActionListener {
26 | private Context context;
27 | private View inflate;
28 | private Dialog dialog;
29 | private int mOldEditLenght;
30 | private InputView mEditPay;
31 | private PasswordListener listener;
32 | private CustomKeyboardView mKeyborView;
33 |
34 | public InputPayPwdDialog(Context context) {
35 | this.context = context;
36 | dialog = new Dialog(context, R.style.ActionSheetDialogStyle);
37 | inflate = LayoutInflater.from(context).inflate(R.layout.paypwd_input_password_dialog, null);
38 | mEditPay = inflate.findViewById(R.id.edit_pay);
39 | mKeyborView = inflate.findViewById(R.id.view_keyboard);
40 | mKeyborView.setOnKeyboardActionListener(this);
41 | // 设置软键盘按键的布局
42 | Keyboard mNumberKeyboard = new Keyboard(context, R.xml.kb_pwd);
43 | mKeyborView.setKeyboard(mNumberKeyboard);
44 | inflate.findViewById(R.id.forget_pwd).setOnClickListener(this);
45 | dialog.setContentView(inflate);
46 | dialog.setCanceledOnTouchOutside(false);
47 | dialog.findViewById(R.id.paypwd_close).setOnClickListener(new View.OnClickListener() {
48 | @Override
49 | public void onClick(View v) {
50 | dismiss();
51 | }
52 | });
53 |
54 | //获取当前Activity所在的窗体
55 | Window dialogWindow = dialog.getWindow();
56 | //设置Dialog从窗体底部弹出
57 | dialogWindow.setGravity(Gravity.BOTTOM);
58 | //获得窗体的属性
59 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
60 | Display d = windowManager.getDefaultDisplay();
61 | WindowManager.LayoutParams lp = dialogWindow.getAttributes();
62 | //将属性设置给窗体
63 | lp.width = d.getWidth();
64 | dialogWindow.setAttributes(lp);
65 | }
66 |
67 | public void show() {
68 | mEditPay.setText("");
69 | dialog.show();
70 | }
71 |
72 | public void dismiss() {
73 | dialog.dismiss();
74 | }
75 |
76 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
77 | @Override
78 | public void onClick(View view) {
79 | int i = view.getId();
80 | if (i == R.id.forget_pwd) {
81 | context.startActivity(new Intent(context, FindPayPwdActivity.class));
82 | mKeyborView.post(new Runnable() {
83 | @Override
84 | public void run() {
85 | dismiss();
86 | }
87 | });
88 | }
89 | }
90 |
91 | public void onInsertKeyEvent(String text) {
92 | mOldEditLenght = mEditPay.length();
93 | mEditPay.append(text);
94 | //输入好密码时回调密码值
95 | if (mOldEditLenght < 6 && mEditPay.length() == 6) {
96 | if (null != listener) {
97 | String password = mEditPay.getText().toString().trim();
98 | listener.onSubmitPwd(password);
99 | mKeyborView.post(new Runnable() {
100 | @Override
101 | public void run() {
102 | dismiss();//支付密码错误的时候,直接弹框, 键盘不隐藏
103 | }
104 | });
105 | }
106 | }
107 | }
108 |
109 | public void onDeleteKeyEvent() {
110 | int start = mEditPay.length();
111 | if (start > 0) {
112 | mEditPay.getText().delete(start - 1, start);
113 | }
114 | }
115 |
116 | public void setInputPasswordListener(PasswordListener listener) {
117 | this.listener = listener;
118 | }
119 |
120 | public String getPassword() {
121 | return mEditPay.getText().toString().trim();
122 | }
123 |
124 | @Override
125 | public void onPress(int primaryCode) {
126 |
127 | }
128 |
129 | @Override
130 | public void onRelease(int primaryCode) {
131 |
132 | }
133 |
134 | @Override
135 | public void onKey(int primaryCode, int[] keyCodes) {
136 | if (primaryCode == Keyboard.KEYCODE_DELETE) {
137 | onDeleteKeyEvent();
138 | }
139 | // 点击了数字按键
140 | else if (primaryCode != CustomKeyboardView.KEYCODE_EMPTY) {
141 | onInsertKeyEvent(Character.toString(
142 | (char) primaryCode));
143 | }
144 | }
145 |
146 | @Override
147 | public void onText(CharSequence text) {
148 |
149 | }
150 |
151 | @Override
152 | public void swipeLeft() {
153 |
154 | }
155 |
156 | @Override
157 | public void swipeRight() {
158 |
159 | }
160 |
161 | @Override
162 | public void swipeDown() {
163 |
164 | }
165 |
166 | @Override
167 | public void swipeUp() {
168 |
169 | }
170 |
171 | public interface PasswordListener {
172 | void onSubmitPwd(String password);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/util/LoaderImpl.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.util;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.BitmapFactory;
5 |
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.io.FileNotFoundException;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.io.InputStream;
12 | import java.io.UnsupportedEncodingException;
13 | import java.lang.ref.SoftReference;
14 | import java.net.HttpURLConnection;
15 | import java.net.URL;
16 | import java.security.MessageDigest;
17 | import java.security.NoSuchAlgorithmException;
18 | import java.util.Map;
19 |
20 | /**
21 | * Author by weioule.
22 | * Date on 2018/11/19.
23 | */
24 | public class LoaderImpl {
25 | // 内存中的软应用缓存
26 | private Map> imageCache;
27 | // 是否缓存图片至本地文件
28 | private boolean cache2FileFlag = false;
29 | private String cachedDir;
30 |
31 | public LoaderImpl(Map> imageCache) {
32 | this.imageCache = imageCache;
33 | }
34 |
35 | /**
36 | * 是否缓存图片至外部文件
37 | *
38 | * @param flag
39 | */
40 | public void setCache2File(boolean flag) {
41 | cache2FileFlag = flag;
42 | }
43 |
44 | /**
45 | * 设置缓存图片到外部文件的路径
46 | *
47 | * @param cacheDir
48 | */
49 | public void setCachedDir(String cacheDir) {
50 | this.cachedDir = cacheDir;
51 | }
52 |
53 | /**
54 | * 从网络端下载图片
55 | *
56 | * @param url 网络图片的URL地址
57 | * @param cache2Memory 是否缓存(缓存在内存中)
58 | * @return bitmap 图片bitmap结构
59 | */
60 | public Bitmap getBitmapFromUrl(String url, boolean cache2Memory) {
61 | Bitmap bitmap = null;
62 | try {
63 | URL u = new URL(url);
64 | HttpURLConnection conn = (HttpURLConnection) u.openConnection();
65 | if (conn.getResponseCode() == 200) {
66 | InputStream is = conn.getInputStream();
67 | bitmap = BitmapFactory.decodeStream(is);
68 | if (bitmap == null) {
69 | return bitmap;
70 | }
71 |
72 | if (cache2Memory) {//缓存bitmap至内存软引用中
73 | imageCache.put(url, new SoftReference(bitmap));
74 | }
75 | if (cache2FileFlag) {//缓存bitmap至文件系统
76 | String fileName = getMD5Str(url);
77 | String filePath = this.cachedDir + fileName;
78 | File file = new File(filePath);
79 | if (!file.getParentFile().exists()) {
80 | file.getParentFile().mkdirs();
81 | }
82 | FileOutputStream fos = new FileOutputStream(filePath);
83 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
84 | }
85 | is.close();
86 | conn.disconnect();
87 | return bitmap;
88 | }
89 | return null;
90 | } catch (IOException e) {
91 | e.printStackTrace();
92 | return null;
93 | }
94 | }
95 |
96 | /**
97 | * 从内存缓存中获取bitmap
98 | *
99 | * @param url
100 | * @return bitmap or null.
101 | */
102 | public Bitmap getBitmapFromMemory(String url) {
103 | Bitmap bitmap = null;
104 | if (imageCache.containsKey(url)) {
105 | synchronized (imageCache) {
106 | SoftReference bitmapRef = imageCache.get(url);
107 | if (bitmapRef != null) {
108 | bitmap = bitmapRef.get();
109 | return bitmap;
110 | }
111 | }
112 | }
113 | // 从外部缓存文件读取
114 | if (cache2FileFlag) {
115 | bitmap = getBitmapFromFile(url);
116 | if (bitmap != null) {
117 | imageCache.put(url, new SoftReference<>(bitmap));
118 | }
119 | }
120 |
121 | return bitmap;
122 | }
123 |
124 | /**
125 | * 从外部文件缓存中获取bitmap
126 | *
127 | * @param url
128 | * @return
129 | */
130 | private Bitmap getBitmapFromFile(String url) {
131 | Bitmap bitmap = null;
132 | String fileName = getMD5Str(url);
133 | if (fileName == null) {
134 | return null;
135 | }
136 |
137 | String filePath = cachedDir + fileName;
138 |
139 | try {
140 | File file = new File(filePath);
141 | if (file.exists()) {
142 | FileInputStream fis = new FileInputStream(filePath);
143 | bitmap = BitmapFactory.decodeStream(fis);
144 | }
145 | } catch (FileNotFoundException e) {
146 | e.printStackTrace();
147 | bitmap = null;
148 | }
149 | return bitmap;
150 | }
151 |
152 | /**
153 | * MD5 加密
154 | */
155 | public static String getMD5Str(String str) {
156 | MessageDigest messageDigest = null;
157 | try {
158 | messageDigest = MessageDigest.getInstance("MD5");
159 | messageDigest.reset();
160 | messageDigest.update(str.getBytes("UTF-8"));
161 | } catch (NoSuchAlgorithmException e) {
162 | return null;
163 | } catch (UnsupportedEncodingException e) {
164 | return null;
165 | }
166 |
167 | byte[] byteArray = messageDigest.digest();
168 | StringBuffer md5StrBuff = new StringBuffer();
169 | for (int i = 0; i < byteArray.length; i++) {
170 | if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) {
171 | md5StrBuff.append("0").append(
172 | Integer.toHexString(0xFF & byteArray[i]));
173 | } else {
174 | md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
175 | }
176 | }
177 |
178 | return md5StrBuff.toString();
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/keyboard/CustomKeyboardView.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.keyboard;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Rect;
8 | import android.graphics.Typeface;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.graphics.drawable.Drawable;
11 | import android.inputmethodservice.Keyboard;
12 | import android.inputmethodservice.KeyboardView;
13 | import android.util.AttributeSet;
14 |
15 | import com.example.weioule.inputkeyboarddemo.R;
16 | import com.example.weioule.inputkeyboarddemo.util.IdCardUtil;
17 |
18 | import java.lang.reflect.Field;
19 | import java.util.List;
20 |
21 | /**
22 | * Author by weioule.
23 | * Date on 2018/11/19.
24 | */
25 | public class CustomKeyboardView extends KeyboardView {
26 | // 用于区分左下角空白的按键
27 | public static final int KEYCODE_EMPTY = -10, KEYCODE_DOUBLE_ZORE = -11, KEYCODE_NUM_DELETE = -12;
28 | private int KEY_PADDING_TOP;
29 | private Context mContext;
30 |
31 | public CustomKeyboardView(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | init(context);
34 | }
35 |
36 | public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
37 | super(context, attrs, defStyleAttr);
38 | init(context);
39 | }
40 |
41 | private void init(Context context) {
42 | KEY_PADDING_TOP = IdCardUtil.dip2px(context, 5);
43 | mContext = context;
44 | setEnabled(true);
45 | // 设置按键没有点击放大镜显示的效果
46 | setPreviewEnabled(false);
47 | }
48 |
49 | @Override
50 | public void onDraw(Canvas canvas) {
51 | super.onDraw(canvas);
52 | List keys = getKeyboard().getKeys();
53 |
54 | for (Keyboard.Key key : keys) {
55 | // 如果是左下角空白的按键,重画按键的背景
56 | if (key.codes[0] == KEYCODE_EMPTY) {
57 | drawKeyBackground(key, canvas, getResources().getColor(R.color.gray_d2d5db));
58 | } else if (key.codes[0] == Keyboard.KEYCODE_DELETE) {
59 | drawKeyBackground(key, canvas, getResources().getColor(R.color.gray_d2d5db));
60 | drawDeleteButton(key, canvas);
61 | } else if (key.codes[0] == KEYCODE_NUM_DELETE) {
62 | drawDeleteButton(key, canvas);
63 | } else if (key.codes[0] == Keyboard.KEYCODE_DONE) {
64 | //重画确定键
65 | drawKeyBackground(key, R.drawable.cmb_checked_bg, canvas);
66 | drawText(canvas, key);
67 | }
68 | }
69 | }
70 |
71 | /**
72 | * 绘制按键的背景
73 | */
74 | private void drawKeyBackground(Keyboard.Key key, Canvas canvas, int color) {
75 | ColorDrawable drawable = new ColorDrawable(color);
76 | drawable.setBounds(key.x, key.y + KEY_PADDING_TOP, key.x + key.width, key.y + key.height + KEY_PADDING_TOP);
77 | drawable.draw(canvas);
78 | }
79 |
80 | private void drawKeyBackground(Keyboard.Key key, int drawableId, Canvas canvas) {
81 | Drawable npd = mContext.getResources().getDrawable(drawableId);
82 | int[] drawableState = key.getCurrentDrawableState();
83 | if (key.codes[0] != 0) {
84 | npd.setState(drawableState);
85 | }
86 | npd.setBounds(key.x, key.y, key.x + key.width, key.y + key.height);
87 | npd.draw(canvas);
88 | }
89 |
90 |
91 | // 绘制删除按键
92 | private void drawDeleteButton(Keyboard.Key key, Canvas canvas) {
93 | Drawable drawable;
94 | if (key.pressed) {
95 | drawable = getContext().getResources().getDrawable(R.drawable.ic_delete_key_pressed);
96 | } else {
97 | drawable = getContext().getResources().getDrawable(R.drawable.ic_delete_key_normal);
98 | }
99 |
100 | if (drawable == null) {
101 | return;
102 | }
103 |
104 | // 计算删除图标绘制的坐标
105 | Rect mDeleteDrawRect = new Rect();
106 | if (mDeleteDrawRect.isEmpty()) {
107 | int drawWidth, drawHeight;
108 | //getIntrinsicWidth()和getIntrinsicHeight()返回的宽高应该是dp为单位
109 | int intrinsicWidth = drawable.getIntrinsicWidth();
110 | int intrinsicHeight = drawable.getIntrinsicHeight();
111 | drawWidth = intrinsicWidth;
112 | drawHeight = intrinsicHeight;
113 |
114 | // 限制图标的大小,防止图标超出按键
115 | if (drawWidth > key.width) {
116 | drawWidth = key.width;
117 | drawHeight = drawWidth * intrinsicHeight / intrinsicWidth;
118 | }
119 | if (drawHeight > key.height) {
120 | drawHeight = key.height;
121 | drawWidth = drawHeight * intrinsicWidth / intrinsicHeight;
122 | }
123 |
124 | // 获取删除图标绘制的坐标
125 | int left = key.x + (key.width - drawWidth) / 2;
126 | int top = KEY_PADDING_TOP + key.y + (key.height - drawHeight) / 2;
127 | mDeleteDrawRect = new Rect(left, top, left + drawWidth, top + drawHeight);
128 | }
129 |
130 | // 绘制删除的图标
131 | if (!mDeleteDrawRect.isEmpty()) {
132 | drawable.setBounds(mDeleteDrawRect.left, mDeleteDrawRect.top,
133 | mDeleteDrawRect.right, mDeleteDrawRect.bottom);
134 | drawable.draw(canvas);
135 | }
136 | }
137 |
138 | private void drawText(Canvas canvas, Keyboard.Key key) {
139 | Rect bounds = new Rect();
140 | Paint paint = new Paint();
141 | paint.setTextAlign(Paint.Align.CENTER);
142 | paint.setAntiAlias(true);
143 |
144 | paint.setColor(Color.WHITE);
145 | if (key.label != null) {
146 | String label = key.label.toString();
147 |
148 | Field field;
149 |
150 | if (label.length() > 1 && key.codes.length < 2) {
151 | int labelTextSize = 0;
152 | try {
153 | field = KeyboardView.class.getDeclaredField("mLabelTextSize");
154 | field.setAccessible(true);
155 | labelTextSize = (int) field.get(this);
156 | } catch (NoSuchFieldException e) {
157 | e.printStackTrace();
158 | } catch (IllegalAccessException e) {
159 | e.printStackTrace();
160 | }
161 | paint.setTextSize(labelTextSize);
162 | paint.setTypeface(Typeface.DEFAULT_BOLD);
163 | } else {
164 | int keyTextSize = 0;
165 | try {
166 | field = KeyboardView.class.getDeclaredField("mLabelTextSize");
167 | field.setAccessible(true);
168 | keyTextSize = (int) field.get(this);
169 | } catch (NoSuchFieldException e) {
170 | e.printStackTrace();
171 | } catch (IllegalAccessException e) {
172 | e.printStackTrace();
173 | }
174 | paint.setTextSize(keyTextSize);
175 | paint.setTypeface(Typeface.DEFAULT);
176 | }
177 |
178 | paint.getTextBounds(key.label.toString(), 0, key.label.toString()
179 | .length(), bounds);
180 | canvas.drawText(key.label.toString(), key.x + (key.width / 2), (key.y + key.height / 2) + bounds.height() / 2, paint);
181 | } else if (key.icon != null) {
182 | key.icon.setBounds(key.x + (key.width - key.icon.getIntrinsicWidth()) / 2, key.y + (key.height - key.icon.getIntrinsicHeight()) / 2,
183 | key.x + (key.width - key.icon.getIntrinsicWidth()) / 2 + key.icon.getIntrinsicWidth(),
184 | key.y + (key.height - key.icon.getIntrinsicHeight()) / 2 + key.icon.getIntrinsicHeight());
185 | key.icon.draw(canvas);
186 | }
187 |
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/keyboard/InputView.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.keyboard;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.RectF;
10 | import android.text.InputFilter;
11 | import android.text.TextUtils;
12 | import android.util.AttributeSet;
13 | import android.widget.EditText;
14 |
15 | import com.example.weioule.inputkeyboarddemo.R;
16 |
17 | import static android.graphics.Paint.ANTI_ALIAS_FLAG;
18 |
19 | /**
20 | * Created by weioule on 2018/1/2.
21 | */
22 |
23 | @SuppressLint("AppCompatCustomView")
24 | public class InputView extends EditText {
25 |
26 | private Context mContext;
27 | /**
28 | * 第一个圆开始绘制的圆心坐标
29 | */
30 | private float startX;
31 | private float startY;
32 |
33 |
34 | private float cX;
35 |
36 |
37 | /**
38 | * 实心圆的半径
39 | */
40 | private int radius = 14;
41 | /**
42 | * view的高度
43 | */
44 | private int height;
45 | private int width;
46 |
47 | /**
48 | * 当前输入密码位数
49 | */
50 | private int textLength = 0;
51 | private int bottomLineLength;
52 | /**
53 | * 最大输入位数
54 | */
55 | protected int maxCount = 6;
56 | /**
57 | * 圆的颜色 默认BLACK
58 | */
59 | private int circleColor = Color.BLACK;
60 | /**
61 | * 底部线的颜色 默认GRAY
62 | */
63 | private int bottomLineColor = 0xffd2d2d2;
64 |
65 | /**
66 | * 分割线的颜色
67 | */
68 | private int borderColor = 0xffd2d2d2;
69 | /**
70 | * 分割线的画笔
71 | */
72 | private Paint borderPaint;
73 | /**
74 | * 分割线开始的坐标x
75 | */
76 | private int divideLineWStartX;
77 |
78 | /**
79 | * 分割线的宽度 默认1
80 | */
81 | private int divideLineWidth = 1;
82 | /**
83 | * 竖直分割线的颜色
84 | */
85 | private int divideLineColor = 0xffd2d2d2;
86 | private int focusedColor = 0xffd2d2d2;
87 | private RectF rectF = new RectF();
88 | private RectF focusedRecF = new RectF();
89 | private int psdType = 0;
90 | private final static int psdType_weChat = 0;
91 | private final static int psdType_bottomLine = 1;
92 |
93 | /**
94 | * 矩形边框的圆角
95 | */
96 | private int rectAngle = 5;
97 |
98 | /**
99 | * 竖直分割线的画笔
100 | */
101 | private Paint divideLinePaint;
102 | /**
103 | * 圆的画笔
104 | */
105 | private Paint circlePaint;
106 | /**
107 | * 底部线的画笔
108 | */
109 | private Paint bottomLinePaint;
110 |
111 | /**
112 | * 需要对比的密码 一般为上次输入的
113 | */
114 | private String mComparePassword = null;
115 |
116 | /**
117 | * 当前输入的位置索引
118 | */
119 | private int position = 0;
120 |
121 | private onPasswordListener mListener;
122 |
123 | public InputView(Context context, AttributeSet attrs) {
124 | super(context, attrs);
125 | mContext = context;
126 |
127 | getAtt(attrs);
128 | initPaint();
129 |
130 | this.setBackgroundColor(Color.TRANSPARENT);
131 | this.setCursorVisible(false);
132 | this.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxCount)});
133 | setKeyListener(null);
134 | }
135 |
136 | private void getAtt(AttributeSet attrs) {
137 | TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.InputView);
138 | maxCount = typedArray.getInt(R.styleable.InputView_maxCount, maxCount);
139 | circleColor = typedArray.getColor(R.styleable.InputView_circleColor, circleColor);
140 | bottomLineColor = typedArray.getColor(R.styleable.InputView_bottomLineColor, bottomLineColor);
141 | radius = typedArray.getDimensionPixelOffset(R.styleable.InputView_radius, radius);
142 |
143 | divideLineWidth = typedArray.getDimensionPixelSize(R.styleable.InputView_divideLineWidth, divideLineWidth);
144 | divideLineColor = typedArray.getColor(R.styleable.InputView_divideLineColor, divideLineColor);
145 | psdType = typedArray.getInt(R.styleable.InputView_psdType, psdType);
146 | rectAngle = typedArray.getDimensionPixelOffset(R.styleable.InputView_rectAngle, rectAngle);
147 | focusedColor = typedArray.getColor(R.styleable.InputView_focusedColor, focusedColor);
148 |
149 | typedArray.recycle();
150 | }
151 |
152 | /**
153 | * 初始化画笔
154 | */
155 | private void initPaint() {
156 |
157 | circlePaint = getPaint(5, Paint.Style.FILL, circleColor);
158 |
159 | bottomLinePaint = getPaint(1, Paint.Style.FILL, bottomLineColor);
160 |
161 | borderPaint = getPaint(3, Paint.Style.STROKE, borderColor);
162 |
163 | divideLinePaint = getPaint(divideLineWidth, Paint.Style.FILL, borderColor);
164 |
165 | }
166 |
167 | /**
168 | * 设置画笔
169 | *
170 | * @param strokeWidth 画笔宽度
171 | * @param style 画笔风格
172 | * @param color 画笔颜色
173 | * @return
174 | */
175 | private Paint getPaint(int strokeWidth, Paint.Style style, int color) {
176 | Paint paint = new Paint(ANTI_ALIAS_FLAG);
177 | paint.setStrokeWidth(strokeWidth);
178 | paint.setStyle(style);
179 | paint.setColor(color);
180 | paint.setAntiAlias(true);
181 |
182 | return paint;
183 | }
184 |
185 | @Override
186 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
187 | super.onSizeChanged(w, h, oldw, oldh);
188 | height = h;
189 | width = w;
190 |
191 | divideLineWStartX = w / maxCount;
192 |
193 | startX = w / maxCount / 2;
194 | startY = h / 2;
195 |
196 | bottomLineLength = w / (maxCount + 2);
197 |
198 | rectF.set(0, 0, width, height);
199 |
200 | }
201 |
202 | @Override
203 | protected void onDraw(Canvas canvas) {
204 | //不删除的话会默认绘制输入的文字
205 | // super.onDraw(canvas);
206 |
207 | switch (psdType) {
208 | case psdType_weChat:
209 | drawWeChatBorder(canvas);
210 | // drawItemFocused(canvas, position);
211 | break;
212 | case psdType_bottomLine:
213 | drawBottomBorder(canvas);
214 | break;
215 | }
216 |
217 | drawPsdCircle(canvas);
218 | }
219 |
220 | /**
221 | * 画微信支付密码的样式
222 | *
223 | * @param canvas
224 | */
225 | private void drawWeChatBorder(Canvas canvas) {
226 |
227 | canvas.drawRoundRect(rectF, rectAngle, rectAngle, borderPaint);
228 |
229 | for (int i = 0; i < maxCount - 1; i++) {
230 | canvas.drawLine((i + 1) * divideLineWStartX,
231 | 0,
232 | (i + 1) * divideLineWStartX,
233 | height,
234 | divideLinePaint);
235 | }
236 |
237 | }
238 |
239 | private void drawItemFocused(Canvas canvas, int position) {
240 | if (position > maxCount - 1) {
241 | return;
242 | }
243 | focusedRecF.set(position * divideLineWStartX + 2, 2, (position + 1) * divideLineWStartX - 2, height - 2);
244 |
245 | canvas.drawRoundRect(focusedRecF, rectAngle, rectAngle, getPaint(3, Paint.Style.STROKE, focusedColor));
246 | }
247 |
248 | /**
249 | * 画底部显示的分割线
250 | *
251 | * @param canvas
252 | */
253 | private void drawBottomBorder(Canvas canvas) {
254 |
255 | for (int i = 0; i < maxCount; i++) {
256 | cX = startX + i * 2 * startX;
257 | canvas.drawLine(cX - bottomLineLength / 2,
258 | height,
259 | cX + bottomLineLength / 2,
260 | height, bottomLinePaint);
261 | }
262 | }
263 |
264 | /**
265 | * 画密码实心圆
266 | *
267 | * @param canvas
268 | */
269 | private void drawPsdCircle(Canvas canvas) {
270 | for (int i = 0; i < textLength; i++) {
271 | canvas.drawCircle(startX + i * 2 * startX,
272 | startY,
273 | radius,
274 | circlePaint);
275 | }
276 | }
277 |
278 | @Override
279 | protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
280 | super.onTextChanged(text, start, lengthBefore, lengthAfter);
281 | this.position = start + lengthAfter;
282 | textLength = text.toString().length();
283 |
284 | if (mComparePassword != null && textLength == maxCount) {
285 | if (TextUtils.equals(mComparePassword, getPasswordString())) {
286 | mListener.onEqual(getPasswordString());
287 | } else {
288 | mListener.onDifference();
289 | }
290 | }
291 | invalidate();
292 |
293 | }
294 |
295 | @Override
296 | protected void onSelectionChanged(int selStart, int selEnd) {
297 | super.onSelectionChanged(selStart, selEnd);
298 |
299 | //保证光标始终在最后
300 | if (selStart == selEnd) {
301 | setSelection(getText().length());
302 | }
303 | }
304 |
305 | /**
306 | * 获取输入的密码
307 | *
308 | * @return
309 | */
310 | public String getPasswordString() {
311 | return getText().toString().trim();
312 | }
313 |
314 | public void setComparePassword(String comparePassword, onPasswordListener listener) {
315 | mComparePassword = comparePassword;
316 | mListener = listener;
317 | }
318 |
319 | /**
320 | * 密码比较监听
321 | */
322 | public interface onPasswordListener {
323 | void onDifference();
324 |
325 | void onEqual(String psd);
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/keyboard/CumKeyboardContainer.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.keyboard;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.graphics.Rect;
6 | import android.inputmethodservice.Keyboard;
7 | import android.inputmethodservice.KeyboardView;
8 | import android.text.Editable;
9 | import android.util.DisplayMetrics;
10 | import android.view.Display;
11 | import android.view.Gravity;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.Window;
15 | import android.widget.EditText;
16 | import android.widget.FrameLayout;
17 |
18 | import com.example.weioule.inputkeyboarddemo.R;
19 |
20 | import java.lang.reflect.Method;
21 | import java.util.List;
22 |
23 | /**
24 | * Author by weioule.
25 | * Date on 2018/11/19.
26 | */
27 | public class CumKeyboardContainer extends FrameLayout implements KeyboardView.OnKeyboardActionListener, View.OnClickListener {
28 |
29 | public static String IDCERT_TYPE = "pwd", AMOUT_TYPE = "amount";
30 | private int KEYCODE_MODE_CHANGE_RIGHT = -100;
31 | private CustomKeyboardView mKeyboardView;
32 | private View mContentView, mDecorView;
33 | private Keyboard mNumberKeyboard;
34 | private EditText mEditText;
35 | private Context mContext;
36 | private int mScrollDis;
37 |
38 | public CumKeyboardContainer(Context context, String type) {
39 | super(context);
40 | mContext = context;
41 | initView(type);
42 | initKeyBoard(type);
43 | setVisibility(GONE);
44 | }
45 |
46 | public CumKeyboardContainer(Context context) {
47 | super(context);
48 | mContext = context;
49 | initView(IDCERT_TYPE);
50 | initKeyBoard(IDCERT_TYPE);
51 | setVisibility(GONE);
52 | }
53 |
54 | public void attachKeyBoardView() {
55 | if (getParent() != null) {
56 | return;
57 | }
58 |
59 | FrameLayout frameLayout = (FrameLayout) ((Activity) getContext()).getWindow().getDecorView();
60 | LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
61 | lp.gravity = Gravity.BOTTOM;
62 | lp.bottomMargin = getBottomKeyboardHeight();
63 | frameLayout.addView(this, lp);
64 | }
65 |
66 | /**
67 | * 获取底部虚拟功能键的高度
68 | */
69 | public int getBottomKeyboardHeight() {
70 | int screenHeight = getAccurateScreenDpi()[1];
71 | DisplayMetrics dm = new DisplayMetrics();
72 | ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm);
73 | int heightDifference = screenHeight - dm.heightPixels;
74 | return heightDifference;
75 | }
76 |
77 | /**
78 | * 获取精确的屏幕大小
79 | */
80 | public int[] getAccurateScreenDpi() {
81 | int[] screenWH = new int[2];
82 | Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay();
83 | DisplayMetrics dm = new DisplayMetrics();
84 | try {
85 | Class> c = Class.forName("android.view.Display");
86 | Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
87 | method.invoke(display, dm);
88 | screenWH[0] = dm.widthPixels;
89 | screenWH[1] = dm.heightPixels;
90 | } catch (Exception e) {
91 | e.printStackTrace();
92 | }
93 | return screenWH;
94 | }
95 |
96 | private void initView(String type) {
97 | if (AMOUT_TYPE.equalsIgnoreCase(type)) {
98 | LayoutInflater.from(mContext).inflate(R.layout.kb_amout_layout, this);
99 | } else {
100 | LayoutInflater.from(mContext).inflate(R.layout.customkeyboard, this);
101 | }
102 | }
103 |
104 | @Override
105 | public void onAttachedToWindow() {
106 | super.onAttachedToWindow();
107 | Window mWindow = ((Activity) getContext()).getWindow();
108 | mDecorView = mWindow.getDecorView();
109 | mContentView = mWindow
110 | .findViewById(Window.ID_ANDROID_CONTENT);
111 | }
112 |
113 | public void setCmbVisibility(int state) {
114 | if (getVisibility() == state) {
115 | return;
116 | }
117 |
118 | setVisibility(state);
119 | if (state == VISIBLE) {
120 | scrollHostWithKeyboard();
121 | } else if (mScrollDis < 0) {
122 | View contentView = mContentView.findViewById(android.R.id.content);
123 | if (null != contentView) {
124 | contentView.scrollBy(0, mScrollDis);
125 | }
126 | mScrollDis = 0;
127 | }
128 | }
129 |
130 | public void setEditText(EditText editText) {
131 | mEditText = editText;
132 | }
133 |
134 | private void initKeyBoard(String type) {
135 | if (AMOUT_TYPE.equalsIgnoreCase(type)) {
136 | mNumberKeyboard = new Keyboard(mContext, R.xml.kb_number);
137 | } else {
138 | mNumberKeyboard = new Keyboard(mContext, R.xml.kb_idcert);
139 | }
140 |
141 | mKeyboardView = findViewById(R.id.cmbkeyboard_view);
142 | mKeyboardView.setKeyboard(mNumberKeyboard);
143 |
144 | mKeyboardView.setEnabled(true);
145 | mKeyboardView.setPreviewEnabled(false);
146 | mKeyboardView.setOnKeyboardActionListener(this);
147 | }
148 |
149 | @Override
150 | public void onPress(int primaryCode) {
151 | if (primaryCode == -1 || primaryCode == -5 || primaryCode == -2 || primaryCode == -3 || primaryCode == KEYCODE_MODE_CHANGE_RIGHT ||
152 | (48 <= primaryCode && primaryCode <= 57)) {
153 | mKeyboardView.setPreviewEnabled(false);
154 | }
155 | }
156 |
157 | @Override
158 | public void onRelease(int primaryCode) {
159 |
160 | }
161 |
162 | @Override
163 | public void onKey(int primaryCode, int[] keyCodes) {
164 | if (mEditText == null) {
165 | return;
166 | }
167 |
168 | Editable editable = mEditText.getText();
169 | int start = mEditText.getSelectionStart();
170 |
171 | // 隐藏键盘
172 | if (primaryCode == Keyboard.KEYCODE_CANCEL) {
173 | //finish();
174 | } else if (primaryCode == Keyboard.KEYCODE_DELETE ||
175 | primaryCode == CustomKeyboardView.KEYCODE_NUM_DELETE) { // 回退
176 | if (editable != null && editable.length() > 0) {
177 | if (start > 0) {
178 | editable.delete(start - 1, start);
179 | }
180 | }
181 | } else if (0x0 <= primaryCode && primaryCode <= 0x7f) {
182 | // 可以直接输入的字符(如0-9,.),他们在键盘映射xml中的keycode值必须配置为该字符的ASCII码
183 | editable.insert(start, Character.toString((char) primaryCode));
184 | } else if (primaryCode > 0x7f) {
185 | Keyboard.Key mkey = getKeyByKeyCode(primaryCode, mKeyboardView.getKeyboard());
186 | // 可以直接输入的字符(如0-9,.),他们在键盘映射xml中的keycode值必须配置为该字符的ASCII码
187 | editable.insert(start, mkey.label);
188 |
189 | } else if (primaryCode == CustomKeyboardView.KEYCODE_DOUBLE_ZORE) {
190 | editable.insert(start, "00");
191 | } else if (primaryCode == Keyboard.KEYCODE_DONE) {
192 | setCmbVisibility(GONE);
193 | } else {
194 | // 其他一些暂未开放的键指令,如next到下一个输入框等指令
195 | }
196 | }
197 |
198 | private Keyboard.Key getKeyByKeyCode(int keyCode, Keyboard keyboard) {
199 | if (null != keyboard) {
200 | List mKeys = keyboard.getKeys();
201 | for (int i = 0, size = mKeys.size(); i < size; i++) {
202 | Keyboard.Key mKey = mKeys.get(i);
203 | int[] codes = mKey.codes;
204 |
205 | if (codes[0] == keyCode) {
206 | return mKey;
207 | }
208 | }
209 | }
210 | return null;
211 | }
212 |
213 | @Override
214 | public void onText(CharSequence text) {
215 |
216 | }
217 |
218 | @Override
219 | public void swipeLeft() {
220 |
221 | }
222 |
223 | @Override
224 | public void swipeRight() {
225 |
226 | }
227 |
228 | @Override
229 | public void swipeDown() {
230 |
231 | }
232 |
233 | @Override
234 | public void swipeUp() {
235 |
236 | }
237 |
238 | /**
239 | * 目前基于全屏Activity
240 | */
241 | private void scrollHostWithKeyboard() {
242 | if (null != mDecorView && null != mContentView) {
243 | int[] pos = new int[2];
244 | View mEditTextParent = (View) mEditText.getParent();
245 | mEditTextParent.getLocationOnScreen(pos);
246 |
247 | // * 包括标题栏,但不包括状态栏。
248 | Rect outRect = new Rect();
249 | //获取root在窗体的可视区域
250 | mDecorView.getWindowVisibleDisplayFrame(outRect);
251 |
252 | //为了忽略原生键盘
253 | DisplayMetrics dm = new DisplayMetrics();
254 | ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm);
255 | outRect.bottom = dm.heightPixels;//屏幕高度
256 |
257 | int contentHeight = mEditTextParent.findViewById(R.id.input_layout).getHeight();
258 | int bottomOnScreen = pos[1] + contentHeight;//输入框控件的顶部+输入框的高度
259 |
260 | //获取到覆盖区域
261 | int cmbHeight = preMeasureKeyboard(findViewById(R.id.contentLayout));
262 | int coveredHeight = outRect.bottom - bottomOnScreen - cmbHeight;
263 | if (coveredHeight < 0) {
264 | mScrollDis = coveredHeight;
265 | View contentView = mContentView.findViewById(android.R.id.content);
266 | if (null != contentView) {
267 | contentView.scrollBy(0, -mScrollDis);
268 | }
269 | }
270 | }
271 | }
272 |
273 | private int preMeasureKeyboard(View view) {
274 | view.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT));
275 | view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
276 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
277 | return view.getMeasuredHeight();
278 | }
279 |
280 | @Override
281 | public void onClick(View view) {
282 |
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/util/IdCardUtil.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.util;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 |
6 | /**
7 | * Author by weioule.
8 | * Date on 2018/11/19.
9 | */
10 | public class IdCardUtil {
11 | private String idCardNum = null;
12 |
13 | private static int IS_EMPTY = 1;
14 | private static int LEN_ERROR = 2;
15 | private static int CHAR_ERROR = 3;
16 | private static int DATE_ERROR = 4;
17 | private static int CHECK_BIT_ERROR = 5;
18 |
19 | private String[] errMsg = new String[]{"身份证完全正确!",
20 | "身份证为空!",
21 | "身份证长度不正确!",
22 | "身份证有非法字符!",
23 | "身份证中出生日期不合法!",
24 | "身份证校验位错误!"};
25 |
26 | private int error = 0;
27 |
28 | /**
29 | * 构造方法。
30 | *
31 | * @param idCardNum
32 | */
33 | public IdCardUtil(String idCardNum) {
34 | this.idCardNum = idCardNum.trim();
35 | if (!TextUtils.isEmpty(this.idCardNum)) {
36 | this.idCardNum = this.idCardNum.replace("x", "X");
37 | }
38 | }
39 |
40 | public String getIdCardNum() {
41 | return idCardNum;
42 | }
43 |
44 | public void setIdCardNum(String idCardNum) {
45 | this.idCardNum = idCardNum;
46 | if (!TextUtils.isEmpty(this.idCardNum)) {
47 | this.idCardNum = this.idCardNum.replace("x", "X");
48 | }
49 | }
50 |
51 | /**
52 | * 得到身份证详细错误信息。
53 | *
54 | * @return 错误信息。
55 | */
56 | public String getErrMsg() {
57 | return this.errMsg[this.error];
58 | }
59 |
60 | /**
61 | * 是否为空。
62 | *
63 | * @return true: null false: not null;
64 | */
65 | public boolean isEmpty() {
66 | if (this.idCardNum == null)
67 | return true;
68 | else
69 | return this.idCardNum.trim().length() > 0 ? false : true;
70 | }
71 |
72 | /**
73 | * 身份证长度。
74 | *
75 | * @return
76 | */
77 | public int getLength() {
78 | return this.isEmpty() ? 0 : this.idCardNum.length();
79 | }
80 |
81 | /**
82 | * 身份证长度。
83 | *
84 | * @return
85 | */
86 | public int getLength(String str) {
87 | return this.isEmpty() ? 0 : str.length();
88 | }
89 |
90 | /**
91 | * 是否是15位身份证。
92 | *
93 | * @return true: 15位 false:其他。
94 | */
95 | public boolean is15() {
96 | return this.getLength() == 15;
97 | }
98 |
99 | /**
100 | * 是否是18位身份证。
101 | *
102 | * @return true: 18位 false:其他。
103 | */
104 | public boolean is18() {
105 | return this.getLength() == 18;
106 | }
107 |
108 | /**
109 | * 得到身份证的省份代码。
110 | *
111 | * @return 省份代码。
112 | */
113 | public String getProvince() {
114 | return this.isCorrect() == 0 ? this.idCardNum.substring(0, 2) : "";
115 | }
116 |
117 | /**
118 | * 得到身份证的城市代码。
119 | *
120 | * @return 城市代码。
121 | */
122 | public String getCity() {
123 | return this.isCorrect() == 0 ? this.idCardNum.substring(2, 4) : "";
124 | }
125 |
126 | /**
127 | * 得到身份证的区县代码。
128 | *
129 | * @return 区县代码。
130 | */
131 | public String getCountry() {
132 | return this.isCorrect() == 0 ? this.idCardNum.substring(4, 6) : "";
133 | }
134 |
135 | /**
136 | * 得到身份证的出生年份。
137 | *
138 | * @return 出生年份。
139 | */
140 | public String getYear() {
141 | if (this.isCorrect() != 0)
142 | return "";
143 |
144 | if (this.getLength() == 15) {
145 | return "19" + this.idCardNum.substring(6, 8);
146 | } else {
147 | return this.idCardNum.substring(6, 10);
148 | }
149 | }
150 |
151 | /**
152 | * 得到身份证的出生月份。
153 | *
154 | * @return 出生月份。
155 | */
156 | public String getMonth() {
157 | if (this.isCorrect() != 0)
158 | return "";
159 |
160 | if (this.getLength() == 15) {
161 | return this.idCardNum.substring(8, 10);
162 | } else {
163 | return this.idCardNum.substring(10, 12);
164 | }
165 | }
166 |
167 | /**
168 | * 得到身份证的出生日子。
169 | *
170 | * @return 出生日期。
171 | */
172 | public String getDay() {
173 | if (this.isCorrect() != 0)
174 | return "";
175 |
176 | if (this.getLength() == 15) {
177 | return this.idCardNum.substring(10, 12);
178 | } else {
179 | return this.idCardNum.substring(12, 14);
180 | }
181 | }
182 |
183 | /**
184 | * 得到身份证的出生日期。
185 | *
186 | * @return 出生日期。
187 | */
188 | public String getBirthday() {
189 | if (this.isCorrect() != 0)
190 | return "";
191 |
192 | if (this.getLength() == 15) {
193 | return "19" + this.idCardNum.substring(6, 12);
194 | } else {
195 | return this.idCardNum.substring(6, 14);
196 | }
197 | }
198 |
199 | /**
200 | * 得到身份证的出生年月。
201 | *
202 | * @return 出生年月。
203 | */
204 | public String getBirthMonth() {
205 | return getBirthday().substring(0, 6);
206 | }
207 |
208 | /**
209 | * 得到身份证的顺序号。
210 | *
211 | * @return 顺序号。
212 | */
213 | public String getOrder() {
214 | if (this.isCorrect() != 0)
215 | return "";
216 |
217 | if (this.getLength() == 15) {
218 | return this.idCardNum.substring(12, 15);
219 | } else {
220 | return this.idCardNum.substring(14, 17);
221 | }
222 | }
223 |
224 | /**
225 | * 得到性别。
226 | *
227 | * @return 性别:1-男 2-女
228 | */
229 | public String getSex() {
230 | if (this.isCorrect() != 0)
231 | return "";
232 |
233 | int p = Integer.parseInt(getOrder());
234 | if (p % 2 == 1) {
235 | return "男";
236 | } else {
237 | return "女";
238 | }
239 | }
240 |
241 | /**
242 | * 得到性别值。
243 | *
244 | * @return 性别:1-男 2-女
245 | */
246 | public String getSexValue() {
247 | if (this.isCorrect() != 0)
248 | return "";
249 |
250 | int p = Integer.parseInt(getOrder());
251 | if (p % 2 == 1) {
252 | return "1";
253 | } else {
254 | return "2";
255 | }
256 | }
257 |
258 | /**
259 | * 得到校验位。
260 | *
261 | * @return 校验位。
262 | */
263 | public String getCheck() {
264 | if (!this.isLenCorrect())
265 | return "";
266 |
267 | String lastStr = this.idCardNum.substring(this.idCardNum.length() - 1);
268 | if ("x".equals(lastStr)) {
269 | lastStr = "X";
270 | }
271 | return lastStr;
272 | }
273 |
274 | /**
275 | * 得到15位身份证。
276 | *
277 | * @return 15位身份证。
278 | */
279 | public String to15() {
280 | if (this.isCorrect() != 0)
281 | return "";
282 |
283 | if (this.is15())
284 | return this.idCardNum;
285 | else
286 | return this.idCardNum.substring(0, 6) + this.idCardNum.substring(8, 17);
287 | }
288 |
289 | /**
290 | * 得到18位身份证。
291 | *
292 | * @return 18位身份证。
293 | */
294 | public String to18() {
295 | if (this.isCorrect() != 0)
296 | return "";
297 |
298 | if (this.is18())
299 | return this.idCardNum;
300 | else
301 | return this.idCardNum.substring(0, 6) + "19" + this.idCardNum.substring(6) + this.getCheckBit();
302 | }
303 |
304 | /**
305 | * 得到18位身份证。
306 | *
307 | * @return 18位身份证。
308 | */
309 | public static String toNewIdCard(String tempStr) {
310 | if (tempStr.length() == 18)
311 | return tempStr.substring(0, 6) + tempStr.substring(8, 17);
312 | else
313 | return tempStr.substring(0, 6) + "19" + tempStr.substring(6) + getCheckBit(tempStr);
314 | }
315 |
316 | /**
317 | * 校验身份证是否正确
318 | *
319 | * @return 0:正确
320 | */
321 | public int isCorrect() {
322 | if (this.isEmpty()) {
323 | this.error = IdCardUtil.IS_EMPTY;
324 | return this.error;
325 | }
326 |
327 | if (!this.isLenCorrect()) {
328 | this.error = IdCardUtil.LEN_ERROR;
329 | return this.error;
330 | }
331 |
332 | if (!this.isCharCorrect()) {
333 | this.error = IdCardUtil.CHAR_ERROR;
334 | return this.error;
335 | }
336 |
337 | if (!this.isDateCorrect()) {
338 | this.error = IdCardUtil.DATE_ERROR;
339 | return this.error;
340 | }
341 |
342 | if (this.is18()) {
343 | if (!this.getCheck().equals(this.getCheckBit())) {
344 | this.error = IdCardUtil.CHECK_BIT_ERROR;
345 | return this.error;
346 | }
347 | }
348 |
349 | return 0;
350 | }
351 |
352 |
353 | private boolean isLenCorrect() {
354 | return this.is15() || this.is18();
355 | }
356 |
357 | /**
358 | * 判断身份证中出生日期是否正确。
359 | *
360 | * @return
361 | */
362 | private boolean isDateCorrect() {
363 |
364 | /*非闰年天数*/
365 | int[] monthDayN = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
366 | /*闰年天数*/
367 | int[] monthDayL = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
368 |
369 | int month;
370 | if (this.is15()) {
371 | month = Integer.parseInt(this.idCardNum.substring(8, 10));
372 | } else {
373 | month = Integer.parseInt(this.idCardNum.substring(10, 12));
374 | }
375 |
376 | int day;
377 | if (this.is15()) {
378 | day = Integer.parseInt(this.idCardNum.substring(10, 12));
379 | } else {
380 | day = Integer.parseInt(this.idCardNum.substring(12, 14));
381 | }
382 |
383 | if (month > 12 || month <= 0) {
384 | return false;
385 | }
386 |
387 | if (this.isLeapyear()) {
388 | if (day > monthDayL[month - 1] || day <= 0)
389 | return false;
390 | } else {
391 | if (day > monthDayN[month - 1] || day <= 0)
392 | return false;
393 | }
394 |
395 | return true;
396 | }
397 |
398 | /**
399 | * 得到校验位。
400 | *
401 | * @return
402 | */
403 | private String getCheckBit() {
404 | if (!this.isLenCorrect())
405 | return "";
406 |
407 | String temp = null;
408 | if (this.is18())
409 | temp = this.idCardNum;
410 | else
411 | temp = this.idCardNum.substring(0, 6) + "19" + this.idCardNum.substring(6);
412 |
413 |
414 | String checkTable[] = new String[]{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};
415 | int[] wi = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1};
416 | int sum = 0;
417 |
418 | for (int i = 0; i < 17; i++) {
419 | String ch = temp.substring(i, i + 1);
420 | sum = sum + Integer.parseInt(ch) * wi[i];
421 | }
422 |
423 | int y = sum % 11;
424 |
425 | return checkTable[y];
426 | }
427 |
428 |
429 | /**
430 | * 得到校验位。
431 | *
432 | * @return
433 | */
434 | private static String getCheckBit(String str) {
435 |
436 | String temp = null;
437 | if (str.length() == 18)
438 | temp = str;
439 | else
440 | temp = str.substring(0, 6) + "19" + str.substring(6);
441 |
442 |
443 | String checkTable[] = new String[]{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};
444 | int[] wi = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1};
445 | int sum = 0;
446 |
447 | for (int i = 0; i < 17; i++) {
448 | String ch = temp.substring(i, i + 1);
449 | sum = sum + Integer.parseInt(ch) * wi[i];
450 | }
451 |
452 | int y = sum % 11;
453 |
454 | return checkTable[y];
455 | }
456 |
457 |
458 | /**
459 | * 身份证号码中是否存在非法字符。
460 | *
461 | * @return true: 正确 false:存在非法字符。
462 | */
463 | private boolean isCharCorrect() {
464 | boolean iRet = true;
465 |
466 | if (this.isLenCorrect()) {
467 | byte[] temp = this.idCardNum.getBytes();
468 |
469 | if (this.is15()) {
470 | for (int i = 0; i < temp.length; i++) {
471 | if (temp[i] < 48 || temp[i] > 57) {
472 | iRet = false;
473 | break;
474 | }
475 | }
476 | }
477 |
478 | if (this.is18()) {
479 | for (int i = 0; i < temp.length; i++) {
480 | if (temp[i] < 48 || temp[i] > 57) {
481 | if (i == 17 && temp[i] != 88) {
482 | iRet = false;
483 | break;
484 | }
485 | }
486 | }
487 | }
488 | } else {
489 | iRet = false;
490 | }
491 | return iRet;
492 | }
493 |
494 | /**
495 | * 判断身份证的出生年份是否未闰年。
496 | *
497 | * @return true :闰年 false 平年
498 | */
499 | private boolean isLeapyear() {
500 | String temp;
501 |
502 | if (this.is15()) {
503 | temp = "19" + this.idCardNum.substring(6, 8);
504 | } else {
505 | temp = this.idCardNum.substring(6, 10);
506 | }
507 |
508 | int year = Integer.parseInt(temp);
509 |
510 | if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
511 | return true;
512 | else
513 | return false;
514 | }
515 |
516 | /**
517 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
518 | */
519 | public static int dip2px(Context context, float dpValue) {
520 | final float scale = context.getResources().getDisplayMetrics().density;
521 | return (int) (dpValue * scale + 0.5f);
522 | }
523 | }
524 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/weioule/inputkeyboarddemo/view/CEditText.java:
--------------------------------------------------------------------------------
1 | package com.example.weioule.inputkeyboarddemo.view;
2 |
3 | import android.animation.Keyframe;
4 | import android.animation.ObjectAnimator;
5 | import android.animation.PropertyValuesHolder;
6 | import android.animation.ValueAnimator;
7 | import android.content.Context;
8 | import android.content.res.TypedArray;
9 | import android.graphics.drawable.Drawable;
10 | import android.text.Editable;
11 | import android.text.InputFilter;
12 | import android.text.InputType;
13 | import android.text.Layout;
14 | import android.text.TextPaint;
15 | import android.text.TextWatcher;
16 | import android.util.AttributeSet;
17 | import android.view.ActionMode;
18 | import android.view.LayoutInflater;
19 | import android.view.Menu;
20 | import android.view.MenuItem;
21 | import android.view.View;
22 | import android.widget.Button;
23 | import android.widget.EditText;
24 | import android.widget.ImageView;
25 | import android.widget.RelativeLayout;
26 | import android.widget.TextView;
27 | import android.widget.TextView.OnEditorActionListener;
28 |
29 | import com.example.weioule.inputkeyboarddemo.MyApplication;
30 | import com.example.weioule.inputkeyboarddemo.R;
31 | import com.example.weioule.inputkeyboarddemo.keyboard.CumKeyboardContainer;
32 |
33 | /**
34 | * Author by weioule.
35 | * Date on 2018/11/19.
36 | */
37 | public class CEditText extends RelativeLayout {
38 | private int maxlength;
39 | private View mSplitLine;
40 | public Button mRightBtn;
41 | public ImageView mClearbtn;
42 | private CmbEditText mEditText;
43 | private FocusChange focusChange;
44 | private AsyncImageView mLeftImage;
45 | private ObjectAnimator nopeAnimator;
46 | public OnEditListener mEditListener;
47 | private boolean clear, clearImage, isShowPwd = false;
48 | private TextView mLabel, mLeftTextView, mRightButton, mErrNote;
49 |
50 | public CEditText(Context context, AttributeSet attrs) {
51 | super(context, attrs);
52 | inflate(R.layout.c_edit_text);
53 | initView();
54 | setAttribute(context, attrs);
55 | }
56 |
57 | public void inflate(int layoutId) {
58 | if (layoutId != -1) {
59 | LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
60 | inflater.inflate(layoutId, this, true);
61 | }
62 | }
63 |
64 | public void initView() {
65 | mLabel = findViewById(R.id.label);
66 | mErrNote = findViewById(R.id.err_note);
67 | mEditText = findViewById(R.id.editText);
68 | mRightBtn = findViewById(R.id.right_btn);
69 | mClearbtn = findViewById(R.id.clear_btn);
70 | mLeftImage = findViewById(R.id.left_image);
71 | mRightButton = findViewById(R.id.right_iamge);
72 | mLeftTextView = findViewById(R.id.left_textView);
73 | mSplitLine = findViewById(R.id.bottom_split_line);
74 | setListener();
75 | listener();
76 | }
77 |
78 | /**
79 | * 设置控件属性
80 | */
81 | public void setAttribute(Context context, AttributeSet attrs) {
82 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.custom);
83 | if (typedArray.getBoolean(R.styleable.custom_clear, false)) {
84 | setClear(true);
85 | }
86 | String txt = typedArray.getString(R.styleable.custom_lable);
87 | if (txt != null && !"".equals(txt)) {
88 | mLabel.setText(txt);
89 | }
90 | float left = typedArray.getDimension(R.styleable.custom_paddingleft_lable, 0);
91 | mLabel.setPadding((int) left, 0, 0, 0);
92 |
93 | String err = typedArray.getString(R.styleable.custom_errNote);
94 | if (err != null && !"".equals(err)) {
95 | mErrNote.setText(err);
96 | }
97 |
98 | if (typedArray.getBoolean(R.styleable.custom_show_custom_keyboard, false)) {
99 | mEditText.setCustomKeyboardEnable(true);
100 | }
101 |
102 | String hit = typedArray.getString(R.styleable.custom_hit);
103 | if (hit != null && !"".equals(hit)) {
104 | getEditText().setHint(hit);
105 | }
106 |
107 | boolean isShowBottomLine = typedArray.getBoolean(R.styleable.custom_show_bottom_split, true);
108 | if (!isShowBottomLine) {
109 | mSplitLine.setVisibility(GONE);
110 | }
111 |
112 | Drawable leftDrawable = typedArray.getDrawable(R.styleable.custom_btn_left_bg);
113 | if (leftDrawable != null) {
114 | mLeftTextView.setBackgroundDrawable(leftDrawable);
115 | mLeftTextView.setVisibility(View.VISIBLE);
116 | }
117 |
118 | Drawable rightDrawable = typedArray.getDrawable(R.styleable.custom_btn_right_bg);
119 | if (rightDrawable != null) {
120 | mRightButton.setBackgroundDrawable(rightDrawable);
121 | mRightButton.setVisibility(View.VISIBLE);
122 | }
123 |
124 | boolean isShowPwdEye = typedArray.getBoolean(R.styleable.custom_show_pwd_eye, false);
125 | if (isShowPwdEye) {
126 | setRightIamgeClick(new OnClickListener() {
127 | @Override
128 | public void onClick(View v) {
129 | setPwdEyeState(mRightButton);
130 | }
131 | });
132 | }
133 |
134 | String type = typedArray.getString(R.styleable.custom_input_typed);
135 | if (type != null && !"".equals(type)) {
136 | getEditText().setInputType(MyInputType.valueOf(type).getType());
137 | }
138 |
139 | int length = typedArray.getInteger(R.styleable.custom_length, -1);
140 | maxlength = length;
141 | if (length > 0) {
142 | getEditText().setFilters(new InputFilter[]{new InputFilter.LengthFilter(length)});
143 | }
144 |
145 | String right_btn_text = typedArray.getString(R.styleable.custom_right_btn_text);
146 | if (right_btn_text != null && !"".equals(right_btn_text)) {
147 | getEditText().setInputType(MyInputType.valueOf(type).getType());
148 | mRightBtn.setVisibility(VISIBLE);
149 | mRightBtn.setText(right_btn_text);
150 | }
151 |
152 | Drawable rightBtnDrawable = typedArray.getDrawable(R.styleable.custom_right_btn);
153 | if (rightBtnDrawable != null) {
154 | mRightBtn.setVisibility(VISIBLE);
155 | mRightBtn.setBackgroundDrawable(rightBtnDrawable);
156 | }
157 |
158 | String left_text = typedArray.getString(R.styleable.custom_left_text);
159 | if (left_text != null && !"".equals(left_text)) {
160 | mLeftTextView.setTextAppearance(getContext(), R.style.font_gray_6_16);
161 | mLeftTextView.setVisibility(VISIBLE);
162 | mLeftTextView.setText(left_text);
163 | mLeftTextView.setPadding(0, 0, 15, 0);
164 | }
165 | typedArray.recycle();
166 | }
167 |
168 | private void setPwdEyeState(View eyeView) {
169 | isShowPwd = !isShowPwd;
170 | if (isShowPwd) {
171 | setInputType("visible_password");
172 | } else {
173 | setInputType("textPassword");
174 | }
175 |
176 | setSelection(getEditText().getText().length());
177 | }
178 |
179 |
180 | public void setSelection(int position) {
181 | getEditText().setSelection(position);
182 | }
183 |
184 | public void setCustomKeyboardView(CumKeyboardContainer customKeyboardView) {
185 | mEditText.setCmbView(customKeyboardView);
186 | }
187 |
188 | public void listener() {
189 | mRightButton.setOnClickListener(new View.OnClickListener() {
190 |
191 | @Override
192 | public void onClick(View paramView) {
193 |
194 | }
195 | });
196 | mClearbtn.setOnClickListener(new View.OnClickListener() {
197 |
198 | @Override
199 | public void onClick(View paramView) {
200 | getEditText().setText("");
201 | getEditText().requestFocus();
202 | if (clearClick != null) {
203 | clearClick.clear();
204 | }
205 | }
206 | });
207 |
208 | }
209 |
210 | //初始化控件,设置初始监听事件
211 | private void setListener() {
212 | //禁用复制黏贴
213 | getEditText().setOnLongClickListener(new View.OnLongClickListener() {
214 | @Override
215 | public boolean onLongClick(View v) {
216 | return true;
217 | }
218 | });
219 | getEditText().setTextIsSelectable(false);
220 |
221 | getEditText().setCustomSelectionActionModeCallback(new ActionMode.Callback() {
222 |
223 | @Override
224 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
225 | return false;
226 | }
227 |
228 | @Override
229 | public void onDestroyActionMode(ActionMode mode) {
230 | }
231 |
232 | @Override
233 | public boolean onCreateActionMode(ActionMode mode, Menu menu) {
234 | return false;
235 | }
236 |
237 | @Override
238 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
239 | return false;
240 | }
241 | });
242 |
243 | ininWatch();
244 | }
245 |
246 |
247 | /**
248 | * 设置左边标签的宽,测量输入的字符串的宽
249 | */
250 | public void setLableWide(String source) {
251 | TextPaint paint = mLabel.getPaint();
252 | int width = (int) Layout.getDesiredWidth(source, 0, source.length(), paint);
253 | android.view.ViewGroup.LayoutParams params = mLabel.getLayoutParams();
254 | params.width = width;
255 | mLabel.setLayoutParams(params);
256 | }
257 |
258 | /**
259 | * 设置左边标签的宽,测量输入的字符串的宽
260 | *
261 | * @spaceWidth 是动态设置标签与输入框的间隔
262 | */
263 | public void setLableWide(String source, float spaceWidth) {
264 | TextPaint paint = mLabel.getPaint();
265 | int width = (int) Layout.getDesiredWidth(source, 0, source.length(), paint);
266 | android.view.ViewGroup.LayoutParams params = mLabel.getLayoutParams();
267 | params.width = width + MyApplication.instance().dip2px(spaceWidth);
268 | mLabel.setLayoutParams(params);
269 | }
270 |
271 | public int getLableWide(String source) {
272 | TextPaint paint = mLabel.getPaint();
273 | return (int) Layout.getDesiredWidth(source, 0, source.length(), paint);
274 | }
275 |
276 | public void setClear(boolean clear) {
277 | this.clear = clear;
278 | }
279 |
280 | /**
281 | * @return 获取输入框中文本
282 | */
283 | public String getText() {
284 | if (getEditText() == null) {
285 | return "";
286 | }
287 | return getEditText().getEditableText().toString();
288 | }
289 |
290 | public void setHit(String hit) {
291 | if (getEditText() != null) {
292 | getEditText().setHint(hit);
293 | }
294 | }
295 |
296 | public void setTextSize(int size) {
297 | if (getEditText() != null) {
298 | getEditText().setTextSize(size);
299 | }
300 | }
301 |
302 | public void setEditable(boolean editable) {
303 | if (getEditText() != null) {
304 | getEditText().setFocusable(editable);
305 | }
306 | }
307 |
308 | /**
309 | * @param text 向输入框中填写文本内容
310 | */
311 | public void setText(String text) {
312 | if (getEditText() != null) {
313 | getEditText().setText(text);
314 | }
315 | }
316 |
317 | public void setLength(int length) {
318 | getEditText().setFilters(new InputFilter[]{new InputFilter.LengthFilter(length)});
319 | }
320 |
321 | public void setActionListener(OnEditorActionListener actionListener) {
322 | getEditText().setOnEditorActionListener(actionListener);
323 | }
324 |
325 | public void enabled(boolean enabled) {
326 | if (getEditText() != null) {
327 | getEditText().setEnabled(enabled);
328 | if (enabled) {
329 | if (clear && getEditText().getText().length() > 0) {
330 | mClearbtn.setVisibility(View.VISIBLE);
331 | if (mEditListener != null) {
332 | mEditListener.onEdit(true);
333 | }
334 | } else {
335 | mClearbtn.setVisibility(View.GONE);
336 | if (mEditListener != null) {
337 | mEditListener.onEdit(false);
338 | }
339 | }
340 | } else {
341 | mClearbtn.setVisibility(View.GONE);
342 | if (mEditListener != null) {
343 | mEditListener.onEdit(false);
344 | }
345 | }
346 | }
347 | }
348 |
349 | public void showErrNote(String errMsg) {
350 | if (mErrNote.getVisibility() == VISIBLE) return;
351 |
352 | mErrNote.setText(errMsg);
353 | mErrNote.setVisibility(VISIBLE);
354 | startLeftAndRightShake(mErrNote);
355 | mErrNote.postDelayed(new Runnable() {
356 | @Override
357 | public void run() {
358 | if (nopeAnimator.isRunning())
359 | nopeAnimator.cancel();
360 | }
361 | }, 1000);
362 | mErrNote.postDelayed(new Runnable() {
363 | @Override
364 | public void run() {
365 | hideErrNote();
366 | }
367 | }, 2600);
368 | }
369 |
370 | public void hideErrNote() {
371 | mErrNote.setVisibility(GONE);
372 | }
373 |
374 | public void setLabelText(String text) {
375 | mLabel.setText(text);
376 | }
377 |
378 | public void setSelectClearORImage(boolean clearImage) {
379 | this.clearImage = clearImage;
380 | }
381 |
382 | public void setLeftText(String text) {
383 | mLeftTextView.setText(text);
384 | }
385 |
386 | public String getLeftText() {
387 | return mLeftTextView.getText().toString();
388 | }
389 |
390 | /**
391 | * 添加额外的TextWatcher方法
392 | * 注意watcher是一个列表
393 | *
394 | * @param watcher
395 | */
396 | public void addWatcher(TextWatcher watcher) {
397 | if (getEditText() != null) {
398 | getEditText().addTextChangedListener(watcher);
399 | }
400 | }
401 |
402 | private void ininWatch() {
403 | getEditText().addTextChangedListener(new TextWatcher() {
404 | @Override
405 | public void onTextChanged(CharSequence s, int start, int before, int count) {
406 | }
407 |
408 | @Override
409 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
410 | }
411 |
412 | @Override
413 | public void afterTextChanged(Editable s) {
414 | // 用户输入时,锁屏重新计时
415 | if (clear && getEditText().isEnabled() && getEditText().getText().length() > 0) {
416 | mClearbtn.setVisibility(View.VISIBLE);
417 | if (clearImage) {
418 | mRightButton.setVisibility(View.GONE);
419 | }
420 | if (mEditListener != null) {
421 | mEditListener.onEdit(true);
422 | }
423 | } else {
424 | mClearbtn.setVisibility(View.GONE);
425 | if (clearImage) {
426 | mRightButton.setVisibility(View.VISIBLE);
427 | }
428 | if (mEditListener != null) {
429 | mEditListener.onEdit(false);
430 | }
431 | }
432 | }
433 | });
434 | }
435 |
436 | public FocusChange getFocusChange() {
437 | return focusChange;
438 | }
439 |
440 | public void setFocusChange(FocusChange focusChange) {
441 | this.focusChange = focusChange;
442 | }
443 |
444 | public interface FocusChange {
445 | void onFocusChange(View v, boolean isFocused);
446 | }
447 |
448 | public void setOnEditListener(OnEditListener mEditListener) {
449 | this.mEditListener = mEditListener;
450 | }
451 |
452 | public interface OnEditListener {
453 | void onEdit(boolean comList);
454 | }
455 |
456 | public void setEditTextClick(View.OnClickListener l) {
457 | if (getEditText() != null) {
458 | getEditText().setOnClickListener(l);
459 | }
460 | }
461 |
462 | public void setRightBtnClick(View.OnClickListener l) {
463 | if (mRightButton != null) {
464 | mRightButton.setOnClickListener(l);
465 | }
466 | }
467 |
468 | public void setRightIamgeBg(int id) {
469 | if (mRightButton != null) {
470 | mRightButton.setBackgroundResource(id);
471 | }
472 | }
473 |
474 | public void setRightIamgeClick(View.OnClickListener l) {
475 | if (mRightButton != null) {
476 | mRightButton.setOnClickListener(l);
477 | }
478 | }
479 |
480 | public void setInputType(String type) {
481 | getEditText().setInputType(MyInputType.valueOf(type).getType());
482 | if ("visible_password".equals(type)) {
483 | mRightButton.setBackgroundResource(R.drawable.pwd_eye_open);
484 | }
485 | if ("textPassword".equals(type)) {
486 | mRightButton.setBackgroundResource(R.drawable.pwd_eye_close);
487 | }
488 | }
489 |
490 | enum MyInputType {
491 | number("number",
492 | InputType.TYPE_CLASS_NUMBER),
493 | numberpassword("numberpassword",
494 | InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
495 | email("email",
496 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
497 | visible_password("visible_password",
498 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
499 | textPassword("textPassword",
500 | InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
501 | numberDecimal("numberDecimal",
502 | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_CLASS_NUMBER),
503 | phone("phone",
504 | InputType.TYPE_CLASS_PHONE);
505 |
506 | private String typeName;
507 | private int type;
508 |
509 | MyInputType(String typeName, int type) {
510 | this.typeName = typeName;
511 | this.type = type;
512 | }
513 |
514 | public int getType() {
515 | return type;
516 | }
517 |
518 | public String getTypeName() {
519 | return typeName;
520 | }
521 | }
522 |
523 | public interface OnClik {
524 | void rightImageBtnOnClick();
525 | }
526 |
527 | private ClearClick clearClick;
528 |
529 | public interface ClearClick {
530 | void clear();
531 | }
532 |
533 |
534 | /**
535 | * 获取xml中属性length的 大小
536 | *
537 | * @return
538 | */
539 | public int getMaxLenth() {
540 | return maxlength;
541 | }
542 |
543 | public void setHorizontallyScrolling() {
544 | getEditText().setHorizontallyScrolling(false);
545 | }
546 |
547 | public EditText getEditText() {
548 | return mEditText;
549 | }
550 |
551 | public void setTextColor(int color) {
552 | if (mEditText != null) {
553 | mEditText.setTextColor(color);
554 | }
555 | }
556 |
557 | public void setLabelTextColor(int color) {
558 | if (mLabel != null) {
559 | mLabel.setTextColor(color);
560 | }
561 | }
562 |
563 | public void setHintTextColor(int color) {
564 | if (mEditText != null) {
565 | mEditText.setHintTextColor(color);
566 | }
567 | }
568 |
569 | /**
570 | * 设置单行or 多行显示
571 | */
572 | public void setEditTextSingleLine(boolean trueorfalse) {
573 | mEditText.setSingleLine(trueorfalse);
574 | }
575 |
576 | /**
577 | * 更换软键盘的换行键为那种方式 及设置显示的文字
578 | */
579 | public void setIme(int imeOptions, String labText) {
580 | mEditText.setImeOptions(imeOptions);
581 | mEditText.setImeActionLabel(labText, imeOptions);
582 | }
583 |
584 | public ClearClick getClearClick() {
585 | return clearClick;
586 | }
587 |
588 | public void setClearClick(ClearClick clearClick) {
589 | this.clearClick = clearClick;
590 | }
591 |
592 | public void setLeftImage(String url) {
593 | if ("".equals(url)) {
594 | mLeftImage.setVisibility(VISIBLE);
595 | mLeftImage.downloadCache2Sd(url);
596 | }
597 | }
598 |
599 | /**
600 | * 获得光标位置
601 | */
602 | public int getSelectionStart() {
603 | return getEditText().getSelectionStart();
604 | }
605 |
606 | public int getSelectionEnd() {
607 | return getEditText().getSelectionEnd();
608 | }
609 |
610 | public Editable getEditableText() {
611 | if (getEditText() != null) {
612 | return getEditText().getEditableText();
613 | }
614 | return null;
615 | }
616 |
617 | public AsyncImageView getLeftImage() {
618 | return mLeftImage;
619 | }
620 |
621 |
622 | /**
623 | * 开始左右抖动
624 | */
625 | private void startLeftAndRightShake(View view) {
626 | nopeAnimator = nope(view);
627 | nopeAnimator.setRepeatCount(ValueAnimator.INFINITE);
628 | nopeAnimator.setDuration(1000);
629 | nopeAnimator.start();
630 | }
631 |
632 | /**
633 | * 左右抖动动画 * * @param view * @return
634 | */
635 | public static ObjectAnimator nope(View view) {
636 | int delta = view.getResources().getDimensionPixelOffset(R.dimen.spacing_medium);
637 | PropertyValuesHolder pvhTranslateX = PropertyValuesHolder.ofKeyframe(View.TRANSLATION_X,
638 | Keyframe.ofFloat(0f, 0),
639 | Keyframe.ofFloat(.10f, -delta),
640 | Keyframe.ofFloat(.26f, delta),
641 | Keyframe.ofFloat(.42f, -delta),
642 | Keyframe.ofFloat(.58f, delta),
643 | Keyframe.ofFloat(.74f, -delta),
644 | Keyframe.ofFloat(.90f, delta),
645 | Keyframe.ofFloat(1f, 0f)
646 | );
647 | return ObjectAnimator.ofPropertyValuesHolder(view, pvhTranslateX).setDuration(500);
648 | }
649 |
650 | }
651 |
--------------------------------------------------------------------------------