Java tutorial
/* * Copyright (C) 2016 Andrew Lord * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. * * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and limitations under the License. */ package com.github.andrewlord1990.materialandroid.component.textfield; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.support.annotation.AttrRes; import android.support.annotation.ColorInt; import android.support.annotation.ColorRes; import android.support.annotation.DrawableRes; import android.support.annotation.IntDef; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.widget.TextViewCompat; import android.support.v7.widget.AppCompatEditText; import android.text.InputType; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import com.github.andrewlord1990.materialandroid.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * An EditText view which meets the Material Design specification for a password input field. It features a visibility * toggle that switches between showing plain text and showing asterisk characters instead. The password is hidden by * default, however, this can be changed using the md_password_shown attribute. The view can be customised through * various setter methods, XML attributes passed to the view or by assigning a style to use for all PasswordEditTexts * to the mdPasswordEditTextStyle theme attribute. */ public class PasswordEditText extends AppCompatEditText { public static final int TOGGLE_OPACITY = 0; public static final int TOGGLE_STRIKETHROUGH = 1; private static final int ALPHA_SHOWN = (int) (255 * 0.54f); private static final int ALPHA_HIDDEN = (int) (255 * 0.38f); private Drawable shownIcon; private Drawable hiddenIcon; @ColorInt private int tintColor; private boolean passwordVisible; /** * Create a PasswordEditText view using settings from the style assigned to the theme attribute * mdPasswordEditTextStyle. * * @param context The Context the view is running in, through which it can access the current theme, resources, etc. */ public PasswordEditText(Context context) { this(context, null); } /** * Create a PasswordEditText view through XML inflation using settings from provided attributes and from the style * assigned to the theme attribute mdPasswordEditTextStyle. * * @param context The Context the view is running in, through which it can access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. */ public PasswordEditText(Context context, AttributeSet attrs) { this(context, attrs, R.attr.mdPasswordEditTextStyle); } /** * Create a PasswordEditText view through XML inflation using settings from provided attributes and from the style * assigned to the specified theme attribute. * * @param context The Context the view is running in, through which it can access the current theme, resources, * etc. * @param attrs The attributes of the XML tag that is inflating the view. * @param defStyleAttr An attribute in the current theme that contains a reference to a style resource that supplies * default values for the view. Can be 0 to not look for defaults. */ public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); loadThemeAttributes(attrs, defStyleAttr); } private void loadThemeAttributes(AttributeSet attrs, @AttrRes int themeAttribute) { TypedArray typedAttrs = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.MDPasswordEditText, themeAttribute, 0); try { loadIcons(typedAttrs); loadToggleType(typedAttrs); loadToggleTintColor(typedAttrs); loadPasswordShown(typedAttrs); } finally { typedAttrs.recycle(); } setPasswordVisibility(); } private void loadIcons(TypedArray attrs) { shownIcon = attrs.getDrawable(R.styleable.MDPasswordEditText_md_password_shown_drawable); hiddenIcon = attrs.getDrawable(R.styleable.MDPasswordEditText_md_password_hidden_drawable); } private void loadToggleType(TypedArray attrs) { int type = attrs.getInt(R.styleable.MDPasswordEditText_md_password_toggle_type, TOGGLE_OPACITY); if (shownIcon == null) { setShownIcon(); } if (hiddenIcon == null) { setHiddenIconFromType(type); } } private void setShownIcon() { shownIcon = getIcon(R.drawable.ic_password_visibility_default); shownIcon.setAlpha(ALPHA_SHOWN); } private void setHiddenIconFromType(int type) { if (type == TOGGLE_OPACITY) { hiddenIcon = getIcon(R.drawable.ic_password_visibility_default); hiddenIcon.setAlpha(ALPHA_HIDDEN); } else if (type == TOGGLE_STRIKETHROUGH) { hiddenIcon = getIcon(R.drawable.ic_password_visibility_strikethrough); hiddenIcon.setAlpha(ALPHA_SHOWN); } } private Drawable getIcon(@DrawableRes int drawable) { return ContextCompat.getDrawable(getContext(), drawable).mutate(); } private void loadToggleTintColor(TypedArray attrs) { tintColor = attrs.getColor(R.styleable.MDPasswordEditText_md_password_toggle_tint_color, 0); } private void loadPasswordShown(TypedArray attrs) { passwordVisible = attrs.getBoolean(R.styleable.MDPasswordEditText_md_password_shown, false); } private void setPasswordVisibility() { setPasswordInputType(); setDrawables(); } private void setDrawables() { Drawable[] drawables = getDrawables(); if (passwordVisible) { drawables[2] = shownIcon; } else { drawables[2] = hiddenIcon; } TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, drawables[0], drawables[1], tintDrawable(drawables[2]), drawables[3]); } private Drawable tintDrawable(Drawable drawable) { if (tintColor != 0) { Drawable wrapper = DrawableCompat.wrap(drawable); DrawableCompat.setTint(wrapper, tintColor); return wrapper; } return drawable; } private void setPasswordInputType() { if (passwordVisible) { setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); } else { setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } } /** * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. When the input type is * set to be a password, by default Android sets the typeface of the view to Monospace. This view instead will * maintain whichever typeface the view currently has, meaning if you set a custom typeface it won't be lost when * changing the input type. It also maintains the position of the insertion point within the EditText. * * @param type The input type to apply to this view. * @see android.text.InputType */ @Override public void setInputType(int type) { Typeface typeface = getTypeface(); int selectionStart = getSelectionStart(); int selectionEnd = getSelectionEnd(); super.setInputType(type); setTypeface(typeface); setSelection(selectionStart, selectionEnd); } Drawable[] getDrawables() { if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { return getCompoundDrawablesRelative(); } return getCompoundDrawables(); } /** * This will be fired when a touch-screen motion event occurs. It is used to handle the visibility toggle being * pressed on. * * @param event The touch-screen motion event which has occurred. * @return Whether the event has been consumed. It will be consumed if the visibility toggle has been pressed. */ @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP && isTouchEventWithinToggle(event)) { togglePasswordVisibility(); return true; } return super.onTouchEvent(event); } private boolean isTouchEventWithinToggle(MotionEvent event) { int xPosition = (int) event.getX(); Rect bounds = getDrawables()[2].getBounds(); boolean withinIconLeftToRight = isLeftToRight() && (xPosition >= getWidth() - bounds.width()); boolean withinIconRightToLeft = !isLeftToRight() && (xPosition <= bounds.width()); return withinIconLeftToRight || withinIconRightToLeft; } private boolean isLeftToRight() { return VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN_MR1 || isLeftToRightLayoutDirection(); } @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) private boolean isLeftToRightLayoutDirection() { Configuration config = getResources().getConfiguration(); return config.getLayoutDirection() != View.LAYOUT_DIRECTION_RTL; } /** * Returns whether the password is currently visible within the view. * * @return Whether the password is currently visible. */ public boolean isPasswordVisible() { return passwordVisible; } /** * Set the visibility of the password. * * @param visible Whether the password should be displayed in plain text. */ public void setPasswordVisible(boolean visible) { passwordVisible = visible; setPasswordVisibility(); } /** * Toggle the visibility of the password. If it is current visible, it will hidden and replaced with asterisk * characters. If it is currently hidden then it will be displayed in plain text. */ public void togglePasswordVisibility() { setPasswordVisible(!passwordVisible); } /** * Get the tint color which be applied to the visibility toggle. This was assigned either through the setter, an XML * attribute when the view was inflated or through the global style. * * @return The visibility toggle tint color. */ @ColorInt public int getTintColor() { return tintColor; } /** * Set the tint color to apply to the visibility toggle to the specified color value. * * @param tintColor The tint color to apply. */ public void setTintColor(@ColorInt int tintColor) { this.tintColor = tintColor; setPasswordVisibility(); } /** * Set the tint color to apply to the visibility toggle to the color linked to by the specified color resource. * * @param tintColorRes The resource of the tint color to apply. */ public void setTintColorRes(@ColorRes int tintColorRes) { tintColor = ContextCompat.getColor(getContext(), tintColorRes); setPasswordVisibility(); } /** * Set the drawable to use for the visibility toggle when the password is currently being shown. * * @param shownDrawable Drawable to use for visibility toggle whilst password is being shown. */ public void setShownDrawable(Drawable shownDrawable) { shownIcon = shownDrawable; setPasswordVisibility(); } /** * Set the drawable to use for the visibility toggle when the password is currently being shown. * * @param shownDrawableRes Drawable to use for visibility toggle whilst password is being shown. */ public void setShownDrawable(@DrawableRes int shownDrawableRes) { shownIcon = ContextCompat.getDrawable(getContext(), shownDrawableRes); setPasswordVisibility(); } /** * Set the drawable to use for the visibility toggle when the password is currently hidden and being displayed as * asterisk characters. * * @param hiddenDrawable Drawable to use for visibility toggle whilst password is being hidden. */ public void setHiddenDrawable(Drawable hiddenDrawable) { hiddenIcon = hiddenDrawable; setPasswordVisibility(); } /** * Set the drawable to use for the visibility toggle when the password is currently hidden and being displayed as * asterisk characters. * * @param hiddenDrawableRes Drawable to use for visibility toggle whilst password is being hidden. */ public void setHiddenDrawable(@DrawableRes int hiddenDrawableRes) { hiddenIcon = ContextCompat.getDrawable(getContext(), hiddenDrawableRes); setPasswordVisibility(); } /** * Set the type of visibility toggle to use. When the password is hidden, then either change the toggle's opacity or * replace it with an icon with a strike through it. * * @param toggleType The type of visibility toggle to use. */ public void setToggleType(@ToggleType int toggleType) { setShownIcon(); setHiddenIconFromType(toggleType); setPasswordVisibility(); } /** * Specifies that an int variable must contain the type of toggle to display in the PasswordEditText. */ @IntDef({ TOGGLE_OPACITY, TOGGLE_STRIKETHROUGH }) @Retention(RetentionPolicy.SOURCE) public @interface ToggleType { } }