教你写一个炫酷的Material Design 风格的登录和注册页面

每个人都会喜欢漂亮的登录界面,一个App 给人们的第一印象是非常重要的。

这篇文章将教你使用谷歌材料设计规范(Material design spec )和谷歌的新的设计支持库( design support library)来创建一个炫酷的登录和注册界面。设计支持库实现了材料设计规范的一部分,它包含了一部分炫酷的UI 部件,让你的Android 应用给人一种优雅的感觉。

对事物的设计和布局方面,如何做到让人感觉到屏幕上的内容是赏心悦目的,这里我们要权衡的的重点,我们会在顶部状态栏添加精细的触摸事件,并使用设计支持库的floating labels (实现自TextInputLayout)。

几乎所有的事情都都已经照顾到你。

  • 完整的代码和样例托管在Github
  • 当接口锁定时,防止后退按钮显示在登录Activity 上。
  • 自定义 ProgressDialog来显示加载的状态。
  • 符合材料设计规范。
  • 悬浮标签(floating labels)(来自设计支持库)
  • 用户表单输入校验
  • 自定义状态栏样式
  • 在每一个Activity 测试模仿验证的方法。

剩下的就是实现自己的身份验证逻辑。

获取源码

登录Activity

让我们来设置登录Activity,通常是开始你的应用程序,会显示给用户的第一个要启动的Activity。

如果你想要添加社交登录按钮,请继续,但是当前在这个文章范围内,只给你基本的代码,让你有一个坚固的起点去构建你的验证流程。

需要注意的是 onBackPressed 方法将会被重写,这样将会防止用户关闭登录Activity。

LoginActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.sourcey.materiallogindemo;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import butterknife.ButterKnife;
import butterknife.InjectView;

public class LoginActivity extends AppCompatActivity {
private static final String TAG = "LoginActivity";
private static final int REQUEST_SIGNUP = 0;

@Bind(R.id.input_email) EditText _emailText;
@Bind(R.id.input_password) EditText _passwordText;
@Bind(R.id.btn_login) Button _loginButton;
@Bind(R.id.link_signup) TextView _signupLink;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ButterKnife.inject(this);

_loginButton.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
login();
}
});

_signupLink.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
// Start the Signup activity
Intent intent = new Intent(getApplicationContext(), SignupActivity.class);
startActivityForResult(intent, REQUEST_SIGNUP);
}
});
}

public void login() {
Log.d(TAG, "Login");

if (!validate()) {
onLoginFailed();
return;
}

_loginButton.setEnabled(false);

final ProgressDialog progressDialog = new ProgressDialog(LoginActivity.this,
R.style.AppTheme_Dark_Dialog);
progressDialog.setIndeterminate(true);
progressDialog.setMessage("Authenticating...");
progressDialog.show();

String email = _emailText.getText().toString();
String password = _passwordText.getText().toString();

// TODO: Implement your own authentication logic here.

new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
// On complete call either onLoginSuccess or onLoginFailed
onLoginSuccess();
// onLoginFailed();
progressDialog.dismiss();
}
}, 3000);
}


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_SIGNUP) {
if (resultCode == RESULT_OK) {

// TODO: Implement successful signup logic here
// By default we just finish the Activity and log them in automatically
this.finish();
}
}
}

@Override
public void onBackPressed() {
// disable going back to the MainActivity
moveTaskToBack(true);
}

public void onLoginSuccess() {
_loginButton.setEnabled(true);
finish();
}

public void onLoginFailed() {
Toast.makeText(getBaseContext(), "Login failed", Toast.LENGTH_LONG).show();

_loginButton.setEnabled(true);
}

public boolean validate() {
boolean valid = true;

String email = _emailText.getText().toString();
String password = _passwordText.getText().toString();

if (email.isEmpty() || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
_emailText.setError("enter a valid email address");
valid = false;
} else {
_emailText.setError(null);
}

if (password.isEmpty() || password.length() < 4 || password.length() > 10) {
_passwordText.setError("between 4 and 10 alphanumeric characters");
valid = false;
} else {
_passwordText.setError(null);
}

return valid;
}
}

res/layout/activity_login.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true">


<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="56dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">


<ImageView android:src="@drawable/logo"
android:layout_width="wrap_content"
android:layout_height="72dp"
android:layout_marginBottom="24dp"
android:layout_gravity="center_horizontal" />


<!-- Email Label -->
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">

<EditText android:id="@+id/input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:hint="Email" />

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

<!-- Password Label -->
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">

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

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

<android.support.v7.widget.AppCompatButton
android:id="@+id/btn_login"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:padding="12dp"
android:text="Login"/>


<TextView android:id="@+id/link_signup"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="No account yet? Create one"
android:gravity="center"
android:textSize="16dip"/>


</LinearLayout>
</ScrollView>

注册 Activity

注册Activity 可以让你在App 中创建一个用户,通常会在登录Activity 中显示(注册的)链接。

