Material Design 文本输入详解
这是一个系列文章,在这个系列里,我会按打造一个 Material Design App 的路线介绍所有应当掌握和值得掌握的系统组件。 你会在这些文章里了解到这些组件的使用和内部实现原理,以及它们背后所反映的 Material Design 的设计思想,希望你会喜欢。
Tips
我的每一篇博客都会提供详尽的API介绍,如果你想快速查阅某个功能的API或如何实现,建议Ctrl+F(Commad+F)在本页面搜索关键字查找。
Thanks for reading~!
文本输入框通常都有提示语,告知用户这里需要输入什么。提示语通常有两种形式,位于输入框内,输入文字后即消失 或 固定浮于输入框上方或左侧。
将提示置于输入框内能够节省空间,但有个致命的缺点就是容易导致用户失去上下文。想象一下,当我们有十个文本框需要输入的时候,如果无法始终看到提示语,很容易忘记这里该填入什么样的数据。
提示语固定于输入框上方或左侧,让人能够非常明确的知道这里该填入的内容,但在动画效果和焦点区分上却不是十分出色。
结合这两种类型的输入框优点,Material Design推出了一种优秀的文本输入框形式——当用户点击空内容的文本输入框时,原本位于输入框内的提示语会经由一个动画浮动至输入框上方,文本颜色同时变成强调色,明确显示此时该组件获得焦点。
为了让开发者更加方便地实现这种效果,Google推出了TextInputLayout组件。向该组件中放入一个EditText组件(或其扩展类也可,比如TextInputEditText),便可轻松实现Material Design文档中所要求的效果。
其中,要提一下的是TextInputEditText。相比于其他的EditText类,使用该类作为子View放入TextInputLayout可以在全屏模式时依然在编辑器里展示hint。
接下来就让我们一起来详细地整理学习一下TextInputLayout。
xml属性&常用方法
counterEnabled:false or true,用于设置字符计数器的开启或关闭。
对应方法:setCounterEnabled(boolean)
counterMaxLength:设置字符计数器的最大长度。(仅用于设置计数器最大值,并不影响文本实际能输入的最大长度)
对应方法:setCounterMaxLength(int)
errorEnabled:false or true,用于设置错误提示是否开启。
对应方法:setErrorEnabled(boolean)
hint:设置输入框的提示语。
对应方法:setHint(CharSequence)
hintAnimationEnabled:true or false,开启或关闭hint浮动成标签的动画效果。
对应方法:setHintAnimationEnabled(boolean)
hintEnabled:true or false,开启或关闭hint浮动的功能,设为false的话就和之前的EditText一样,在输入文字后,提示语就消失了。
对应方法:setHintEnabled(boolean)
hintTextAppearance:设置hint的style,字体颜色,字体大小等,可引用系统自带的也可以自定义。若要使用请统一使用,以保证APP体验的统一性。
对应方法:setHintTextAppearance(int)
当文本输入类型为密码时,系统提供了一个开关来控制密码是否可见(默认为眼睛👁)。此为design包24.2.0新提供的功能。
passwordToggleEnabled:控制密码可见开关是否启用。设为false则该功能不启用,密码输入框右侧也没有控制密码可见与否的开关。
对应方法:setPasswordVisibilityToggleEnabled(boolean)
passwordToggleDrawable:设置密码可见开关的图标。通常我们会在不同的情况下设定不同的图标,可通过自定义一个selector,根据“state_checked”属性来控制图标的切换。后面代码实践里会有示范。
对应方法:setPasswordVisibilityToggleDrawable(Drawable)
passwordToggleTint:控制密码可见开关图标的颜色。在开启或关闭的状态下我们可以设定不同的颜色,可通过自定义一个color的selector,根据“state_checked”和“state_selected”属性来控制颜色的切换。后面代码实践里会有示范。
对应方法:setPasswordVisibilityToggleTintList(ColorStateList)
passwordToggleTintMode:控制密码可见开关图标的背景颜色混合模式。这个地方我不是很能理解,暂作标记,希望有人可以指教。不过可以肯定的是正常需求都用不到这个属性。
分别是:
- multiply[Sa*Da, Sc*Dc]
- screen[Sa+Da-Sa*Da, Sc+Dc-Sc*Dc]
- src_atop[Da, Sc*Da+(1-Sa)*Dc]
- src_in[Sa*Da, Sc*Da]
- src_over[Sa+(1-Sa)*Da, Rc=Sc+(1-Sa)*Dc]
对应方法:setPasswordVisibilityToggleTintMode(PorterDuff.Mode)
注:关于这方面的知识有兴趣请参考Xfermode in android其中有关于这方面概念的解释。
passwordToggleContentDescription:该功能是为Talkback或其他无障碍功能提供的。TalkBack会去读contentDescription的值,告诉用户这个操作是什么。
对应方法:setPasswordVisibilityToggleContentDescription(int)
效果图
效果视频→点这里
Layout布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jiandanxinli.smileback.materiallogin.LoginActivity">
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:paddingEnd="16dp"
android:paddingStart="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_perm_identity_black_24dp" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hotchpotch_name_tv">
<android.support.design.widget.TextInputEditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_phone_black_24dp" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hotchpotch_phone_tv"
app:counterEnabled="true"
app:counterMaxLength="11">
<android.support.design.widget.TextInputEditText
android:id="@+id/edit_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone"
android:maxLength="11" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_mail_black_24dp" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hotchpotch_mail_tv">
<android.support.design.widget.TextInputEditText
android:id="@+id/edit_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_security_black_24dp" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hotchpotch_password_tv"
app:passwordToggleDrawable="@drawable/visibility_selector"
app:passwordToggleEnabled="true"
app:passwordToggleTint="@color/visibility_selector">
<android.support.design.widget.TextInputEditText
android:id="@+id/edit_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_feedback_black_24dp" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hotchpotch_feedback_tv"
app:counterEnabled="true"
app:counterMaxLength="10"
app:hintTextAppearance="@style/FloatingStyle">
<android.support.design.widget.TextInputEditText
android:id="@+id/edit_feedback"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<Button
android:id="@+id/submit_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="30dp"
android:background="@drawable/btn_selector"
android:text="@string/hotchpotch_submit_btn"
android:textColor="@color/colorWhite"
android:textSize="20sp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
FloatingStyle
<style name="FloatingStyle" parent="@android:style/TextAppearance">
<item name="android:textColor">#FFEB3B</item>
<item name="android:textSize">20sp</item>
</style>
btn_selector
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorPrimaryDark" android:state_pressed="true" />
<item android:drawable="@color/colorPrimary" />
</selector>
drawable/visibility_selector
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_visibility_black_24dp" android:state_checked="false" android:state_pressed="true" />
<item android:drawable="@drawable/ic_visibility_off_black_24dp" android:state_checked="true" android:state_pressed="true" />
<item android:drawable="@drawable/ic_visibility_black_24dp" android:state_checked="true" android:state_pressed="false" />
<item android:drawable="@drawable/ic_visibility_off_black_24dp" android:state_checked="false" android:state_pressed="false" />
</selector>
color/visibility_selector
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorAccent" android:state_checked="false" android:state_pressed="true" />
<item android:color="@color/colorDivider" android:state_checked="true" android:state_pressed="true" />
<item android:color="@color/colorAccent" android:state_checked="true" android:state_pressed="false" />
<item android:color="@color/colorDivider" android:state_checked="false" android:state_pressed="false" />
</selector>
HotchpotchActivity.java
public class HotchpotchActivity extends AppCompatActivity {
private TextInputEditText mNameEditx;
private TextInputEditText mPhoneEditx;
private TextInputEditText mEmailEditx;
private TextInputEditText mPasswordEditx;
private TextInputEditText mFeedbackEditx;
private String mContentName;
private String mContentPhone;
private String mContentEmail;
private String mContentPassword;
private String mContentFeedback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hotchpotch);
initView();
}
private void initView() {
mNameEditx = (TextInputEditText) findViewById(R.id.edit_name);
mPhoneEditx = (TextInputEditText) findViewById(R.id.edit_phone);
mEmailEditx = (TextInputEditText) findViewById(R.id.edit_email);
mPasswordEditx = (TextInputEditText) findViewById(R.id.edit_password);
mFeedbackEditx = (TextInputEditText) findViewById(R.id.edit_feedback);
Button submitBtn = (Button) findViewById(R.id.submit_btn);
submitBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
controlAction();
}
});
}
private void getContent() {
mContentName = mNameEditx.getText().toString();
mContentPhone = mPhoneEditx.getText().toString();
mContentEmail = mEmailEditx.getText().toString();
mContentPassword = mPasswordEditx.getText().toString();
mContentFeedback = mFeedbackEditx.getText().toString();
}
private void controlAction() {
getContent();
if (mContentName.length() == 0) {
showToast("姓名不能为空!");
} else if (mContentPhone.length() != 11) {
showToast("请正确填写11位手机号码!");
} else if (mContentEmail.length() == 0 || !android.util.Patterns.EMAIL_ADDRESS.matcher(mContentEmail).matches()) {
showToast("请正确填写邮箱地址!");
} else if (mContentPassword.length() == 0) {
showToast("密码不能为空");
} else if (mContentFeedback.length() == 0) {
showToast("反馈意见不能为空!");
} else if (mContentFeedback.length() > 10) {
showToast("反馈意见长度字数不能大于10!");
} else {
doSubmission();
}
}
private void doSubmission() {
showToast("恭喜您,数据正确!");
}
private void showToast(String message) {
Toast.makeText(HotchpotchActivity.this, message, Toast.LENGTH_SHORT).show();
}
}
就像文章开头说的,这是一个系列文章,路线是一个APP打开的路线。那么这第一篇就用来写登录页面吧,代码如下:
Layout布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingEnd="32dp"
android:paddingStart="32dp"
tools:context="com.jiandanxinli.smileback.materiallogin.LoginActivity">
<!-- Login progress -->
<ProgressBar
android:id="@+id/login_progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="24dp"
android:focusableInTouchMode="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="48dp"
android:fontFamily="cursive"
android:text="@string/app_name"
android:textSize="48sp"
android:textStyle="bold" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<AutoCompleteTextView
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
app:passwordToggleDrawable="@drawable/visibility_selector">
<android.support.design.widget.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_password"
android:imeActionId="@+id/login"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/email_sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
android:text="@string/action_sign_in"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
</LinearLayout>
LoginActivity.java
/**
* 登录页面
*
* @author bugdev
*/
public class LoginActivity extends AppCompatActivity {
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
//尝试登录
attemptLogin();
return true;
}
return false;
}
});
Button emailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
emailSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//尝试登录
attemptLogin();
}
});
}
/**
* 说明:获取用户输入的内容并调用相应的验证方法
*/
private void attemptLogin() {
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
if (isEmailValid(email) && isPasswordValid(password)) {
login();
}
}
/**
* 说明:验证邮箱格式
*
* @param email 邮箱地址
* @return 邮箱地址格式正确或错误
*/
private boolean isEmailValid(String email) {
if (email.length() == 0) {
showToast("请输入邮箱!");
return false;
} else if (!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
showToast("邮箱格式不正确!");
return false;
} else {
return true;
}
}
/**
* 说明:验证密码格式
*
* @param password 密码
* @return 密码格式正确或错误
*/
private boolean isPasswordValid(String password) {
if (password.length() == 0) {
showToast("请输入密码!");
return false;
} else if (password.length() < 6) {
showToast("请输入至少6位密码!");
return false;
} else {
return true;
}
}
/**
* 说明:显示Toast的方法
*
* @param message 要显示的消息
*/
private void showToast(String message) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
/**
* 说明:登录
* <p>
* 这里为了展示对话框,将对话框固定显示了3秒
*/
private void login() {
showDialog();
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
mProgressDialog.dismiss();
Intent intent = new Intent(LoginActivity.this, HomeActivity.class);
startActivity(intent);
finish();
}
};
timer.schedule(timerTask, 3000);
}
/**
* 说明:show一个Dialog
*/
private void showDialog() {
mProgressDialog = new ProgressDialog(this, R.style.AppTheme_Dark_Dialog);
mProgressDialog.setIndeterminate(true);
mProgressDialog.setMessage("正在验证...");
mProgressDialog.show();
}
}
Dialog Style
<style name="AppTheme.Dark.Dialog" parent="Theme.AppCompat.Dialog">
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColorPrimary">@color/textPrimary</item>
<item name="android:background">@color/colorPrimary</item>
</style>
效果图
效果视频→点这里
Creating a Login Screen Using TextInputLayout
微信公众号: BugDev「bugdev」
知乎专栏:BugDev
欢迎关注啦~ !