Android中密码提示字体

260

当EditText处于密码模式时,提示似乎以不同的字体(Courier?)显示。我该如何避免这种情况?我希望提示与EditText非密码模式下显示相同的字体。

我当前的xml:

<EditText 
android:hint="@string/edt_password_hint"
android:layout_width="fill_parent"
android:layout_height="wrap_content" 
android:password="true"
android:singleLine="true" />

1
请注意,并非所有设备都会表现出这种行为。 "HTC One X @ 4.1.1" 会(等宽字体),而 "Samsung GT-7510 @4.0.4" 不会(相同字体)。 - Loda
1
在LeWa OS 4.2.2上表现相同。API太不一致了。 - ruX
仍然存在于棒棒糖中。 - Mightian
希望这个能行。你可以在这里检查我的答案:https://dev59.com/t2445IYBdhLWcg3wustT#40695930 - Dmila Ram
18个回答

400

在我的xml文件中更改字体似乎对提示文本没有作用。我发现两种不同的解决方案,其中第二种对我来说具有更好的行为:

  1. 从您的xml文件中删除android:inputType="textPassword"并在Java中设置它:

    EditText password = (EditText) findViewById(R.id.password_text); password.setTransformationMethod(new PasswordTransformationMethod());

使用这种方法,提示字体看起来很好,但是当您在该编辑字段中输入时,您不会看到每个字符以明文形式出现,而是会变成密码点。此外,在全屏输入时,点将不会出现,但是密码会以明文形式出现。

  1. 在xml中保留android:inputType="textPassword"。在Java中,还要设置字体类型和密码方法:

    EditText password = (EditText) findViewById(R.id.register_password_text); password.setTypeface(Typeface.DEFAULT); password.setTransformationMethod(new PasswordTransformationMethod());

这种方法给我想要的提示字体,并且也给我想要的密码点的行为。


3
太好了,这是一些奇怪的行为,你不会从默认设置中预料到的! - Sander Versluys
15
password.setTransformationMethod(new PasswordTransformationMethod()); 这行代码是必要的吗?没有这行代码似乎也能正常工作。(注:该代码用于将EditText中输入的字符转换为密码符号,例如将输入的文本“123456”转换为“******”。) - loeschg
10
更好的是,在这个页面的一个回答中,有一个简单而有效的解决方案:https://dev59.com/33A75IYBdhLWcg3wOWVd#18073897 - Marcin Koziński
16
请注意,使用第一种解决方案是不好的想法——启用自动建议的用户将开始在其他应用程序中看到他们的密码被建议给他们,因为EditText没有声明为inputType="textPassword" - Elad Nava
1
这个不适用于AppCompat - android.support.v7.widget.AppCompatEditText :( - Bharathwaaj
显示剩余9条评论

197

我从 对话框指南 中发现了这个有用的提示:

提示:默认情况下,当您将 EditText 元素设置为使用“textPassword”输入类型时,字体系列会设置为等宽字体,因此您应将其字体系列更改为“sans-serif”,以便两个文本字段使用匹配的字体样式。


例如:

android:fontFamily="sans-serif"

23
应该接受这个答案。它更简单,而且出自文档。 - Marcin Koziński
29
除了只支持 API 等级 16 及以上之外,它还不错。 - Ben Clayton
6
请注意,虽然该功能仅适用于API级别16及以上的设备,但XML属性不会导致旧设备崩溃,它们只是被忽略。 - Adam Johns
如果您多次点击显示/隐藏密码,它将无法正常工作。 - Ali Habbash

32

这是我用来解决这个问题的方法。由于某些原因,我不必设置转换方法,因此这可能是更好的解决方案:

在我的 XML 中:

<EditText
    android:id="@+id/password_edit_field"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="Password"
    android:inputType="textPassword" />

在我的Activity中:

EditText password = (EditText) findViewById( R.id.password_edit_field );
password.setTypeface( Typeface.DEFAULT );

我刚刚尝试了在XML中使用textPassword,就像你在.java文件中所做的那样,它完全正常工作。对我来说,似乎不需要转换。 - Joe Plante
2
提醒:在您通过代码更改InputType后再调用setTypeface()。如果InputType包括xx_VARIATION_PASSWORD,则不需要setTransformationMethod。 - Loda

22

使用setTransformationMethod方法会破坏我的android:imeOption,并允许在密码字段中输入换行符。相反,我正在这样做:

setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
setTypeface(Typeface.DEFAULT);

我在XML中没有设置android:password="true"。


是的,这个方法在6.0.1版本中对我也有效,而被选中的答案则不行。 - Nactus

5

解决这个问题的方法有很多种,但每种方法都有其利弊。以下是我的测试结果:

当启用输入密码时,我只在某些设备(列在答案末尾)中面对这个字体问题。

edtPassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);

