From 167e04406e217ddc0a187a203ac2872fd481dc57 Mon Sep 17 00:00:00 2001 From: Marcio Date: Sat, 2 Nov 2019 23:46:22 -0300 Subject: [PATCH 1/5] Add SkipMaskLastChar --- README.MD | 1 + .../alimuzaffar/lib/pin/PinEntryEditText.java | 19 ++++++++++++------- .../src/main/res/values/attrs.xml | 1 + .../main/res/layout/activity_edit_text.xml | 3 ++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.MD b/README.MD index 77fffd8..0405d5b 100755 --- a/README.MD +++ b/README.MD @@ -90,6 +90,7 @@ as your code is likely to use a different version from the library. app:pinLineStrokeSelected="4dp" //the stroke (height) of the bottom line when field is focused. app:pinBackgroundIsSquare="true|false" //optional, if you want the background drawable to be a square or circle width of each digit will be set to match the height of the widget. app:pinLineColors="@color/pin_line_colors" //optional + app:pinSkipMaskLastChar // optional, default false, if you want to show the last typed digit without the mask android:layoutDirection="ltr|rtl" /> ``` diff --git a/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java b/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java index 09e4952..6cff990 100755 --- a/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java +++ b/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java @@ -67,6 +67,7 @@ public class PinEntryEditText extends AppCompatEditText { protected Drawable mPinBackground; protected Rect mTextHeight = new Rect(); protected boolean mIsDigitSquare = false; + protected boolean mShouldSkipMaskLastChar = false; protected OnClickListener mClickListener; protected OnPinEnteredListener mOnPinEnteredListener = null; @@ -148,6 +149,7 @@ private void init(Context context, AttributeSet attrs) { mTextBottomPadding = ta.getDimension(R.styleable.PinEntryEditText_pinTextBottomPadding, mTextBottomPadding); mIsDigitSquare = ta.getBoolean(R.styleable.PinEntryEditText_pinBackgroundIsSquare, mIsDigitSquare); mPinBackground = ta.getDrawable(R.styleable.PinEntryEditText_pinBackgroundDrawable); + mShouldSkipMaskLastChar = ta.getBoolean(R.styleable.PinEntryEditText_pinSkipMaskLastChar, mShouldSkipMaskLastChar); ColorStateList colors = ta.getColorStateList(R.styleable.PinEntryEditText_pinLineColors); if (colors != null) { mColorStates = colors; @@ -399,12 +401,13 @@ private StringBuilder getMaskChars() { if (mMaskChars == null) { mMaskChars = new StringBuilder(); } - int textLength = getText().length(); - while (mMaskChars.length() != textLength) { - if (mMaskChars.length() < textLength) { - mMaskChars.append(mMask); + String text = getText().toString(); + mMaskChars.delete(0, mMaskChars.length()); + for (int i = 0; i < text.length(); i++) { + if (mShouldSkipMaskLastChar && isFocused() && i == text.length() - 1) { + mMaskChars.append(text.charAt(i)); } else { - mMaskChars.deleteCharAt(mMaskChars.length() - 1); + mMaskChars.append(mMask); } } return mMaskChars; @@ -520,11 +523,13 @@ protected void onTextChanged(CharSequence text, final int start, int lengthBefor return; } - if (lengthAfter > lengthBefore) { + boolean shouldAnimateDeletion = mShouldSkipMaskLastChar && !TextUtils.isEmpty(mMask); + if (lengthAfter > lengthBefore || (shouldAnimateDeletion && lengthAfter < lengthBefore)) { if (mAnimatedType == 0) { animatePopIn(); } else { - animateBottomUp(text, start); + int startIndex = lengthAfter < lengthBefore ? start - 1 : start; + animateBottomUp(text, startIndex); } } } diff --git a/pinentryedittext/src/main/res/values/attrs.xml b/pinentryedittext/src/main/res/values/attrs.xml index 848abdb..2020f9d 100755 --- a/pinentryedittext/src/main/res/values/attrs.xml +++ b/pinentryedittext/src/main/res/values/attrs.xml @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/sample-app/src/main/res/layout/activity_edit_text.xml b/sample-app/src/main/res/layout/activity_edit_text.xml index b725766..635d1a4 100755 --- a/sample-app/src/main/res/layout/activity_edit_text.xml +++ b/sample-app/src/main/res/layout/activity_edit_text.xml @@ -43,7 +43,7 @@ android:background="@null" android:cursorVisible="false" android:digits="1234567890" - android:inputType="number" + android:inputType="numberPassword" android:maxLength="6" android:textIsSelectable="false" android:textSize="24dp" @@ -51,6 +51,7 @@ app:pinBackgroundDrawable="@drawable/bg_pin" app:pinBackgroundIsSquare="true" app:pinCharacterSpacing="4dp" + app:pinSkipMaskLastChar="true" app:pinTextBottomPadding="16dp" tools:ignore="SpUsage" /> From dfce293ddd523a6529d7716f8dfac0f1ca090698 Mon Sep 17 00:00:00 2001 From: Marcio Date: Sat, 2 Nov 2019 23:51:43 -0300 Subject: [PATCH 2/5] Fix readme --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index 0405d5b..53af8a8 100755 --- a/README.MD +++ b/README.MD @@ -90,7 +90,7 @@ as your code is likely to use a different version from the library. app:pinLineStrokeSelected="4dp" //the stroke (height) of the bottom line when field is focused. app:pinBackgroundIsSquare="true|false" //optional, if you want the background drawable to be a square or circle width of each digit will be set to match the height of the widget. app:pinLineColors="@color/pin_line_colors" //optional - app:pinSkipMaskLastChar // optional, default false, if you want to show the last typed digit without the mask + app:pinSkipMaskLastChar="false" // optional, default false, if you want to show the last typed digit without the mask android:layoutDirection="ltr|rtl" /> ``` From 5520e2c0a7ad128c07a5f3af1f948a17021ac84e Mon Sep 17 00:00:00 2001 From: Marcio Date: Sat, 2 Nov 2019 23:52:22 -0300 Subject: [PATCH 3/5] Update README --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index 53af8a8..748f1fc 100755 --- a/README.MD +++ b/README.MD @@ -90,7 +90,7 @@ as your code is likely to use a different version from the library. app:pinLineStrokeSelected="4dp" //the stroke (height) of the bottom line when field is focused. app:pinBackgroundIsSquare="true|false" //optional, if you want the background drawable to be a square or circle width of each digit will be set to match the height of the widget. app:pinLineColors="@color/pin_line_colors" //optional - app:pinSkipMaskLastChar="false" // optional, default false, if you want to show the last typed digit without the mask + app:pinSkipMaskLastChar="false" // optional, default false, if you want to show the last typed digit without the mask set it to "true" android:layoutDirection="ltr|rtl" /> ``` From 4f6d1539ef7bd76184bb01e36412f0ee9b7d7de9 Mon Sep 17 00:00:00 2001 From: Marcio Date: Sun, 3 Nov 2019 03:55:12 -0300 Subject: [PATCH 4/5] Add timer to hide last char --- .../alimuzaffar/lib/pin/PinEntryEditText.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java b/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java index 6cff990..af3992a 100755 --- a/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java +++ b/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java @@ -28,6 +28,7 @@ import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.text.InputFilter; import android.text.InputType; import android.text.TextUtils; @@ -77,6 +78,7 @@ public class PinEntryEditText extends AppCompatEditText { protected Paint mLinesPaint; protected boolean mAnimate = false; protected boolean mHasError = false; + protected boolean mHideLastChar = false; protected ColorStateList mOriginalTextColors; protected int[][] mStates = new int[][]{ new int[]{android.R.attr.state_selected}, // selected @@ -93,6 +95,7 @@ public class PinEntryEditText extends AppCompatEditText { }; protected ColorStateList mColorStates = new ColorStateList(mStates, mColors); + private Handler mLastCharTimer = new Handler(); public PinEntryEditText(Context context) { super(context); @@ -404,8 +407,9 @@ private StringBuilder getMaskChars() { String text = getText().toString(); mMaskChars.delete(0, mMaskChars.length()); for (int i = 0; i < text.length(); i++) { - if (mShouldSkipMaskLastChar && isFocused() && i == text.length() - 1) { + if (mShouldSkipMaskLastChar && !mHideLastChar && isFocused() && i == text.length() - 1) { mMaskChars.append(text.charAt(i)); + startLastCharTimer(); } else { mMaskChars.append(mMask); } @@ -413,6 +417,16 @@ private StringBuilder getMaskChars() { return mMaskChars; } + private void startLastCharTimer() { + mLastCharTimer.removeCallbacksAndMessages(null); + mLastCharTimer.postDelayed(new Runnable() { + @Override + public void run() { + mHideLastChar = true; + if (mShouldSkipMaskLastChar) invalidate(); + } + }, 800); + } private int getColorForState(int... states) { return mColorStates.getColorForState(states, Color.GRAY); @@ -523,13 +537,12 @@ protected void onTextChanged(CharSequence text, final int start, int lengthBefor return; } - boolean shouldAnimateDeletion = mShouldSkipMaskLastChar && !TextUtils.isEmpty(mMask); - if (lengthAfter > lengthBefore || (shouldAnimateDeletion && lengthAfter < lengthBefore)) { + mHideLastChar = lengthAfter < lengthBefore; + if (lengthAfter > lengthBefore) { if (mAnimatedType == 0) { animatePopIn(); } else { - int startIndex = lengthAfter < lengthBefore ? start - 1 : start; - animateBottomUp(text, startIndex); + animateBottomUp(text, start); } } } From 59c7b9133aeee9c1c41215d9720f4095ab3d23a2 Mon Sep 17 00:00:00 2001 From: Marcio Date: Sun, 3 Nov 2019 22:53:59 -0300 Subject: [PATCH 5/5] Fix timer --- .../alimuzaffar/lib/pin/PinEntryEditText.java | 85 ++++++++++--------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java b/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java index af3992a..693e950 100755 --- a/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java +++ b/pinentryedittext/src/main/java/com/alimuzaffar/lib/pin/PinEntryEditText.java @@ -29,6 +29,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.Looper; import android.text.InputFilter; import android.text.InputType; import android.text.TextUtils; @@ -50,6 +51,7 @@ public class PinEntryEditText extends AppCompatEditText { private static final String XML_NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android"; public static final String DEFAULT_MASK = "\u25CF"; + public static final int HIDE_LAST_CHAR_MILLIS = 600; protected String mMask = null; protected StringBuilder mMaskChars = null; @@ -95,7 +97,7 @@ public class PinEntryEditText extends AppCompatEditText { }; protected ColorStateList mColorStates = new ColorStateList(mStates, mColors); - private Handler mLastCharTimer = new Handler(); + private Handler mLastCharTimer = new Handler(Looper.getMainLooper()); public PinEntryEditText(Context context) { super(context); @@ -409,7 +411,6 @@ private StringBuilder getMaskChars() { for (int i = 0; i < text.length(); i++) { if (mShouldSkipMaskLastChar && !mHideLastChar && isFocused() && i == text.length() - 1) { mMaskChars.append(text.charAt(i)); - startLastCharTimer(); } else { mMaskChars.append(mMask); } @@ -417,7 +418,8 @@ private StringBuilder getMaskChars() { return mMaskChars; } - private void startLastCharTimer() { + private void startLastCharTimerIfNeeded() { + if (!mShouldSkipMaskLastChar) return; mLastCharTimer.removeCallbacksAndMessages(null); mLastCharTimer.postDelayed(new Runnable() { @Override @@ -425,7 +427,7 @@ public void run() { mHideLastChar = true; if (mShouldSkipMaskLastChar) invalidate(); } - }, 800); + }, HIDE_LAST_CHAR_MILLIS); } private int getColorForState(int... states) { @@ -525,19 +527,21 @@ public void setPinBackground(Drawable pinBackground) { @Override protected void onTextChanged(CharSequence text, final int start, int lengthBefore, final int lengthAfter) { setError(false); + mHideLastChar = lengthAfter < lengthBefore; if (mLineCoords == null || !mAnimate) { if (mOnPinEnteredListener != null && text.length() == mMaxLength) { mOnPinEnteredListener.onPinEntered(text); } + startLastCharTimerIfNeeded(); return; } if (mAnimatedType == -1) { invalidate(); + startLastCharTimerIfNeeded(); return; } - mHideLastChar = lengthAfter < lengthBefore; if (lengthAfter > lengthBefore) { if (mAnimatedType == 0) { animatePopIn(); @@ -558,32 +562,34 @@ public void onAnimationUpdate(ValueAnimator animation) { PinEntryEditText.this.invalidate(); } }); - if (getText().length() == mMaxLength && mOnPinEnteredListener != null) { - va.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } + va.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } - @Override - public void onAnimationEnd(Animator animation) { + @Override + public void onAnimationEnd(Animator animation) { + if (getText().length() == mMaxLength && mOnPinEnteredListener != null) { mOnPinEnteredListener.onPinEntered(getText()); } + startLastCharTimerIfNeeded(); + } - @Override - public void onAnimationCancel(Animator animation) { - } + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - } va.start(); } - private void animateBottomUp(CharSequence text, final int start) { + private void animateBottomUp(final CharSequence text, final int start) { mCharBottom[start] = mLineCoords[start].bottom - mTextBottomPadding; - ValueAnimator animUp = ValueAnimator.ofFloat(mCharBottom[start] + getPaint().getTextSize(), mCharBottom[start]); + final ValueAnimator animUp = ValueAnimator.ofFloat(mCharBottom[start] + getPaint().getTextSize(), mCharBottom[start]); animUp.setDuration(300); animUp.setInterpolator(new OvershootInterpolator()); animUp.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -596,7 +602,7 @@ public void onAnimationUpdate(ValueAnimator animation) { }); mLastCharPaint.setAlpha(255); - ValueAnimator animAlpha = ValueAnimator.ofInt(0, 255); + final ValueAnimator animAlpha = ValueAnimator.ofInt(0, 255); animAlpha.setDuration(300); animAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override @@ -606,29 +612,30 @@ public void onAnimationUpdate(ValueAnimator animation) { } }); - AnimatorSet set = new AnimatorSet(); - if (text.length() == mMaxLength && mOnPinEnteredListener != null) { - set.addListener(new Animator.AnimatorListener() { + final AnimatorSet set = new AnimatorSet(); + set.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } + @Override + public void onAnimationStart(Animator animation) { + } - @Override - public void onAnimationEnd(Animator animation) { + @Override + public void onAnimationEnd(Animator animation) { + if (text.length() == mMaxLength && mOnPinEnteredListener != null) { mOnPinEnteredListener.onPinEntered(getText()); } + startLastCharTimerIfNeeded(); + } - @Override - public void onAnimationCancel(Animator animation) { - } + @Override + public void onAnimationCancel(Animator animation) { + } - @Override - public void onAnimationRepeat(Animator animation) { + @Override + public void onAnimationRepeat(Animator animation) { - } - }); - } + } + }); set.playTogether(animUp, animAlpha); set.start(); }