需要注意的是当用户注册成功时我们会设置一个RESULT_OK 的结果值,这个结果将会在登录Activity 中的 onActivityResult 方法中调用,并且确定注册成功是如何处理的。当前逻辑是很简单的,当用户注册成功时我们会马上做一个记录。当然你想要实现邮箱验证,你需要自己来实现。

SignupActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package com.sourcey.materiallogindemo;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import butterknife.ButterKnife;
import butterknife.InjectView;

public class SignupActivity extends AppCompatActivity {
private static final String TAG = "SignupActivity";

@Bind(R.id.input_name) EditText _nameText;
@Bind(R.id.input_email) EditText _emailText;
@Bind(R.id.input_password) EditText _passwordText;
@Bind(R.id.btn_signup) Button _signupButton;
@Bind(R.id.link_login) TextView _loginLink;


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_signup);
ButterKnife.inject(this);

_signupButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
signup();
}
});

_loginLink.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Finish the registration screen and return to the Login activity
finish();
}
});
}

public void signup() {
Log.d(TAG, "Signup");

if (!validate()) {
onSignupFailed();
return;
}

_signupButton.setEnabled(false);

final ProgressDialog progressDialog = new ProgressDialog(SignupActivity.this,
R.style.AppTheme_Dark_Dialog);
progressDialog.setIndeterminate(true);
progressDialog.setMessage("Creating Account...");
progressDialog.show();

String name = _nameText.getText().toString();
String email = _emailText.getText().toString();
String password = _passwordText.getText().toString();

// TODO: Implement your own signup logic here.

new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
// On complete call either onSignupSuccess or onSignupFailed
// depending on success
onSignupSuccess();
// onSignupFailed();
progressDialog.dismiss();
}
}, 3000);
}


public void onSignupSuccess() {
_signupButton.setEnabled(true);
setResult(RESULT_OK, null);
finish();
}

public void onSignupFailed() {
Toast.makeText(getBaseContext(), "Login failed", Toast.LENGTH_LONG).show();

_signupButton.setEnabled(true);
}

public boolean validate() {
boolean valid = true;

String name = _nameText.getText().toString();
String email = _emailText.getText().toString();
String password = _passwordText.getText().toString();

if (name.isEmpty() || name.length() < 3) {
_nameText.setError("at least 3 characters");
valid = false;
} else {
_nameText.setError(null);
}

if (email.isEmpty() || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
_emailText.setError("enter a valid email address");
valid = false;
} else {
_emailText.setError(null);
}

if (password.isEmpty() || password.length() < 4 || password.length() > 10) {
_passwordText.setError("between 4 and 10 alphanumeric characters");
valid = false;
} else {
_passwordText.setError(null);
}

return valid;
}
}

res/layout/activity_signup.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true">


<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="56dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">


<ImageView android:src="@drawable/logo"
android:layout_width="wrap_content"
android:layout_height="72dp"
android:layout_marginBottom="24dp"
android:layout_gravity="center_horizontal" />


<!-- Name Label -->
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">

<EditText android:id="@+id/input_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:hint="Name" />

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

<!-- Email Label -->
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">

<EditText android:id="@+id/input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:hint="Email" />

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

<!-- Password Label -->
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">

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

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

<!-- Signup Button -->
<android.support.v7.widget.AppCompatButton
android:id="@+id/btn_signup"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:padding="12dp"
android:text="Create Account"/>


<TextView android:id="@+id/link_login"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="Already a member? Login"
android:gravity="center"
android:textSize="16dip"/>


</LinearLayout>
</ScrollView>

配置

为了让程序一切正常工作,我们在需要在 app 目录下的 build.gradle 中添加一些依赖,ButterKnife 是可选的,当然我们更喜欢用它让我们的Java 代码更加整洁一些。

1
2
3
4
5
6
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:design:22.2.0'
compile 'com.jakewharton:butterknife:7.0.1'
}

还有一个我们必须要在AndroidManifest 中添加声明Activity。我已经把AndroidManifest 清晰完整的代码贴了出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sourcey.materiallogindemo" >


<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >

<activity
android:name=".MainActivity"
android:label="@string/app_name" >

<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".LoginActivity" android:theme="@style/AppTheme.Dark" />
<activity android:name=".SignupActivity" android:theme="@style/AppTheme.Dark" />
</application>

</manifest>

希望这篇文章对你是有帮助的,如果这篇文章真的节约你宝贵的开发时间,请给我留言。


本文作者:sourcey
本文译者:Tikitoo
原文链接:http://sourcey.com/beautiful-android-login-and-signup-screens-with-material-design/
翻译链接:http://tikitoo.github.io/2016/05/14/beautiful-android-login-and-signup-screens-with-material-design-zh
非商业转载转载请在开头注明作者详细信息本文出处,以及本文所有内容。

本文首发我的微信公众号,分享Android 开发互联网内容
微信号:AndroidMate
公众号:安卓同学
安卓同学