Repository: maning0303/MNPasswordEditText Branch: master Commit: 4db5ab1316af Files: 39 Total size: 59.2 KB Directory structure: gitextract_ktc04v1x/ ├── .gitignore ├── .idea/ │ ├── caches/ │ │ ├── build_file_checksums.ser │ │ └── gradle_models.ser │ ├── codeStyles/ │ │ └── Project.xml │ ├── compiler.xml │ ├── copyright/ │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── inspectionProfiles/ │ │ └── Project_Default.xml │ ├── jarRepositories.xml │ ├── misc.xml │ ├── modules.xml │ └── vcs.xml ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── maning/ │ │ └── mnpasswordedittext/ │ │ └── MainActivity.java │ └── res/ │ ├── layout/ │ │ └── activity_main.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── pswedittextlibrary/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── maning/ │ │ └── pswedittextlibrary/ │ │ └── MNPasswordEditText.java │ └── res/ │ └── values/ │ ├── attr.xml │ └── strings.xml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures .externalNativeBuild ================================================ FILE: .idea/codeStyles/Project.xml ================================================
xmlns:android ^$
xmlns:.* ^$ BY_NAME
.*:id http://schemas.android.com/apk/res/android
.*:name http://schemas.android.com/apk/res/android
name ^$
style ^$
.* ^$ BY_NAME
.* http://schemas.android.com/apk/res/android ANDROID_ATTRIBUTE_ORDER
.* .* BY_NAME
================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/inspectionProfiles/Project_Default.xml ================================================ ================================================ FILE: .idea/jarRepositories.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: README.md ================================================ # MNPasswordEditText [![](https://jitpack.io/v/maning0303/MNPasswordEditText.svg)](https://jitpack.io/#maning0303/MNPasswordEditText) Android验证码和密码输入框,能自定义输入框个数和样式(连体,下划线和方形框) 类似微信支付宝的密码输入框等 ## 截图: ![](https://github.com/maning0303/MNPasswordEditText/raw/master/screenshots/mn_pswedittext_001.jpeg) ![](https://github.com/maning0303/MNPasswordEditText/raw/master/screenshots/mn_pswedittext_002.jpeg) ## 如何添加 ### 1:Gradle添加: #### 1.在Project的build.gradle中添加仓库地址 ``` gradle allprojects { repositories { ... maven { url "https://jitpack.io" } } } ``` #### 2.在app目录下的build.gradle中添加依赖 ``` gradle dependencies { implementation 'com.github.maning0303:MNPasswordEditText:V1.0.4' } ``` ### 2:源码Module添加: #### 直接关联pswedittextlibrary ``` gradle implementation project(':pswedittextlibrary') ``` ## 使用方法(查看Demo详情): ### 1:自定义参数介绍: ``` java ``` ### 2:布局文件使用(详细查看Demo): ``` java ================================================ FILE: app/src/main/java/com/maning/mnpasswordedittext/MainActivity.java ================================================ package com.maning.mnpasswordedittext; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.widget.TextView; import android.widget.Toast; import com.maning.pswedittextlibrary.MNPasswordEditText; public class MainActivity extends AppCompatActivity { private MNPasswordEditText mPswEditText; private TextView tvShow; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvShow = (TextView) findViewById(R.id.tvShow); mPswEditText = (MNPasswordEditText) findViewById(R.id.mPswEditText); mPswEditText.setOnTextChangeListener(new MNPasswordEditText.OnTextChangeListener() { @Override public void onTextChange(String text, boolean isComplete) { tvShow.setText(text); if (isComplete) { Toast.makeText(MainActivity.this, "输入完成", Toast.LENGTH_SHORT).show(); } } }); } } ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #3F51B5 #303F9F #FF4081 ================================================ FILE: app/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ MNPasswordEditText ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() jcenter() maven { url "https://jitpack.io" } } dependencies { classpath 'com.android.tools.build:gradle:4.0.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() maven { url "https://jitpack.io" } } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Mon Dec 28 10:00:20 PST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: pswedittextlibrary/.gitignore ================================================ /build ================================================ FILE: pswedittextlibrary/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' group='com.github.maning0303' android { compileSdkVersion 29 defaultConfig { minSdkVersion 14 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) compileOnly 'androidx.legacy:legacy-support-v4:1.0.0' compileOnly 'androidx.annotation:annotation:1.2.0' } ================================================ FILE: pswedittextlibrary/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/maning/Develop/Android/SDK/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: pswedittextlibrary/src/main/AndroidManifest.xml ================================================ ================================================ FILE: pswedittextlibrary/src/main/java/com/maning/pswedittextlibrary/MNPasswordEditText.java ================================================ package com.maning.pswedittextlibrary; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.text.InputFilter; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.EditText; import java.lang.reflect.Field; /** * @author : maning * @desc : 验证码和密码的输入框 */ public class MNPasswordEditText extends EditText { private static final String TAG = "MNPasswordEditText"; private Context mContext; private String defaultColor = "#FF0000"; /** * 长度 */ private int maxLength; /** * 文字的颜色 */ private int textColor; /** * 文字的画笔 */ private Paint mPaintText; /** * 线框的画笔 */ private Paint mPaintLine; /** * 背景色 */ private int backgroundColor; /** * 线框的颜色 */ private int borderColor; /** * 线框被选中的颜色 */ private int borderSelectedColor; /** * 线框的圆角 */ private float borderRadius; /** * 线框的宽度 */ private float borderWidth; /** * 密码框的间隔 */ private float itemMargin; /** * 输入的类型 */ private int inputMode; /** * 样式 */ private int editTextStyle; /** * 文字遮盖 */ private String coverText; /** * 图片遮盖 */ private int coverBitmapID; /** * 图片宽度 */ private float coverBitmapWidth; /** * 圆形遮盖的颜色 */ private int coverCirclrColor; /** * 圆形遮盖的半径 */ private float coverCirclrRadius; /** * 线框背景 */ private GradientDrawable gradientDrawable = new GradientDrawable(); private Bitmap coverBitmap; private Paint mPaintCursor; /** * 光标 */ private GradientDrawable cursorDrawable = new GradientDrawable(); private int cursorColor; private float cursorWidth; private float cursorHeight; private float cursorCornerRadius; private boolean showCursor = false; private boolean mCursorFlag; private Blink mBlink; public MNPasswordEditText(Context context) { this(context, null); } public MNPasswordEditText(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MNPasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; //初始化参数 initAttrs(attrs, defStyleAttr); //初始化相关 init(); } private void initAttrs(AttributeSet attrs, int defStyleAttr) { TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.MNPasswordEditText, defStyleAttr, 0); //背景色 backgroundColor = array.getColor(R.styleable.MNPasswordEditText_psw_background_color, Color.parseColor("#FFFFFF")); //边框颜色 borderColor = array.getColor(R.styleable.MNPasswordEditText_psw_border_color, Color.parseColor(defaultColor)); //边框选中的颜色 borderSelectedColor = array.getColor(R.styleable.MNPasswordEditText_psw_border_selected_color, Color.parseColor(defaultColor)); //文字的颜色 textColor = array.getColor(R.styleable.MNPasswordEditText_psw_text_color, Color.parseColor(defaultColor)); //边框圆角 borderRadius = array.getDimension(R.styleable.MNPasswordEditText_psw_border_radius, dip2px(6)); //边框线大小 borderWidth = array.getDimension(R.styleable.MNPasswordEditText_psw_border_width, dip2px(1)); //每个密码框的间隔 itemMargin = array.getDimension(R.styleable.MNPasswordEditText_psw_item_margin, dip2px(10)); //输入的模式 inputMode = array.getInt(R.styleable.MNPasswordEditText_psw_mode, 1); //整体样式 editTextStyle = array.getInt(R.styleable.MNPasswordEditText_psw_style, 1); //替换的图片 coverBitmapID = array.getResourceId(R.styleable.MNPasswordEditText_psw_cover_bitmap_id, -1); //替换的文字 coverText = array.getString(R.styleable.MNPasswordEditText_psw_cover_text); if (TextUtils.isEmpty(coverText)) { coverText = "密"; } //圆形的颜色 coverCirclrColor = array.getColor(R.styleable.MNPasswordEditText_psw_cover_circle_color, Color.parseColor(defaultColor)); //密码圆形遮盖半径 coverCirclrRadius = array.getDimension(R.styleable.MNPasswordEditText_psw_cover_circle_radius, 0); //密码图片遮盖长宽 coverBitmapWidth = array.getDimension(R.styleable.MNPasswordEditText_psw_cover_bitmap_width, 0); //--------------光标属性----- showCursor = array.getBoolean(R.styleable.MNPasswordEditText_psw_show_cursor, false); cursorColor = array.getColor(R.styleable.MNPasswordEditText_psw_cursor_color, borderSelectedColor); cursorHeight = array.getDimension(R.styleable.MNPasswordEditText_psw_cursor_height, 0); cursorWidth = array.getDimension(R.styleable.MNPasswordEditText_psw_cursor_width, 6); cursorCornerRadius = array.getDimension(R.styleable.MNPasswordEditText_psw_cursor_corner_radius, 0); //回收 array.recycle(); } private void init() { //最大的长度 maxLength = getMaxLength(); //隐藏光标 setCursorVisible(false); //设置本来文字的颜色为透明 setTextColor(Color.TRANSPARENT); //触摸获取焦点 setFocusableInTouchMode(true); //屏蔽长按 setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View view) { return true; } }); //初始化画笔 //文字 mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintText.setStyle(Paint.Style.FILL); mPaintText.setColor(textColor); mPaintText.setTextSize(getTextSize()); //线 mPaintLine = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintLine.setStyle(Paint.Style.STROKE); mPaintLine.setColor(borderColor); mPaintLine.setStrokeWidth(borderWidth); //光标 cursorDrawable.setCornerRadius(cursorCornerRadius); cursorDrawable.setColor(cursorColor); mPaintCursor = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintCursor.setStyle(Paint.Style.FILL); mPaintCursor.setColor(cursorColor); //遮盖是图片方式,提前加载图片 if (inputMode == 2) { //判断有没有图片 if (coverBitmapID == -1) { //抛出异常 throw new NullPointerException("遮盖图片为空"); } else { coverBitmap = BitmapFactory.decodeResource(getContext().getResources(), coverBitmapID); } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //获取宽高 int measuredWidth = getMeasuredWidth(); float itemH = getMeasuredHeight(); //方形框 float margin = itemMargin; float itemW = (measuredWidth - margin * (maxLength - 1)) / maxLength; int currentIndex = getText().length(); //判断类型 if (editTextStyle == 1) { //连体框 gradientDrawable.setStroke((int) borderWidth, borderColor); gradientDrawable.setCornerRadius(borderRadius); gradientDrawable.setColor(backgroundColor); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { //Android系统大于等于API16,使用setBackground setBackground(gradientDrawable); } else { //Android系统小于API16,使用setBackgroundDrawable setBackgroundDrawable(gradientDrawable); } margin = 0; itemW = measuredWidth / maxLength; //画线 for (int i = 1; i < maxLength; i++) { float startX = itemW * i; float startY = 0; float stopX = startX; float stopY = itemH; canvas.drawLine(startX, startY, stopX, stopY, mPaintLine); } } else if (editTextStyle == 2) { gradientDrawable.setStroke((int) borderWidth, borderColor); gradientDrawable.setCornerRadius(borderRadius); gradientDrawable.setColor(backgroundColor); Bitmap bitmap = drawableToBitmap(gradientDrawable, (int) itemW, (int) itemH); Bitmap bitmapSelected = null; if (borderSelectedColor != 0) { gradientDrawable.setStroke((int) borderWidth, borderSelectedColor); bitmapSelected = drawableToBitmap(gradientDrawable, (int) itemW, (int) itemH); } //画每个Item背景 for (int i = 0; i < maxLength; i++) { float left = itemW * i + margin * i; float top = 0; if (bitmapSelected == null) { canvas.drawBitmap(bitmap, left, top, mPaintLine); } else { if (currentIndex == i) { //选中是另外的颜色 canvas.drawBitmap(bitmapSelected, left, top, mPaintLine); } else { canvas.drawBitmap(bitmap, left, top, mPaintLine); } } } } else if (editTextStyle == 3) { //下划线格式 for (int i = 0; i < maxLength; i++) { if (borderSelectedColor != 0) { if (currentIndex == i) { //选中是另外的颜色 mPaintLine.setColor(borderSelectedColor); } else { mPaintLine.setColor(borderColor); } } else { mPaintLine.setColor(borderColor); } float startX = itemW * i + itemMargin * i; float startY = itemH - borderWidth; float stopX = startX + itemW; float stopY = startY; canvas.drawLine(startX, startY, stopX, stopY, mPaintLine); } } //写文字 String currentText = getText().toString(); for (int i = 0; i < maxLength; i++) { if (!TextUtils.isEmpty(currentText) && i < currentText.length()) { // if (inputMode == 1) { //圆点半径 float circleRadius = itemW * 0.5f * 0.5f; if (circleRadius > itemH / 2f) { circleRadius = itemH * 0.5f * 0.5f; } if (coverCirclrRadius > 0) { circleRadius = coverCirclrRadius; } float startX = (itemW / 2f) + itemW * i + margin * i; float startY = (itemH) / 2.0f; mPaintText.setColor(coverCirclrColor); canvas.drawCircle(startX, startY, circleRadius, mPaintText); } else if (inputMode == 2) { float picW = itemW * 0.5f; if (coverBitmapWidth > 0) { picW = coverBitmapWidth; } float startX = (itemW - picW) / 2f + itemW * i + margin * i; float startY = (itemH - picW) / 2f; Bitmap bitmap = Bitmap.createScaledBitmap(coverBitmap, (int) picW, (int) picW, true); canvas.drawBitmap(bitmap, startX, startY, mPaintText); } else if (inputMode == 3) { float fontWidth = getFontWidth(mPaintText, coverText); float fontHeight = getFontHeight(mPaintText, coverText); float startX = (itemW - fontWidth) / 2f + itemW * i + margin * i; float startY = (itemH + fontHeight) / 2f - 6; mPaintText.setColor(textColor); canvas.drawText(coverText, startX, startY, mPaintText); } else { String StrPosition = String.valueOf(currentText.charAt(i)); float fontWidth = getFontWidth(mPaintText, StrPosition); float fontHeight = getFontHeight(mPaintText, StrPosition); float startX = (itemW - fontWidth) / 2f + itemW * i + margin * i; float startY = (itemH + fontHeight) / 2f; mPaintText.setColor(textColor); canvas.drawText(StrPosition, startX, startY, mPaintText); } } } if (showCursor && mCursorFlag) { if (cursorHeight == 0 || cursorHeight > itemH) { cursorHeight = itemH * 50 / 100; } Bitmap bitmap = drawableToBitmap(cursorDrawable, (int) cursorWidth, (int) cursorHeight); float cursorLeft = (itemW + margin) * currentIndex + itemW / 2 - cursorWidth / 2; float cursorTop = (itemH - cursorHeight) / 2; canvas.drawBitmap(bitmap, cursorLeft, cursorTop, mPaintCursor); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); resumeBlink(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); suspendBlink(); } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (hasWindowFocus) { if (mBlink != null) { mBlink.uncancel(); } makeBlink(); } else { if (mBlink != null) { mBlink.cancel(); } } } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); if (focused) { if (mBlink != null) { mBlink.uncancel(); } makeBlink(); } else { if (mBlink != null) { mBlink.cancel(); } } } private void resumeBlink() { if (mBlink != null) { mBlink.uncancel(); } makeBlink(); } private void suspendBlink() { if (mBlink != null) { mBlink.cancel(); } } private void makeBlink() { if (true) { if (mBlink == null) mBlink = new Blink(); removeCallbacks(mBlink); postDelayed(mBlink, 500); } else { if (mBlink != null) removeCallbacks(mBlink); } } private class Blink implements Runnable { private boolean mCancelled = false; @Override public void run() { mCursorFlag = !mCursorFlag; invalidate(); if (mCancelled) { return; } //每个500毫秒刷新一次 postDelayed(this, 500); } public void cancel() { if (!mCancelled) { removeCallbacks(this); mCancelled = true; } } public void uncancel() { mCancelled = false; } } public static Bitmap drawableToBitmap(Drawable drawable, int width, int height) { Bitmap bitmap = Bitmap.createBitmap( width, height, drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, width, height); drawable.draw(canvas); return bitmap; } @Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { super.onTextChanged(text, start, lengthBefore, lengthAfter); //刷新界面 invalidate(); if (onTextChangeListener != null) { if (getText().toString().length() == getMaxLength()) { onTextChangeListener.onTextChange(getText().toString(), true); } else { onTextChangeListener.onTextChange(getText().toString(), false); } } } public float getFontWidth(Paint paint, String str) { Rect rect = new Rect(); paint.getTextBounds(str, 0, str.length(), rect); return rect.width(); } public float getFontHeight(Paint paint, String str) { Rect rect = new Rect(); paint.getTextBounds(str, 0, str.length(), rect); return rect.height(); } public int getMaxLength() { int length = 0; try { InputFilter[] inputFilters = getFilters(); for (InputFilter filter : inputFilters) { Class c = filter.getClass(); if (c.getName().equals("android.text.InputFilter$LengthFilter")) { Field[] f = c.getDeclaredFields(); for (Field field : f) { if (field.getName().equals("mMax")) { field.setAccessible(true); length = (Integer) field.get(filter); } } } } } catch (Exception e) { e.printStackTrace(); } return length; } private int dip2px(float dpValue) { final float scale = getContext().getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } private OnTextChangeListener onTextChangeListener; public void setOnTextChangeListener(OnTextChangeListener onTextChangeListener) { this.onTextChangeListener = onTextChangeListener; } public interface OnTextChangeListener { /** * 监听输入变化 * * @param text 当前的文案 * @param isComplete 是不是完成输入 */ void onTextChange(String text, boolean isComplete); } } ================================================ FILE: pswedittextlibrary/src/main/res/values/attr.xml ================================================ ================================================ FILE: pswedittextlibrary/src/main/res/values/strings.xml ================================================ PswEditTextLibrary ================================================ FILE: settings.gradle ================================================ include ':app', ':pswedittextlibrary'