diff --git a/README.MD b/README.MD index 77fffd8..748f1fc 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="false" // optional, default false, if you want to show the last typed digit without the mask set it to "true" 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..693e950 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,8 @@ import android.graphics.RectF; 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; @@ -49,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; @@ -67,6 +70,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; @@ -76,6 +80,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 @@ -92,6 +97,7 @@ public class PinEntryEditText extends AppCompatEditText { }; protected ColorStateList mColorStates = new ColorStateList(mStates, mColors); + private Handler mLastCharTimer = new Handler(Looper.getMainLooper()); public PinEntryEditText(Context context) { super(context); @@ -148,6 +154,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,17 +406,29 @@ 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 && !mHideLastChar && isFocused() && i == text.length() - 1) { + mMaskChars.append(text.charAt(i)); } else { - mMaskChars.deleteCharAt(mMaskChars.length() - 1); + mMaskChars.append(mMask); } } return mMaskChars; } + private void startLastCharTimerIfNeeded() { + if (!mShouldSkipMaskLastChar) return; + mLastCharTimer.removeCallbacksAndMessages(null); + mLastCharTimer.postDelayed(new Runnable() { + @Override + public void run() { + mHideLastChar = true; + if (mShouldSkipMaskLastChar) invalidate(); + } + }, HIDE_LAST_CHAR_MILLIS); + } private int getColorForState(int... states) { return mColorStates.getColorForState(states, Color.GRAY); @@ -508,15 +527,18 @@ 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; } @@ -540,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() { @@ -578,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 @@ -588,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(); } 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" />