Skip to content

Commit

Permalink
Improve password visibility animation.
Browse files Browse the repository at this point in the history
– Beter encapsulation.
– Improve the animation by moving the scaling character.
– Fix bug with text color.
– More comments & meaningful variables.
  • Loading branch information
nickbutcher committed Aug 25, 2016
1 parent 558cead commit f960645
Showing 1 changed file with 41 additions and 42 deletions.
83 changes: 41 additions & 42 deletions app/src/main/java/io/plaidapp/ui/widget/PasswordEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
Expand All @@ -32,7 +32,6 @@
import android.text.TextPaint;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.Property;
import android.view.animation.Interpolator;

import io.plaidapp.R;
Expand All @@ -45,10 +44,11 @@
*/
public class PasswordEntry extends TextInputEditText {

static final char[] PASSWORD_MASK = { '' };
static final char[] PASSWORD_MASK = { '\u2022' }; // PasswordTransformationMethod#DOT

private boolean passwordMasked = false;
private MaskMorphDrawable maskDrawable;
private ColorStateList textColor;

public PasswordEntry(Context context) {
super(context);
Expand Down Expand Up @@ -79,30 +79,35 @@ public void setText(CharSequence text, BufferType type) {
}
}

@Override
public void setTextColor(ColorStateList colors) {
super.setTextColor(colors);
textColor = colors;
}

private void passwordVisibilityToggled(boolean isMasked, CharSequence password) {
if (maskDrawable == null) {
// lazily create the drawable that morphs the dots
if (!isLaidOut() || getText().length() < 1) return;
maskDrawable = new MaskMorphDrawable(getContext(), getPaint(), getBaseline(),
getLayout().getPrimaryHorizontal(1), getTextLeft());
maskDrawable.setBounds(getPaddingLeft(), getPaddingTop(), 0,
maskDrawable.setBounds(getPaddingLeft(), getPaddingTop(), getPaddingLeft(),
getHeight() - getPaddingBottom());
getOverlay().add(maskDrawable);
}

// hide the text during the animation
final ColorStateList textColors = getTextColors();
setTextColor(Color.TRANSPARENT);
Animator morph = isMasked ?
Animator maskMorph = isMasked ?
maskDrawable.createShowMaskAnimator(password)
: maskDrawable.createHideMaskAnimator(password);
morph.addListener(new AnimatorListenerAdapter() {
maskMorph.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setTextColor(textColors); // restore the proper text color
setTextColor(textColor); // restore the proper text color
}
});
morph.start();
maskMorph.start();
}

private int getTextLeft() {
Expand All @@ -122,6 +127,8 @@ private int getTextLeft() {
static class MaskMorphDrawable extends Drawable {

private static final float NO_PROGRESS = -1f;
private static final float PROGRESS_CHARACTER = 0f;
private static final float PROGRESS_MASK = 1f;

private final TextPaint paint;
private final float charWidth;
Expand Down Expand Up @@ -154,23 +161,12 @@ static class MaskMorphDrawable extends Drawable {
fastOutSlowIn = AnimUtils.getFastOutSlowInInterpolator(context);
}

public float getMorphProgress() {
return morphProgress;
}

public void setMorphProgress(float morphProgress) {
if (this.morphProgress != morphProgress) {
this.morphProgress = morphProgress;
invalidateSelf();
}
}

Animator createShowMaskAnimator(CharSequence password) {
return morphPassword(password, 0f, 1f, hidePasswordDuration);
return morphPassword(password, PROGRESS_CHARACTER, PROGRESS_MASK, hidePasswordDuration);
}

Animator createHideMaskAnimator(CharSequence password) {
return morphPassword(password, 1f, 0f, showPasswordDuration);
return morphPassword(password, PROGRESS_MASK, PROGRESS_CHARACTER, showPasswordDuration);
}

@Override
Expand All @@ -187,7 +183,7 @@ public void draw(Canvas canvas) {

@Override
public void setAlpha(int alpha) {
if (alpha != getAlpha()) {
if (alpha != paint.getAlpha()) {
paint.setAlpha(alpha);
invalidateSelf();
}
Expand All @@ -213,7 +209,15 @@ private Animator morphPassword(
characters[i] = new PasswordCharacter(passStr, i, paint, maskDiameter, maskCenterY);
}

Animator anim = ObjectAnimator.ofFloat(this, MORPH, fromProgress, toProgress);
ValueAnimator anim = ValueAnimator.ofFloat(fromProgress, toProgress);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
morphProgress = (float) valueAnimator.getAnimatedValue();
invalidateSelf();
}
});

anim.setDuration(duration);
anim.setInterpolator(fastOutSlowIn);
anim.addListener(new AnimatorListenerAdapter() {
Expand All @@ -223,6 +227,7 @@ public void onAnimationEnd(Animator animation) {
morphProgress = NO_PROGRESS;
password = null;
updateBounds();
invalidateSelf();
}
});
return anim;
Expand All @@ -231,28 +236,16 @@ public void onAnimationEnd(Animator animation) {
private void updateBounds() {
Rect oldBounds = getBounds();
if (password != null) {
setBounds(oldBounds.left, oldBounds.top,
setBounds(
oldBounds.left,
oldBounds.top,
oldBounds.left + (int) Math.ceil(password.length() * charWidth),
oldBounds.bottom);
} else {
setBounds(oldBounds.left, oldBounds.top, oldBounds.left, oldBounds.bottom);
}
}

static final Property<MaskMorphDrawable, Float> MORPH
= new AnimUtils.FloatProperty<MaskMorphDrawable>("morphProgress") {

@Override
public void setValue(MaskMorphDrawable drawable, float progress) {
drawable.setMorphProgress(progress);
}

@Override
public Float get(MaskMorphDrawable drawable) {
return drawable.getMorphProgress();
}
};

}

/**
Expand Down Expand Up @@ -289,21 +282,27 @@ void draw(Canvas canvas, TextPaint paint, CharSequence password,
// draw the character
canvas.save();
float textScale = lerp(1f, textToMaskScale, progress);
// scale character: shrinks to/grows from the mask's height, remaining centered
// scale character: shrinks to/grows from the mask's height
canvas.scale(textScale, textScale, x + bounds.exactCenterX(), bounds.exactCenterY());
// cross fade between the character/mask
paint.setAlpha((int) lerp(alpha, 0, progress));
canvas.drawText(password, index, index + 1, x, 0, paint);
// vertically move the character center toward/from the mask center
canvas.drawText(password, index, index + 1,
x, lerp(0f, textOffsetY, progress) / textScale, paint);
canvas.restore();

// draw the mask
canvas.save();
float maskScale = lerp(maskToTextScale, 1f, progress);
// scale the mask: down from/up to the character width
canvas.scale(maskScale, maskScale, x + bounds.exactCenterX(), bounds.exactCenterY());
// cross fade between the mask/character
paint.setAlpha((int) AnimUtils.lerp(0, alpha, progress));
// vertically move the mask: character center ↔ mask center
// vertically move the mask center from/toward the character center
canvas.drawText(PASSWORD_MASK, 0, 1, x, -lerp(textOffsetY, 0f, progress), paint);
canvas.restore();

// restore the paint to how we found it
paint.setAlpha(alpha);
}

Expand Down

0 comments on commit f960645

Please sign in to comment.