如果我使用android:inputType="textPassword",这个问题就不会发生。

我尝试过以下方法:

1)使用setTransformationMethod替代inputType

edtPassword.setTransformationMethod(PasswordTransformationMethod.getInstance());
  • 字体将运作良好
  • 键盘显示不太完美(它只显示文本,不在文本上方显示数字)

2)使用Typeface.DEFAULT

setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
setTypeface(Typeface.DEFAULT);
  • 键盘显示良好,
  • 字体可能不完全正常工作。例如,sans-serif-light是我应用程序中所有View的默认字体 => 在使用setTypeface(Typeface.DEFAULT)之后,EditText的字体在某些设备上仍然看起来不同

3) 使用android:fontFamily="sans-serif"

我的解决方案

setInputType之前缓存字体,然后重复使用它

Typeface cache = edtPassword.getTypeface();
edtPassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
edtPassword.setTypeface(cache);

测试
一些设备遇到了字体问题

  • Xiaomi A2 (8.0.1)
  • Pixel XL (8.1.0)
  • Sony Xperia Z5 Au (SOV32) (6.0)
  • Arrow NX (F-04G) (6.0.1)
  • Kyocera (S2) (7.0)

一些设备没有遇到字体问题

  • Samsung S4 (SC-04E) (5.0.1)
  • Samsung Galaxy Node 5 (5.1.1)
  • Samsung S7 Edge (SM-G935F) (7.0)

设备是什么(名称和操作系统)? - CoolMind
1
@CoolMind,感谢您的建议。我已经根据我的当前设备更新了设备面部字体问题和设备不面部字体问题。 - Linh
感谢进行大规模测试!我从 https://dev59.com/4mkw5IYBdhLWcg3wbqGZ#52178340 这里来。在三星S4上,我使用了 setTransformationMethod 方法(没有遇到问题)。 - CoolMind
正如我在答案中提到的那样,setTransformationMethod会起作用,但键盘显示不正确。然而,如果你可以接受它,那还是没问题的,因为这只是一个小问题。 - Linh

5
manisha提供的方法确实可行,但与默认情况相比,它使密码字段处于非标准状态。也就是说,默认字体样式也适用于密码字段,包括点替换和在被点替换之前出现的预览字符(以及当它是“可见密码”字段时)。
要解决这个问题并使其 1) 看起来和操作起来与默认的textPassword输入类型完全一样,同时也 2) 允许提示文本以默认(非等宽)字体显示,你需要在该字段上使用一个TextWatcher,它可以根据是否为空来正确地在Typeface.DEFAULT和Typeface.MONOSPACE之间切换字体样式。我创建了一个帮助类来完成这个任务:
import android.graphics.Typeface;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;

/**
 * This class watches the text input in a password field in order to toggle the field's font so that the hint text
 * appears in a normal font and the password appears as monospace.
 *
 * <p />
 * Works around an issue with the Hint typeface.
 *
 * @author jhansche
 * @see <a
 * href="https://dev59.com/33A75IYBdhLWcg3wOWVd">https://dev59.com/33A75IYBdhLWcg3wOWVd</a>
 */
