├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── airsaid │ │ └── inputcodelayout │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── airsaid │ │ │ └── inputcodelayout │ │ │ ├── MainActivity.java │ │ │ └── widget │ │ │ └── InputCodeLayout.java │ └── res │ │ ├── drawable │ │ ├── bg_edit_focus.xml │ │ └── bg_edit_un_focus.xml │ │ ├── layout │ │ └── activity_main.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 │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── github │ └── airsaid │ └── inputcodelayout │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── preview └── preview.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | /.idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InputCodeLayout 2 | 这是一个 Android 上输入验证码的自定义布局. 3 | 4 | ## 预览 5 | ![](https://github.com/Airsaid/InputCodeLayout/blob/master/preview/preview.gif) 6 | 7 | ## 使用 8 | - 布局中: 9 | ``` 10 | 24 | ``` 25 | 26 | - 代码中: 27 | ``` 28 | mInputCodeLayout = (InputCodeLayout) findViewById(R.id.inputCodeLayout); 29 | mInputCodeLayout.setOnInputCompleteListener(new InputCodeLayout.OnInputCompleteCallback() { 30 | @Override 31 | public void onInputCompleteListener(String code) { 32 | Log.e(TAG, "输入的验证码为:" + code); 33 | Toast.makeText(MainActivity.this, "输入的验证码为:" + code, Toast.LENGTH_SHORT).show(); 34 | } 35 | }); 36 | ``` 37 | 38 | ## 自定义属性 39 | ``` 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ``` 66 | 67 | ## 联系我 68 | - **QQ 群:** 5707887 69 | - **Blog:**[http://blog.csdn.net/airsaid](http://blog.csdn.net/airsaid) 70 | - **Email:** airsaid1024@gmail.com 71 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | defaultConfig { 6 | applicationId "com.github.airsaid.inputcodelayout" 7 | minSdkVersion rootProject.ext.minSdkVersion 8 | targetSdkVersion rootProject.ext.targetSdkVersion 9 | versionCode 1 10 | versionName "1.0" 11 | 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | 26 | testImplementation "junit:junit:${rootProject.ext.testJunitVersion}" 27 | androidTestImplementation "com.android.support.test:runner:${rootProject.ext.testRunnerVersion}" 28 | androidTestImplementation "com.android.support.test.espresso:espresso-core:${rootProject.ext.testEspressoCoreVersion}" 29 | 30 | implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" 31 | implementation "com.android.support.constraint:constraint-layout:${rootProject.ext.constraintLayoutVersion}" 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/airsaid/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/airsaid/inputcodelayout/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.airsaid.inputcodelayout; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.github.airsaid.inputverificationcodelayout", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/airsaid/inputcodelayout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.airsaid.inputcodelayout; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.widget.SeekBar; 8 | import android.widget.TextView; 9 | import android.widget.Toast; 10 | 11 | import com.github.airsaid.inputcodelayout.widget.InputCodeLayout; 12 | 13 | public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener { 14 | 15 | private static final String TAG = MainActivity.class.getSimpleName(); 16 | 17 | private InputCodeLayout mInputCodeLayout; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | ((SeekBar) findViewById(R.id.sbr_number)).setOnSeekBarChangeListener(this); 25 | ((SeekBar) findViewById(R.id.sbr_divide_width)).setOnSeekBarChangeListener(this); 26 | ((SeekBar) findViewById(R.id.sbr_size)).setOnSeekBarChangeListener(this); 27 | 28 | mInputCodeLayout = findViewById(R.id.inputCodeLayout); 29 | mInputCodeLayout.setOnInputCompleteListener(new InputCodeLayout.OnInputCompleteCallback() { 30 | @Override 31 | public void onInputCompleteListener(String code) { 32 | Log.e(TAG, "输入的验证码为:" + code); 33 | Toast.makeText(MainActivity.this, "输入的验证码为:" + code, Toast.LENGTH_SHORT).show(); 34 | } 35 | }); 36 | } 37 | 38 | public void normal(View v) { 39 | mInputCodeLayout.setShowMode(InputCodeLayout.NORMAL); 40 | } 41 | 42 | public void password(View v) { 43 | mInputCodeLayout.setShowMode(InputCodeLayout.PASSWORD); 44 | } 45 | 46 | public void clear(View v) { 47 | mInputCodeLayout.clear(); 48 | } 49 | 50 | @Override 51 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 52 | switch (seekBar.getId()) { 53 | case R.id.sbr_number: 54 | mInputCodeLayout.setNumber(progress); 55 | ((TextView) findViewById(R.id.txt_number)).setText("修改输入框数量(" + progress + ")"); 56 | break; 57 | case R.id.sbr_divide_width: 58 | mInputCodeLayout.setDivideWidth(dp2px(progress)); 59 | ((TextView) findViewById(R.id.txt_divide_width)).setText("修改输入框间距(" + progress + ")"); 60 | break; 61 | case R.id.sbr_size: 62 | mInputCodeLayout.setWidth(dp2px(progress)); 63 | mInputCodeLayout.setHeight(dp2px(progress)); 64 | ((TextView) findViewById(R.id.txt_size)).setText("修改输入框大小(" + progress + ")"); 65 | break; 66 | } 67 | } 68 | 69 | @Override 70 | public void onStartTrackingTouch(SeekBar seekBar) { 71 | 72 | } 73 | 74 | @Override 75 | public void onStopTrackingTouch(SeekBar seekBar) { 76 | 77 | } 78 | 79 | public int dp2px(float dpValue) { 80 | return (int) (dpValue * (getResources().getDisplayMetrics().density) + 0.5f); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/airsaid/inputcodelayout/widget/InputCodeLayout.java: -------------------------------------------------------------------------------- 1 | package com.github.airsaid.inputcodelayout.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.drawable.Drawable; 6 | import android.graphics.drawable.GradientDrawable; 7 | import android.support.annotation.IntDef; 8 | import android.text.Editable; 9 | import android.text.InputFilter; 10 | import android.text.TextUtils; 11 | import android.text.TextWatcher; 12 | import android.text.method.HideReturnsTransformationMethod; 13 | import android.text.method.PasswordTransformationMethod; 14 | import android.util.AttributeSet; 15 | import android.view.Gravity; 16 | import android.view.KeyEvent; 17 | import android.view.View; 18 | import android.view.inputmethod.EditorInfo; 19 | import android.widget.EditText; 20 | import android.widget.LinearLayout; 21 | import android.widget.RelativeLayout; 22 | import android.widget.TextView; 23 | 24 | import com.github.airsaid.inputcodelayout.R; 25 | 26 | import java.lang.annotation.Retention; 27 | import java.lang.annotation.RetentionPolicy; 28 | 29 | /** 30 | * @author airsaid 31 | */ 32 | public class InputCodeLayout extends RelativeLayout implements TextWatcher, View.OnKeyListener { 33 | 34 | @IntDef({NORMAL, PASSWORD}) 35 | @Retention(RetentionPolicy.SOURCE) 36 | @interface ShowMode {} 37 | 38 | public static final int NORMAL = 0; 39 | public static final int PASSWORD = 1; 40 | 41 | private final Context mContext; 42 | 43 | /** 输入框数量 */ 44 | private int mNumber; 45 | /** 输入框宽度 */ 46 | private int mWidth; 47 | /** 输入框高度 */ 48 | private int mHeight; 49 | /** 输入框之间的分割线宽度 */ 50 | private int mDivideWidth; 51 | /** 输入文字颜色 */ 52 | private int mTextColor; 53 | /** 输入文字大小 */ 54 | private int mTextSize; 55 | /** 有焦点时输入框背景 */ 56 | private int mFocusBackground; 57 | /** 无焦点时输入框背景 */ 58 | private int mUnFocusBackground; 59 | /** 显示模式 */ 60 | private int mShowMode; 61 | 62 | private LinearLayout mContainer; 63 | private TextView[] mTextViews; 64 | private EditText mEdtCode; 65 | 66 | public InputCodeLayout(Context context) { 67 | this(context, null); 68 | } 69 | 70 | public InputCodeLayout(Context context, AttributeSet attrs) { 71 | this(context, attrs, 0); 72 | } 73 | 74 | public InputCodeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 75 | super(context, attrs, defStyleAttr); 76 | mContext = context; 77 | initViews(); 78 | initAttrs(attrs); 79 | initListener(); 80 | } 81 | 82 | private void initAttrs(AttributeSet attrs) { 83 | TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.InputCodeLayout); 84 | mNumber = a.getInt(R.styleable.InputCodeLayout_icl_number, -1); 85 | mWidth = a.getDimensionPixelSize(R.styleable.InputCodeLayout_icl_width, -1); 86 | mHeight = a.getDimensionPixelSize(R.styleable.InputCodeLayout_icl_height, -1); 87 | int divideWidth = a.getDimensionPixelSize(R.styleable.InputCodeLayout_icl_divideWidth, -1); 88 | if(divideWidth != -1) setDivideWidth(divideWidth); 89 | mTextColor = a.getColor(R.styleable.InputCodeLayout_icl_textColor, -1); 90 | mTextSize = a.getDimensionPixelSize(R.styleable.InputCodeLayout_icl_textSize, 14); 91 | mFocusBackground = a.getResourceId(R.styleable.InputCodeLayout_icl_focusBackground, -1); 92 | mUnFocusBackground = a.getResourceId(R.styleable.InputCodeLayout_icl_unFocusBackground, -1); 93 | mShowMode = a.getInt(R.styleable.InputCodeLayout_icl_showMode, NORMAL); 94 | int gravity = a.getInt(R.styleable.InputCodeLayout_android_gravity, -1); 95 | if(gravity != -1) setGravity(gravity); 96 | a.recycle(); 97 | } 98 | 99 | private void initViews() { 100 | LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 101 | mContainer = new LinearLayout(mContext); 102 | mContainer.setLayoutParams(params); 103 | mContainer.setOrientation(LinearLayout.HORIZONTAL); 104 | mContainer.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); 105 | addView(mContainer); 106 | 107 | mEdtCode = new EditText(mContext); 108 | mEdtCode.setLayoutParams(params); 109 | mEdtCode.setCursorVisible(false); 110 | mEdtCode.setInputType(EditorInfo.TYPE_CLASS_NUMBER); 111 | mEdtCode.setBackgroundResource(android.R.color.transparent); 112 | addView(mEdtCode); 113 | } 114 | 115 | private void initListener() { 116 | mEdtCode.addTextChangedListener(this); 117 | mEdtCode.setOnKeyListener(this); 118 | } 119 | 120 | @Override 121 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 122 | } 123 | 124 | @Override 125 | public void onTextChanged(CharSequence s, int start, int before, int count) { 126 | } 127 | 128 | @Override 129 | public void afterTextChanged(Editable s) { 130 | setCode(s.toString()); 131 | } 132 | 133 | @Override 134 | public boolean onKey(View v, int keyCode, KeyEvent event) { 135 | if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { 136 | deleteCode(); 137 | return true; 138 | } 139 | return false; 140 | } 141 | 142 | /** 143 | * 设置验证码。 144 | * 145 | * @param code 验证码 146 | */ 147 | private void setCode(String code) { 148 | if (TextUtils.isEmpty(code)) return; 149 | 150 | for (int i = 0; i < mTextViews.length; i++) { 151 | TextView textView = mTextViews[i]; 152 | // 如果没设置文本则设置 153 | if (TextUtils.isEmpty(textView.getText().toString())) { 154 | textView.setText(code); 155 | // 取消焦点 156 | textView.setBackgroundResource(mUnFocusBackground); 157 | // 有下一个输入框则设置下一个输入框焦点 158 | if (i < mTextViews.length - 1) 159 | mTextViews[i + 1].setBackgroundResource(mFocusBackground); 160 | // 设置输入完成回调 161 | if (i == mTextViews.length - 1 && mOnInputCompleteCallback != null) 162 | mOnInputCompleteCallback.onInputCompleteListener(getCode()); 163 | // 跳出 164 | break; 165 | } 166 | } 167 | mEdtCode.setText(""); 168 | } 169 | 170 | /** 171 | * 删除验证码。 172 | */ 173 | private void deleteCode() { 174 | for (int i = mTextViews.length - 1; i >= 0; i--) { 175 | TextView textView = mTextViews[i]; 176 | // 如果有设置文本则删除 177 | if (!TextUtils.isEmpty(textView.getText().toString())) { 178 | textView.setText(""); 179 | // 设置焦点 180 | textView.setBackgroundResource(mFocusBackground); 181 | // 有下一个输入框则取消下一个输入框的焦点 182 | if (i < mTextViews.length - 1) 183 | mTextViews[i + 1].setBackgroundResource(mUnFocusBackground); 184 | // 跳出 185 | break; 186 | } 187 | } 188 | } 189 | 190 | @Override 191 | protected void onFinishInflate() { 192 | super.onFinishInflate(); 193 | mContainer.post(new Runnable() { 194 | @Override 195 | public void run() { 196 | initTextView(); 197 | } 198 | }); 199 | } 200 | 201 | private void initTextView() { 202 | if(mNumber <= 0) return; 203 | 204 | int measuredWidth = mContainer.getMeasuredWidth(); 205 | // 均分情况下,根据控件的宽度计算输入框的高度 206 | int height = (measuredWidth - (mDivideWidth * (mNumber - 1))) / mNumber; 207 | 208 | mTextViews = new TextView[mNumber]; 209 | mContainer.removeAllViews(); 210 | for (int i = 0; i < mNumber; i++) { 211 | final TextView textView = new TextView(mContext); 212 | 213 | // 判断如果没有设置宽高,则根据控件宽度均分 214 | if (mWidth != -1 && mHeight != -1) { 215 | textView.setWidth(mWidth); 216 | textView.setHeight(mHeight); 217 | } else { 218 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 219 | LinearLayout.LayoutParams.WRAP_CONTENT + mDivideWidth, height, 1); 220 | textView.setLayoutParams(lp); 221 | } 222 | 223 | if (mTextSize != -1) 224 | textView.getPaint().setTextSize(mTextSize); 225 | if (mTextColor != -1) 226 | textView.setTextColor(mTextColor); 227 | if (mFocusBackground != -1 && mUnFocusBackground != -1) 228 | textView.setBackgroundResource(i != 0 ? mUnFocusBackground : mFocusBackground); 229 | 230 | textView.setGravity(Gravity.CENTER); 231 | textView.setFocusable(false); 232 | setShowMode(textView); 233 | mTextViews[i] = textView; 234 | mContainer.addView(textView); 235 | } 236 | 237 | mContainer.post(new Runnable() { 238 | @Override 239 | public void run() { 240 | mEdtCode.setHeight(mContainer.getMeasuredHeight()); 241 | } 242 | }); 243 | } 244 | 245 | /** 246 | * 设置输入框数量。 247 | * 248 | * @param number 输入框数量 249 | */ 250 | public void setNumber(int number){ 251 | if(mNumber != number){ 252 | mNumber = number; 253 | mEdtCode.setFilters(new InputFilter[]{new InputFilter.LengthFilter(mNumber)}); 254 | onFinishInflate(); 255 | } 256 | } 257 | 258 | /** 259 | * 设置分割线宽度。 260 | * 261 | * @param width 分割线宽度 262 | */ 263 | public void setDivideWidth(int width){ 264 | if(width != mDivideWidth){ 265 | mDivideWidth = width; 266 | mContainer.setDividerDrawable(createDivideShape(mDivideWidth)); 267 | } 268 | } 269 | 270 | private Drawable createDivideShape(int width) { 271 | GradientDrawable shape = new GradientDrawable(); 272 | shape.setSize(width, 0); 273 | return shape; 274 | } 275 | 276 | /** 277 | * 设置输入框宽度。(如果宽度 == -1, 则输入框大小按照控件的宽度来均分) 278 | * 279 | * @param width 输入框宽度 280 | */ 281 | public void setWidth(int width){ 282 | if(mWidth != width){ 283 | mWidth = width; 284 | onFinishInflate(); 285 | } 286 | } 287 | 288 | /** 289 | * 设置输入框高度。(如果高度 == -1, 则输入框大小按照控件的宽度来均分) 290 | * 291 | * @param height 输入框高度 292 | */ 293 | public void setHeight(int height){ 294 | if(mHeight != height){ 295 | mHeight = height; 296 | onFinishInflate(); 297 | } 298 | } 299 | 300 | /** 301 | * 设置显示模式。 302 | * 303 | * @param showMode 通过 {@link #NORMAL} 或者 {@link #PASSWORD} 设置 304 | * 默认是 {@link #NORMAL} 305 | */ 306 | public void setShowMode(@ShowMode int showMode) { 307 | if (mShowMode != showMode) { 308 | mShowMode = showMode; 309 | for (TextView textView : mTextViews) { 310 | setShowMode(textView); 311 | } 312 | } 313 | } 314 | 315 | private void setShowMode(TextView textView){ 316 | if (mShowMode == NORMAL) 317 | textView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); 318 | else 319 | textView.setTransformationMethod(PasswordTransformationMethod.getInstance()); 320 | } 321 | 322 | /** 323 | * 设置输入框的摆放位置。 324 | * 325 | * @param gravity 请参阅 {@link android.view.Gravity} 326 | */ 327 | public void setGravity(int gravity) { 328 | if(mContainer != null) 329 | mContainer.setGravity(gravity); 330 | } 331 | 332 | /** 333 | * 获取已经输入的验证码。 334 | * 335 | * @return 验证码 336 | */ 337 | public String getCode() { 338 | StringBuilder sb = new StringBuilder(); 339 | for (TextView textView : mTextViews) { 340 | sb.append(textView.getText().toString()); 341 | } 342 | return sb.toString(); 343 | } 344 | 345 | /** 346 | * 清空输入框。 347 | */ 348 | public void clear() { 349 | for (int i = 0; i < mTextViews.length; i++) { 350 | TextView textView = mTextViews[i]; 351 | textView.setText(""); 352 | textView.setBackgroundResource(i != 0 ? mUnFocusBackground : mFocusBackground); 353 | } 354 | } 355 | 356 | private OnInputCompleteCallback mOnInputCompleteCallback; 357 | 358 | public interface OnInputCompleteCallback { 359 | /** 360 | * 输入完成监听。 361 | * 362 | * @param code 输入的验证码 363 | */ 364 | void onInputCompleteListener(String code); 365 | } 366 | 367 | /** 368 | * 设置输入完成监听。 369 | * 370 | * @param callback 回调 371 | */ 372 | public void setOnInputCompleteListener(OnInputCompleteCallback callback) { 373 | this.mOnInputCompleteCallback = callback; 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edit_focus.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edit_un_focus.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 25 | 26 | 32 | 33 | 39 | 40 | 46 | 47 | 53 | 54 | 60 | 61 | 67 | 68 | 72 | 73 |