├── .gitignore ├── README.md ├── VerificationCodeInputView_Lib ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── wynsbin │ │ └── vciv │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── wynsbin │ │ │ └── vciv │ │ │ ├── AsteriskPasswordTransformationMethod.java │ │ │ ├── DensityUtils.java │ │ │ ├── SoftInputUtils.java │ │ │ └── VerificationCodeInputView.java │ └── res │ │ ├── drawable │ │ ├── vciv_et_cursor.xml │ │ └── vciv_paste_bg.xml │ │ └── values │ │ ├── attrs.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── wynsbin │ └── vciv │ └── ExampleUnitTest.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── wynsbin │ │ └── verificationcodeinputview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── wynsbin │ │ │ └── verificationcodeinputview │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── vciv_et_bg.xml │ │ └── vciv_et_focus_bg.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── wynsbin │ └── verificationcodeinputview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | .idea 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![这里写图片描述](https://github.com/Wynsbin/Screenshots/blob/master/VerificationCodeInputView/VerificationCodeInputView.png) 4 | 5 |
6 | 7 | # VerificationCodeInputView -- 自定义验证码输入框 8 | 9 | [![这里写图片描述](https://jitpack.io/v/Wynsbin/VerificationCodeInputView.svg)](https://jitpack.io/#Wynsbin/VerificationCodeInputView) 10 | 11 | **验证码输入框** 12 | 13 | 1、能自定义输入框个数和样式 14 | 15 | 2、支持长按粘贴或剪切板内容自动填充(数字模式时,如果剪切板第一个条不是数字,长按输入框不会弹出粘贴窗) 16 | 17 | ## Demo 18 | 19 | ![这里写图片描述](https://github.com/Wynsbin/Screenshots/blob/master/VerificationCodeInputView/vciv_oneself.gif) 20 | 21 | ## Principle 22 | 23 | 大致是Edittext + n* TextView,然后设置edittext字体跟背景颜色都为透明,隐藏光标 24 | 25 | Edittext:监听edittext每次输入一个字符就赋值到对应的TextView上,然后在清空自己 26 | 27 | 下划线:在TextView下面添加View 28 | 29 | 光标:这里的每个TextView的焦点光标其实对View设置了ValueAnimator 30 | 31 | 粘贴:粘贴弹窗是自定义的PopupWindow 32 | 33 | ## Gradle 34 | 35 | Step 1. Add it in your root build.gradle at the end of repositories: 36 | 37 | ``` 38 | allprojects { 39 | repositories { 40 | ... 41 | maven { url 'https://jitpack.io' } 42 | } 43 | } 44 | ``` 45 | 46 | Step 2. Add the dependency: 47 | 48 | ``` 49 | dependencies { 50 | implementation 'com.github.Wynsbin:VerificationCodeInputView:1.0.2' 51 | } 52 | ``` 53 | 54 | 55 | ## How to use 56 | 57 | ### In layout 58 | 59 | ``` 60 | 80 | ``` 81 | 82 | ### In Java Code 83 | 84 | ``` 85 | VerificationCodeInputView view = findViewById(R.id.vciv_code); 86 | view.setOnInputListener(new VerificationCodeInputView.OnInputListener() { 87 | @Override 88 | public void onComplete(String code) { 89 | Toast.makeText(MainActivity.this, code, Toast.LENGTH_SHORT).show(); 90 | } 91 | 92 | @Override 93 | public void onInput() { 94 | 95 | } 96 | }); 97 | 98 | //清除验证码 99 | view.clearCode(); 100 | ``` 101 | 102 | ## Attributes 103 | 104 | |name|describe|format|default| 105 | |:--|:--|:--|:--:| 106 | |vciv_et_number|输入框的数量|integer|4| 107 | |vciv_et_inputType|输入框输入类型|enum|数字模式| 108 | |vciv_et_width|输入框的宽度|dimension|40dp| 109 | |vciv_et_height|输入框的高度|dimension|40dp| 110 | |vciv_et_text_color|输入框文字颜色|color|Color.BLACK| 111 | |vciv_et_text_size|输入框文字大小|dimension|14sp| 112 | |vciv_et_spacing|输入框间距,不输入则代表平分|dimension|| 113 | |vciv_et_background|输入框背景色|reference&color|Color.WHITE| 114 | |vciv_et_foucs_background|输入框焦点背景色,不输入代表不设置|reference&color|| 115 | |vciv_et_cursor_width|输入框焦点宽度|dimension|2dp| 116 | |vciv_et_cursor_height|输入框焦点高度|dimension|30dp| 117 | |vciv_et_cursor_color|输入框焦点颜色|color|#C3C3C3| 118 | |vciv_et_underline_height|输入框下划线高度|dimension|1dp| 119 | |vciv_et_underline_default_color|输入框无焦点的下划线颜色|color|#F0F0F0| 120 | |vciv_et_underline_focus_color|输入框有焦点的下划线颜色|color|#C3C3C3| 121 | |vciv_et_underline_show|输入框下划线是否展示|boolean|false| 122 | 123 | 124 | ### VCInputType 125 | 126 | |name|describe 127 | |:--|:--| 128 | |number|数字模式| 129 | |numberPassword|数字密码模式| 130 | |text|字符模式| 131 | |textPassword|字符密码模式| 132 | 133 | 134 | ### vciv_et_background&vciv_et_foucs_background 135 | 136 | 1、@drawable/xxx 137 | 138 | 2、@color/xxx 139 | 140 | 3、#xxxxxx 141 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.1" 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 19 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles 'consumer-rules.pro' 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | dataBinding { 26 | enabled = true 27 | } 28 | 29 | compileOptions { 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | } 33 | 34 | } 35 | 36 | dependencies { 37 | implementation fileTree(dir: 'libs', include: ['*.jar']) 38 | 39 | implementation 'androidx.appcompat:appcompat:1.1.0' 40 | testImplementation 'junit:junit:4.12' 41 | androidTestImplementation 'androidx.test:runner:1.2.0' 42 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 43 | } 44 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wynsbin/VerificationCodeInputView/a8e21276bf866557b14914927b8e8cae87bfa765/VerificationCodeInputView_Lib/consumer-rules.pro -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/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 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/androidTest/java/com/wynsbin/vciv/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.wynsbin.vciv; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.wynsbin.verificationcodeinputview_lib.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/java/com/wynsbin/vciv/AsteriskPasswordTransformationMethod.java: -------------------------------------------------------------------------------- 1 | package com.wynsbin.vciv; 2 | 3 | import android.text.method.PasswordTransformationMethod; 4 | import android.view.View; 5 | 6 | /** 7 | * Description : 修改密码字符串改变的等待时间 8 | * 9 | * @author WSoban 10 | * @date 2019/9/3 11 | */ 12 | public class AsteriskPasswordTransformationMethod extends PasswordTransformationMethod { 13 | @Override 14 | public CharSequence getTransformation(CharSequence source, View view) { 15 | return new PasswordCharSequence(source); 16 | } 17 | 18 | private class PasswordCharSequence implements CharSequence { 19 | private CharSequence mSource; 20 | 21 | public PasswordCharSequence(CharSequence source) { 22 | mSource = source; // Store char sequence 23 | } 24 | 25 | @Override 26 | public char charAt(int index) { 27 | return '•'; // This is the important part 28 | } 29 | 30 | @Override 31 | public int length() { 32 | return mSource.length(); // Return default 33 | } 34 | 35 | @Override 36 | public CharSequence subSequence(int start, int end) { 37 | return mSource.subSequence(start, end); // Return default 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/java/com/wynsbin/vciv/DensityUtils.java: -------------------------------------------------------------------------------- 1 | package com.wynsbin.vciv; 2 | 3 | import android.content.Context; 4 | import android.util.TypedValue; 5 | 6 | /** 7 | * Description : 常用单位转换的辅助类 8 | * 9 | * @author WSoban 10 | * @date 2019/10/15 11 | */ 12 | public class DensityUtils { 13 | private DensityUtils() { 14 | /* cannot be instantiated */ 15 | throw new UnsupportedOperationException("cannot be instantiated"); 16 | } 17 | 18 | /** 19 | * dp转px 20 | * 21 | * @param context 22 | * @param dpVal 23 | * @return 24 | */ 25 | public static int dp2px(Context context, float dpVal) { 26 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 27 | dpVal, context.getResources().getDisplayMetrics()); 28 | } 29 | 30 | /** 31 | * sp转px 32 | * 33 | * @param context 34 | * @param spVal 35 | * @return 36 | */ 37 | public static int sp2px(Context context, float spVal) { 38 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 39 | spVal, context.getResources().getDisplayMetrics()); 40 | } 41 | 42 | /** 43 | * px转dp 44 | * 45 | * @param context 46 | * @param pxVal 47 | * @return 48 | */ 49 | public static float px2dp(Context context, float pxVal) { 50 | final float scale = context.getResources().getDisplayMetrics().density; 51 | return (pxVal / scale); 52 | } 53 | 54 | /** 55 | * px转sp 56 | * 57 | * @param context 58 | * @param pxVal 59 | * @return 60 | */ 61 | public static float px2sp(Context context, float pxVal) { 62 | return (pxVal / context.getResources().getDisplayMetrics().scaledDensity); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/java/com/wynsbin/vciv/SoftInputUtils.java: -------------------------------------------------------------------------------- 1 | package com.wynsbin.vciv; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.view.View; 6 | import android.view.inputmethod.InputMethodManager; 7 | 8 | /** 9 | * Description : 键盘辅助类 10 | * 11 | * @author WSoban 12 | * @date 2019/10/15 13 | */ 14 | public class SoftInputUtils { 15 | 16 | public static void toggleSoftInput(Context context) { 17 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 18 | imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); 19 | } 20 | 21 | public static void showSoftInput(Context context, View view) { 22 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 23 | imm.showSoftInput(view, InputMethodManager.SHOW_FORCED); 24 | } 25 | 26 | public static void hideSoftInput(Context context, View view) { 27 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 28 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); //强制隐藏键盘 29 | } 30 | 31 | public static void hideSoftInput(Activity activity) { 32 | InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 33 | if (imm != null) { 34 | imm.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/java/com/wynsbin/vciv/VerificationCodeInputView.java: -------------------------------------------------------------------------------- 1 | package com.wynsbin.vciv; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.animation.ValueAnimator; 5 | import android.app.Activity; 6 | import android.content.ClipData; 7 | import android.content.ClipDescription; 8 | import android.content.ClipboardManager; 9 | import android.content.Context; 10 | import android.content.res.TypedArray; 11 | import android.graphics.Color; 12 | import android.graphics.drawable.ColorDrawable; 13 | import android.text.Editable; 14 | import android.text.InputType; 15 | import android.text.TextUtils; 16 | import android.text.TextWatcher; 17 | import android.util.AttributeSet; 18 | import android.util.TypedValue; 19 | import android.view.Gravity; 20 | import android.view.KeyEvent; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.widget.EditText; 24 | import android.widget.LinearLayout; 25 | import android.widget.PopupWindow; 26 | import android.widget.RelativeLayout; 27 | import android.widget.TextView; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | import java.util.regex.Matcher; 32 | import java.util.regex.Pattern; 33 | 34 | /** 35 | * Description : 验证码输入框 36 | *

37 | * 支持粘贴功能 38 | * 39 | * @author WSoban 40 | * @date 2019/10/9 41 | */ 42 | public class VerificationCodeInputView extends RelativeLayout { 43 | 44 | private Context mContext; 45 | private OnInputListener onInputListener; 46 | 47 | private LinearLayout mLinearLayout; 48 | private RelativeLayout[] mRelativeLayouts; 49 | private TextView[] mTextViews; 50 | private View[] mUnderLineViews; 51 | private View[] mCursorViews; 52 | private EditText mEditText; 53 | private PopupWindow mPopupWindow; 54 | private ValueAnimator valueAnimator; 55 | 56 | private List mCodes = new ArrayList<>(); 57 | 58 | /** 59 | * 输入框数量 60 | */ 61 | private int mEtNumber; 62 | 63 | /** 64 | * 输入框类型 65 | */ 66 | private VCInputType mEtInputType; 67 | 68 | /** 69 | * 输入框的宽度 70 | */ 71 | private int mEtWidth; 72 | 73 | /** 74 | * 输入框的高度 75 | */ 76 | private int mEtHeight; 77 | 78 | /** 79 | * 文字颜色 80 | */ 81 | private int mEtTextColor; 82 | 83 | /** 84 | * 文字大小 85 | */ 86 | private float mEtTextSize; 87 | 88 | /** 89 | * 输入框间距 90 | */ 91 | private int mEtSpacing; 92 | 93 | /** 94 | * 平分后的间距 95 | */ 96 | private int mEtBisectSpacing; 97 | 98 | /** 99 | * 判断是否平分,默认平分 100 | */ 101 | private boolean isBisect; 102 | 103 | /** 104 | * 输入框宽度 105 | */ 106 | private int mViewWidth; 107 | 108 | /** 109 | * 下划线默认颜色,焦点颜色,高度,是否展示 110 | */ 111 | private int mEtUnderLineDefaultColor; 112 | private int mEtUnderLineFocusColor; 113 | private int mEtUnderLineHeight; 114 | private boolean mEtUnderLineShow; 115 | 116 | /** 117 | * 光标宽高,颜色 118 | */ 119 | private int mEtCursorWidth; 120 | private int mEtCursorHeight; 121 | private int mEtCursorColor; 122 | /** 123 | * 输入框的背景色、焦点背景色、是否有焦点背景色 124 | */ 125 | private int mEtBackground; 126 | private int mEtFocusBackground; 127 | private boolean isFocusBackgroud; 128 | 129 | public enum VCInputType { 130 | /** 131 | * 数字类型 132 | */ 133 | NUMBER, 134 | /** 135 | * 数字密码 136 | */ 137 | NUMBERPASSWORD, 138 | /** 139 | * 文字 140 | */ 141 | TEXT, 142 | /** 143 | * 文字密码 144 | */ 145 | TEXTPASSWORD, 146 | } 147 | 148 | public VerificationCodeInputView(Context context) { 149 | super(context); 150 | init(context, null); 151 | } 152 | 153 | public VerificationCodeInputView(Context context, AttributeSet attrs) { 154 | super(context, attrs); 155 | init(context, attrs); 156 | } 157 | 158 | public VerificationCodeInputView(Context context, AttributeSet attrs, int defStyleAttr) { 159 | super(context, attrs, defStyleAttr); 160 | init(context, attrs); 161 | } 162 | 163 | private void init(Context context, AttributeSet attrs) { 164 | this.mContext = context; 165 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerificationCodeInputView); 166 | mEtNumber = typedArray.getInteger(R.styleable.VerificationCodeInputView_vciv_et_number, 4); 167 | int inputType = typedArray.getInt(R.styleable.VerificationCodeInputView_vciv_et_inputType, VCInputType.NUMBER.ordinal()); 168 | mEtInputType = VCInputType.values()[inputType]; 169 | mEtWidth = typedArray.getDimensionPixelSize(R.styleable.VerificationCodeInputView_vciv_et_width, DensityUtils.dp2px(context, 40)); 170 | mEtHeight = typedArray.getDimensionPixelSize(R.styleable.VerificationCodeInputView_vciv_et_height, DensityUtils.dp2px(context, 40)); 171 | mEtTextColor = typedArray.getColor(R.styleable.VerificationCodeInputView_vciv_et_text_color, Color.BLACK); 172 | mEtTextSize = typedArray.getDimensionPixelSize(R.styleable.VerificationCodeInputView_vciv_et_text_size, DensityUtils.sp2px(context, 14)); 173 | mEtBackground = typedArray.getResourceId(R.styleable.VerificationCodeInputView_vciv_et_background, -1); 174 | if (mEtBackground < 0) { 175 | mEtBackground = typedArray.getColor(R.styleable.VerificationCodeInputView_vciv_et_background, Color.WHITE); 176 | } 177 | isFocusBackgroud = typedArray.hasValue(R.styleable.VerificationCodeInputView_vciv_et_foucs_background); 178 | mEtFocusBackground = typedArray.getResourceId(R.styleable.VerificationCodeInputView_vciv_et_foucs_background, -1); 179 | if (mEtFocusBackground < 0) { 180 | mEtFocusBackground = typedArray.getColor(R.styleable.VerificationCodeInputView_vciv_et_foucs_background, Color.WHITE); 181 | } 182 | isBisect = typedArray.hasValue(R.styleable.VerificationCodeInputView_vciv_et_spacing); 183 | if (isBisect) { 184 | mEtSpacing = typedArray.getDimensionPixelSize(R.styleable.VerificationCodeInputView_vciv_et_spacing, 0); 185 | } 186 | mEtCursorWidth = typedArray.getDimensionPixelOffset(R.styleable.VerificationCodeInputView_vciv_et_cursor_width, DensityUtils.dp2px(context, 2)); 187 | mEtCursorHeight = typedArray.getDimensionPixelOffset(R.styleable.VerificationCodeInputView_vciv_et_cursor_height, DensityUtils.dp2px(context, 30)); 188 | mEtCursorColor = typedArray.getColor(R.styleable.VerificationCodeInputView_vciv_et_cursor_color, Color.parseColor("#C3C3C3")); 189 | mEtUnderLineHeight = typedArray.getDimensionPixelOffset(R.styleable.VerificationCodeInputView_vciv_et_underline_height, DensityUtils.dp2px(context, 1)); 190 | mEtUnderLineDefaultColor = typedArray.getColor(R.styleable.VerificationCodeInputView_vciv_et_underline_default_color, Color.parseColor("#F0F0F0")); 191 | mEtUnderLineFocusColor = typedArray.getColor(R.styleable.VerificationCodeInputView_vciv_et_underline_focus_color, Color.parseColor("#C3C3C3")); 192 | mEtUnderLineShow = typedArray.getBoolean(R.styleable.VerificationCodeInputView_vciv_et_underline_show, false); 193 | initView(); 194 | typedArray.recycle(); 195 | } 196 | 197 | private void initView() { 198 | mRelativeLayouts = new RelativeLayout[mEtNumber]; 199 | mTextViews = new TextView[mEtNumber]; 200 | mUnderLineViews = new View[mEtNumber]; 201 | mCursorViews = new View[mEtNumber]; 202 | 203 | mLinearLayout = new LinearLayout(mContext); 204 | mLinearLayout.setOrientation(LinearLayout.HORIZONTAL); 205 | mLinearLayout.setGravity(Gravity.CENTER_HORIZONTAL); 206 | mLinearLayout.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 207 | for (int i = 0; i < mEtNumber; i++) { 208 | RelativeLayout relativeLayout = new RelativeLayout(mContext); 209 | relativeLayout.setLayoutParams(getEtLayoutParams(i)); 210 | setEtBackground(relativeLayout, mEtBackground); 211 | mRelativeLayouts[i] = relativeLayout; 212 | 213 | TextView textView = new TextView(mContext); 214 | initTextView(textView); 215 | relativeLayout.addView(textView); 216 | mTextViews[i] = textView; 217 | 218 | View cursorView = new View(mContext); 219 | initCursorView(cursorView); 220 | relativeLayout.addView(cursorView); 221 | mCursorViews[i] = cursorView; 222 | 223 | if (mEtUnderLineShow) { 224 | View underLineView = new View(mContext); 225 | initUnderLineView(underLineView); 226 | relativeLayout.addView(underLineView); 227 | mUnderLineViews[i] = underLineView; 228 | } 229 | mLinearLayout.addView(relativeLayout); 230 | } 231 | addView(mLinearLayout); 232 | mEditText = new EditText(mContext); 233 | initEdittext(mEditText); 234 | addView(mEditText); 235 | setCursorColor(); 236 | } 237 | 238 | private void initTextView(TextView textView) { 239 | RelativeLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 240 | textView.setLayoutParams(lp); 241 | textView.setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER); 242 | textView.setGravity(Gravity.CENTER); 243 | textView.setTextColor(mEtTextColor); 244 | textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mEtTextSize); 245 | setInputType(textView); 246 | textView.setPadding(0, 0, 0, 0); 247 | } 248 | 249 | private void initCursorView(View view) { 250 | RelativeLayout.LayoutParams layoutParams = new LayoutParams(mEtCursorWidth, mEtCursorHeight); 251 | layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); 252 | view.setLayoutParams(layoutParams); 253 | } 254 | 255 | private void initUnderLineView(View view) { 256 | RelativeLayout.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mEtUnderLineHeight); 257 | layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); 258 | view.setLayoutParams(layoutParams); 259 | view.setBackgroundColor(mEtUnderLineDefaultColor); 260 | } 261 | 262 | private void initEdittext(EditText editText) { 263 | LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 264 | layoutParams.addRule(RelativeLayout.ALIGN_TOP, mLinearLayout.getId()); 265 | layoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mLinearLayout.getId()); 266 | editText.setLayoutParams(layoutParams); 267 | setInputType(editText); 268 | editText.setBackgroundColor(Color.TRANSPARENT); 269 | editText.setTextColor(Color.TRANSPARENT); 270 | editText.setCursorVisible(false); 271 | editText.addTextChangedListener(new TextWatcher() { 272 | @Override 273 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 274 | 275 | } 276 | 277 | @Override 278 | public void onTextChanged(CharSequence s, int start, int before, int count) { 279 | 280 | } 281 | 282 | @Override 283 | public void afterTextChanged(Editable editable) { 284 | if (editable != null && editable.length() > 0) { 285 | mEditText.setText(""); 286 | setCode(editable.toString()); 287 | } 288 | } 289 | }); 290 | // 监听验证码删除按键 291 | editText.setOnKeyListener((view, keyCode, keyEvent) -> { 292 | if (keyCode == KeyEvent.KEYCODE_DEL && keyEvent.getAction() == KeyEvent.ACTION_DOWN && mCodes.size() > 0) { 293 | mCodes.remove(mCodes.size() - 1); 294 | showCode(); 295 | return true; 296 | } 297 | return false; 298 | }); 299 | editText.setOnLongClickListener(v -> { 300 | showPaste(); 301 | return false; 302 | }); 303 | getEtFocus(editText); 304 | } 305 | 306 | private void initPopupWindow() { 307 | mPopupWindow = new PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 308 | TextView tv = new TextView(mContext); 309 | tv.setText("粘贴"); 310 | tv.setTextSize(14.0f); 311 | tv.setTextColor(Color.BLACK); 312 | tv.setBackgroundResource(R.drawable.vciv_paste_bg); 313 | tv.setPadding(30, 10, 30, 10); 314 | tv.setOnClickListener(v -> { 315 | setCode(getClipboardString()); 316 | mPopupWindow.dismiss(); 317 | }); 318 | mPopupWindow.setContentView(tv); 319 | mPopupWindow.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);// 设置菜单的宽度 320 | mPopupWindow.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT); 321 | mPopupWindow.setFocusable(true);// 获取焦点 322 | mPopupWindow.setTouchable(true); // 设置PopupWindow可触摸 323 | mPopupWindow.setOutsideTouchable(true); // 设置非PopupWindow区域可触摸 324 | //设置点击隐藏popwindow 325 | ColorDrawable dw = new ColorDrawable(Color.TRANSPARENT); 326 | mPopupWindow.setBackgroundDrawable(dw); 327 | } 328 | 329 | private void setEtBackground(RelativeLayout rl, int background) { 330 | if (background > 0) { 331 | rl.setBackgroundResource(background); 332 | } else { 333 | rl.setBackgroundColor(background); 334 | } 335 | } 336 | 337 | private String getClipboardString() { 338 | ClipboardManager clipboardManager = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); 339 | //获取剪贴板中第一条数据 340 | if (clipboardManager != null && clipboardManager.hasPrimaryClip() && clipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { 341 | ClipData.Item itemAt = clipboardManager.getPrimaryClip().getItemAt(0); 342 | if (!(itemAt == null || TextUtils.isEmpty(itemAt.getText()))) { 343 | return itemAt.getText().toString(); 344 | } 345 | } 346 | return null; 347 | } 348 | 349 | private LinearLayout.LayoutParams getEtLayoutParams(int i) { 350 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mEtWidth, mEtHeight); 351 | int spacing; 352 | if (!isBisect) { 353 | spacing = mEtBisectSpacing / 2; 354 | } else { 355 | spacing = mEtSpacing / 2; 356 | //如果大于最大平分数,将设为最大值 357 | if (mEtSpacing > mEtBisectSpacing) { 358 | spacing = mEtBisectSpacing / 2; 359 | } 360 | } 361 | if (i == 0) { 362 | layoutParams.leftMargin = 0; 363 | layoutParams.rightMargin = spacing; 364 | } else if (i == mEtNumber - 1) { 365 | layoutParams.leftMargin = spacing; 366 | layoutParams.rightMargin = 0; 367 | } else { 368 | layoutParams.leftMargin = spacing; 369 | layoutParams.rightMargin = spacing; 370 | } 371 | return layoutParams; 372 | } 373 | 374 | private void setInputType(TextView textView) { 375 | switch (mEtInputType) { 376 | case NUMBERPASSWORD: 377 | textView.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); 378 | textView.setTransformationMethod(new AsteriskPasswordTransformationMethod()); 379 | break; 380 | case TEXT: 381 | textView.setInputType(InputType.TYPE_CLASS_TEXT); 382 | break; 383 | case TEXTPASSWORD: 384 | textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_NUMBER_VARIATION_PASSWORD); 385 | textView.setTransformationMethod(new AsteriskPasswordTransformationMethod()); 386 | break; 387 | default: 388 | textView.setInputType(InputType.TYPE_CLASS_NUMBER); 389 | break; 390 | } 391 | } 392 | 393 | /** 394 | * 展示自定义的粘贴板 395 | */ 396 | private void showPaste() { 397 | //去除输入框为数字模式,但粘贴板不是数字模式 398 | if ((mEtInputType == VCInputType.NUMBER || mEtInputType == VCInputType.NUMBERPASSWORD) && !isNumeric(getClipboardString())) { 399 | return; 400 | } 401 | if (!TextUtils.isEmpty(getClipboardString())) { 402 | if (mPopupWindow == null) { 403 | initPopupWindow(); 404 | } 405 | mPopupWindow.showAsDropDown(mTextViews[0], 0, 20); 406 | SoftInputUtils.hideSoftInput((Activity) getContext()); 407 | } 408 | } 409 | 410 | /** 411 | * 判断粘贴板上的是不是数字 412 | * 413 | * @param str 414 | * @return 415 | */ 416 | private boolean isNumeric(String str) { 417 | if (TextUtils.isEmpty(str)) { 418 | return false; 419 | } 420 | Pattern pattern = Pattern.compile("[0-9]*"); 421 | Matcher isNum = pattern.matcher(str); 422 | if (!isNum.matches()) { 423 | return false; 424 | } 425 | return true; 426 | } 427 | 428 | private void setCode(String code) { 429 | if (TextUtils.isEmpty(code)) { 430 | return; 431 | } 432 | for (int i = 0; i < code.length(); i++) { 433 | if (mCodes.size() < mEtNumber) { 434 | mCodes.add(String.valueOf(code.charAt(i))); 435 | } 436 | } 437 | showCode(); 438 | } 439 | 440 | private void showCode() { 441 | for (int i = 0; i < mEtNumber; i++) { 442 | TextView textView = mTextViews[i]; 443 | if (mCodes.size() > i) { 444 | textView.setText(mCodes.get(i)); 445 | } else { 446 | textView.setText(""); 447 | } 448 | } 449 | setCursorColor();//设置高亮跟光标颜色 450 | setCallBack();//回调 451 | } 452 | 453 | /** 454 | * 设置焦点输入框底部线、光标颜色、背景色 455 | */ 456 | private void setCursorColor() { 457 | if (valueAnimator != null) { 458 | valueAnimator.cancel(); 459 | } 460 | for (int i = 0; i < mEtNumber; i++) { 461 | View cursorView = mCursorViews[i]; 462 | cursorView.setBackgroundColor(Color.TRANSPARENT); 463 | 464 | if (mEtUnderLineShow) { 465 | View underLineView = mUnderLineViews[i]; 466 | underLineView.setBackgroundColor(mEtUnderLineDefaultColor); 467 | } 468 | if (isFocusBackgroud) { 469 | setEtBackground(mRelativeLayouts[i], mEtBackground); 470 | } 471 | } 472 | if (mCodes.size() < mEtNumber) { 473 | setCursorView(mCursorViews[mCodes.size()]); 474 | if (mEtUnderLineShow) { 475 | mUnderLineViews[mCodes.size()].setBackgroundColor(mEtUnderLineFocusColor); 476 | } 477 | if (isFocusBackgroud) { 478 | setEtBackground(mRelativeLayouts[mCodes.size()], mEtFocusBackground); 479 | } 480 | } 481 | } 482 | 483 | /** 484 | * 设置焦点色变换动画 485 | * 486 | * @param view 487 | */ 488 | private void setCursorView(View view) { 489 | this.valueAnimator = ObjectAnimator.ofInt(view, "backgroundColor", mEtCursorColor, android.R.color.transparent); 490 | this.valueAnimator.setDuration(1500); 491 | this.valueAnimator.setRepeatCount(-1); 492 | this.valueAnimator.setRepeatMode(ValueAnimator.RESTART); 493 | this.valueAnimator.setEvaluator((fraction, startValue, endValue) -> fraction <= 0.5f ? startValue : endValue); 494 | this.valueAnimator.start(); 495 | } 496 | 497 | private void setCallBack() { 498 | if (onInputListener == null) { 499 | return; 500 | } 501 | if (mCodes.size() == mEtNumber) { 502 | onInputListener.onComplete(getCode()); 503 | } else { 504 | onInputListener.onInput(); 505 | } 506 | } 507 | 508 | /** 509 | * 获得验证码 510 | * 511 | * @return 验证码 512 | */ 513 | private String getCode() { 514 | StringBuilder sb = new StringBuilder(); 515 | for (String code : mCodes) { 516 | sb.append(code); 517 | } 518 | return sb.toString(); 519 | } 520 | 521 | /** 522 | * 清空验证码 523 | */ 524 | public void clearCode() { 525 | mCodes.clear(); 526 | showCode(); 527 | } 528 | 529 | @Override 530 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 531 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 532 | mViewWidth = getMeasuredWidth(); 533 | updateETMargin(); 534 | } 535 | 536 | private void updateETMargin() { 537 | //平分Margin,把第一个TextView跟最后一个TextView的间距同设为平分 538 | mEtBisectSpacing = (mViewWidth - mEtNumber * mEtWidth) / (mEtNumber - 1); 539 | for (int i = 0; i < mEtNumber; i++) { 540 | mLinearLayout.getChildAt(i).setLayoutParams(getEtLayoutParams(i)); 541 | } 542 | } 543 | 544 | private void getEtFocus(EditText editText) { 545 | editText.setFocusable(true); 546 | editText.setFocusableInTouchMode(true); 547 | editText.requestFocus(); 548 | SoftInputUtils.showSoftInput(getContext(), editText); 549 | } 550 | 551 | @Override 552 | protected void onDetachedFromWindow() { 553 | super.onDetachedFromWindow(); 554 | SoftInputUtils.hideSoftInput((Activity) getContext()); 555 | if (valueAnimator != null) { 556 | valueAnimator.cancel(); 557 | } 558 | } 559 | 560 | //定义回调 561 | public interface OnInputListener { 562 | void onComplete(String code); 563 | 564 | void onInput(); 565 | } 566 | 567 | public void setOnInputListener(OnInputListener onInputListener) { 568 | this.onInputListener = onInputListener; 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/res/drawable/vciv_et_cursor.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/res/drawable/vciv_paste_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | VerificationCodeInputView_Lib 3 | 4 | -------------------------------------------------------------------------------- /VerificationCodeInputView_Lib/src/test/java/com/wynsbin/vciv/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.wynsbin.vciv; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.1" 6 | defaultConfig { 7 | applicationId "com.wynsbin.verificationcodeinputview" 8 | minSdkVersion 19 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | dataBinding { 22 | enabled = true 23 | } 24 | 25 | compileOptions { 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation 'androidx.appcompat:appcompat:1.1.0' 34 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'androidx.test:runner:1.2.0' 37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 38 | 39 | // implementation 'com.github.Wynsbin:VerificationCodeInputView:1.0.2' 40 | api project(':VerificationCodeInputView_Lib') 41 | } 42 | -------------------------------------------------------------------------------- /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/androidTest/java/com/wynsbin/verificationcodeinputview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.wynsbin.verificationcodeinputview; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.wynsbin.verificationcodeinputview", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/wynsbin/verificationcodeinputview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wynsbin.verificationcodeinputview; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | import com.wynsbin.vciv.VerificationCodeInputView; 10 | 11 | public class MainActivity extends AppCompatActivity implements VerificationCodeInputView.OnInputListener { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | 18 | VerificationCodeInputView verificationCodeInputView1 = findViewById(R.id.vciv_code1); 19 | VerificationCodeInputView verificationCodeInputView2 = findViewById(R.id.vciv_code2); 20 | VerificationCodeInputView verificationCodeInputView3 = findViewById(R.id.vciv_code3); 21 | verificationCodeInputView1.setOnInputListener(this); 22 | verificationCodeInputView2.setOnInputListener(this); 23 | verificationCodeInputView3.setOnInputListener(this); 24 | findViewById(R.id.btn_clear).setOnClickListener(view -> { 25 | verificationCodeInputView1.clearCode(); 26 | verificationCodeInputView2.clearCode(); 27 | verificationCodeInputView3.clearCode(); 28 | }); 29 | } 30 | 31 | @Override 32 | public void onComplete(String code) { 33 | Toast.makeText(MainActivity.this, code, Toast.LENGTH_SHORT).show(); 34 | } 35 | 36 | @Override 37 | public void onInput() { 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vciv_et_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vciv_et_focus_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 20 | 21 | 41 | 42 | 63 | 64 | 84 | 85 |