public class PasswordFontfaceWatcher implements TextWatcher {
    private static final int TEXT_VARIATION_PASSWORD =
            (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
    private TextView mView;

    /**
     * Register a new watcher for this {@code TextView} to alter the fontface based on the field's contents.
     *
     * <p />
     * This is only necessary for a textPassword field that has a non-empty hint text. A view not meeting these
     * conditions will incur no side effects.
     *
     * @param view
     */
    public static void register(TextView view) {
        final CharSequence hint = view.getHint();
        final int inputType = view.getInputType();
        final boolean isPassword = ((inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
                == TEXT_VARIATION_PASSWORD);

        if (isPassword && hint != null && !"".equals(hint)) {
            PasswordFontfaceWatcher obj = new PasswordFontfaceWatcher(view);
            view.addTextChangedListener(obj);

            if (view.length() > 0) {
                obj.setMonospaceFont();
            } else {
                obj.setDefaultFont();
            }
        }
    }

    public PasswordFontfaceWatcher(TextView view) {
        mView = view;
    }

    public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
        // Not needed
    }

    public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
        if (s.length() == 0 && after > 0) {
            // Input field went from empty to non-empty
            setMonospaceFont();
        }
    }

    public void afterTextChanged(final Editable s) {
        if (s.length() == 0) {
            // Input field went from non-empty to empty
            setDefaultFont();
        }
    }

    public void setDefaultFont() {
        mView.setTypeface(Typeface.DEFAULT);
    }

    public void setMonospaceFont() {
        mView.setTypeface(Typeface.MONOSPACE);
    }
}

接下来,为了使用它,你只需要调用register(View)静态方法即可。其他的都是自动完成的,包括在视图不需要时跳过解决方法!

    final EditText txtPassword = (EditText) view.findViewById(R.id.txt_password);
    PasswordFontfaceWatcher.register(txtPassword);

1
干得好。有一个小改动:如果在EditText上设置了字体(例如将提示文本设置为Roboto Light),那么稍后设置Typeface.DEFAULT将覆盖此设置。我会提前调用field.getTypeface()并在以后需要重置“默认”字体时使用该字体。 - Christopher Orr
嗯,调用 field.getTypeface() 似乎并不完全可靠(因为通常你只会得到等宽字体),但是通过 Typeface.create() 创建一个 Typeface 然后设置它似乎效果很好。 - Christopher Orr

4
其他答案对于大多数情况是正确的解决方案。
然而,如果您正在使用自定义的EditText子类来应用默认的自定义字体等操作,那就有一个微妙的问题。如果您在子类的构造函数中设置了自定义字体,则如果您设置了inputType="textPassword",系统仍会覆盖它。
在这种情况下,请在调用super.onAttachedToWindow后将样式移至onAttachedToWindow
示例实现:
package net.petosky.android.ui;

import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.EditText;

/**
 * An EditText that applies a custom font.
 *
 * @author cory@petosky.net
 */
public class EditTextWithCustomFont extends EditText {

    private static Typeface customTypeface;

    public EditTextWithCustomFont(Context context) {
        super(context);
    }

    public EditTextWithCustomFont(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EditTextWithCustomFont(
            Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * Load and store the custom typeface for this app.
     *
     * You should have a font file in: project-root/assets/fonts/
     */
    private static Typeface getTypeface(Context context) {
        if (customTypeface == null) {
            customTypeface = Typeface.createFromAsset(
                    context.getAssets(), "fonts/my_font.ttf");
        }
        return customTypeface;
    }

    /**
     * Set a custom font for our EditText.
     *
     * We do this in onAttachedToWindow instead of the constructor to support
     * password input types. Internally in TextView, setting the password
     * input type overwrites the specified typeface with the system default
     * monospace.
     */
    @Override protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // Our fonts aren't present in developer tools, like live UI
        // preview in AndroidStudio.
        if (!isInEditMode()) {
            setTypeface(getTypeface(getContext()));
        }
    }
}

3

我知道这可能是较旧的问题,但当我同时使用InputTypeapp:passwordToggleEnabled="true"时,我遇到了与此问题相关的情况。

因此,我写下这篇文章,希望能够帮助到某些人。

我想在密码输入字段中使用自定义字体,并使用app:passwordToggleEnabled选项。但是,在27.1.1(编写本文时)支持库中,它会崩溃。

所以代码如下:

<android.support.design.widget.TextInputLayout
        android:id="@+id/input_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/_10dp"
        android:layout_marginTop="@dimen/_32dp"
        android:hint="@string/current_password"
        android:textColorHint="@color/hint_text_color"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:passwordToggleEnabled="true"
        app:passwordToggleTint="@color/black">


        <EditText
            android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="start|left"
            android:maxLines="1"
            android:textAlignment="viewStart"
            android:textColor="@color/black"
            android:textColorHint="@color/camel"
            android:textSize="@dimen/txt_16sp"
            app:font_style="regular"
            app:drawableEnd="@drawable/ic_remove_eye" />

    </android.support.design.widget.TextInputLayout>

以上代码在XML中没有定义inputType

EditText password = (EditText) findViewById(R.id.password);
password.setTransformationMethod(new PasswordTransformationMethod());

在Java中,setTransformationMethod 方法可以帮助我获取textPassword输入类型的属性,并且我可以使用自定义字体样式。但是在所有API级别中,使用27.1.1支持库时发生了下面提到的崩溃。

java.lang.NullPointerException: 尝试调用空对象引用的'void android.support.design.widget.CheckableImageButton.setChecked(boolean)'方法

这是由于TextInputLayout类中的onRestoreInstanceState引起的。 重现步骤: 切换密码可见性,最小化应用并从最近的应用程序中打开。噢,出问题了!
我需要的只是默认的密码切换选项(使用支持库)和密码输入字段中的自定义字体。
经过一段时间的研究,我找到了以下解决方法:
<android.support.design.widget.TextInputLayout
        android:id="@+id/input_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/_10dp"
        android:layout_marginTop="@dimen/_32dp"
        android:hint="@string/current_password"
        android:textColorHint="@color/hint_text_color"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:passwordToggleEnabled="true"
        app:passwordToggleTint="@color/black">


        <EditText
            android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="start|left"
            android:maxLines="1"
            android:textAlignment="viewStart"
            android:textColor="@color/black"
            android:textColorHint="@color/camel"
            android:textSize="@dimen/txt_16sp"
            app:font_style="regular"
            app:drawableEnd="@drawable/ic_remove_eye"
            android:inputType="textPassword" />

    </android.support.design.widget.TextInputLayout>

在XML中,添加了android:inputType="textPassword"
TextInputLayout inputPassword = findViewById(R.id.input_password);
EditText password = findViewById(R.id.password);
EditText userName = findViewById(R.id.user_name);
// Get the typeface of user name or other edit text
Typeface typeface = userName.getTypeface();
if (typeface != null)
   inputLayout.setTypeface(typeface); // set to password text input layout

在上面的Java代码中,我从用户名 EditText 获取了自定义字体,并将其应用于密码字段的 TextInputLayout 。现在,您不需要显式设置密码 EditText 的字体,因为它将获取 TextInputLayout 的属性。
此外,我删除了 password.setTransformationMethod(new PasswordTransformationMethod()); 通过这种方式, passwordToggleEnabled 正常工作,并且自定义字体也得到应用,崩溃问题也解决了。希望这个问题能在即将发布的支持版本中修复。

2
您还可以使用自定义小部件。这非常简单,不会使您的活动/片段代码混乱。
代码如下:
public class PasswordEditText extends EditText {

  public PasswordEditText(Context context) {
    super(context);
    init();
  }

  public PasswordEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();

  }

  public PasswordEditText(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
  }

  private void init() {
    setTypeface(Typeface.DEFAULT);
  }
}

您的XML将如下所示:
<com.sample.PasswordEditText
  android:id="@+id/password_edit_field"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:hint="Password"
  android:inputType="textPassword"
  android:password="true" />

根据我的经验,这种方法行不通——在构造函数调用之后,内部的 TextView 似乎会覆盖自定义字体。在另一个答案中提供了一种解决方法。 - Cory Petosky

2

使用Calligraphy库

然后,在代码中而不是XML中执行此操作,否则它仍无法使用正确的字体更新密码字段:

Typeface typeface_temp = editText.getTypeface();
editText.setInputType(inputType); /*whatever inputType you want like "TYPE_TEXT_FLAG_NO_SUGGESTIONS"*/
//font is now messed up ..set it back with the below call
editText.setTypeface(typeface_temp); 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接