系列专栏链接如下,方便跟进:
https://blog.csdn.net/weixin_62588253/category_12821860.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12821860&sharerefer=PC&sharesource=weixin_62588253&sharefrom=from_linkhttps://blog.csdn.net/weixin_62588253/category_12821860.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12821860&sharerefer=PC&sharesource=weixin_62588253&sharefrom=from_link
同时篇幅有限,不能把所有文件列举出来,想要跟进代码,进入我个人仓库查看即可。觉得还可以的给个Star是对我最大的支持。
https://github.com/Messimeimei/PersonalExpenseTrackerhttps://github.com/Messimeimei/PersonalExpenseTracker
24.11.10
为了开发的方便(每次真机调试都需要重新登录),这篇文章讲解如何保持app的登录状态和实现退出登录。即只要登录了,除非主动退出登录,否则就算清空后台进程下次打开app后依旧是明细页面。
1.设置页面布局
首先我们创建一个设置页面,在里面加入退出登录的按钮。首先看下效果图:
代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background_grey">
<!-- 返回箭头和标题栏 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/Argentina_blue"
android:padding="16dp">
<ImageView
android:id="@+id/backButton"
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/ic_arrow_back"
android:clickable="true"
android:focusable="true"
android:layout_marginEnd="16dp"/>
<TextView
android:layout_width="260dp"
android:layout_height="wrap_content"
android:text="账户设置"
android:textSize="18sp"
android:textColor="@color/black"
android:gravity="center"/>
</LinearLayout>
<!-- 顶部间距 -->
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/white_smoke"/>
<!-- 设置内容布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="@color/white_smoke">
<!-- 头像行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="头像"
android:textSize="16sp"
android:textColor="@color/black"/>
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/app_logo"
app:shapeAppearanceOverlay="@style/RoundedCornersImage"
android:layout_marginEnd="8dp"/>
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/ic_arrow_right"/>
</LinearLayout>
<!-- ID行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="ID"
android:textSize="16sp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/userIdTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="61793910"
android:textSize="16sp"
android:textColor="@color/black"/>
</LinearLayout>
<!-- 昵称行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="昵称"
android:textSize="16sp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/nicknameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Disfruta el momento"
android:textSize="16sp"
android:textColor="@color/black"/>
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/ic_arrow_right"/>
</LinearLayout>
<!-- 手机号行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="手机号"
android:textSize="16sp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/phoneTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未绑定"
android:textSize="16sp"
android:textColor="@color/Argentina_yellow"/>
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/ic_arrow_right"/>
</LinearLayout>
<!-- 微信行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="微信"
android:textSize="16sp"
android:textColor="@color/black"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Disfruta el momento"
android:textSize="16sp"
android:textColor="@color/black"/>
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/ic_arrow_right"/>
</LinearLayout>
<!-- 应急联系方式行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="应急联系方式"
android:textSize="16sp"
android:textColor="@color/black"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未设置"
android:textSize="16sp"
android:textColor="@color/Argentina_yellow"/>
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@drawable/ic_arrow_right"/>
</LinearLayout>
<!-- 注销账号 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="申请注销账号"
android:textSize="16sp"
android:textColor="@color/grey"
android:padding="16dp"
android:gravity="center"/>
<!-- 退出登录按钮 -->
<!-- 注册按钮 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/logoutButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="退出登录"
android:layout_marginTop="24dp"
app:backgroundTint="@color/Argentina_blue"
android:textColor="@color/black"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
除了可以退出登录外,还实现了展示用户的ID和昵称以及手机号。这里的逻辑代码需要在设置的活动类里面介绍。
2.设置活动类
在活动类中我们为退出登录按钮设置了监听,完成退出就是利用SharedPreferences,然后clear掉即可。同时我们从手机登录页面中获取用户的信息。
package com.example.personalexpensetracker.ui.activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.example.personalexpensetracker.R;
public class SettingsActivity extends AppCompatActivity {
private TextView userIdTextView, nicknameTextView, phoneTextView;
private ImageView backButton;
private Button logoutButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
// 绑定UI组件
backButton = findViewById(R.id.backButton);
userIdTextView = findViewById(R.id.userIdTextView);
nicknameTextView = findViewById(R.id.nicknameTextView);
phoneTextView = findViewById(R.id.phoneTextView);
logoutButton = findViewById(R.id.logoutButton); // 新增的“退出登录”按钮
// 设置任务栏颜色为蓝色
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(getResources().getColor(R.color.Argentina_blue)); // 蓝色背景色
}
// 设置返回按钮点击事件
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
// 获取 SharedPreferences 中保存的用户信息
SharedPreferences sharedPreferences = getSharedPreferences("AppPreferences", MODE_PRIVATE);
String userId = sharedPreferences.getString("userId", "N/A");
String nickname = sharedPreferences.getString("nickname", "N/A");
String phone = sharedPreferences.getString("phone", "N/A");
// 将信息显示在 TextView 中
userIdTextView.setText(userId);
nicknameTextView.setText(nickname);
phoneTextView.setText(phone);
// 设置“退出登录”按钮点击事件
logoutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 清除登录状态和用户信息
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.apply();
// 跳转到启动页面(例如 LoginActivity)
Intent intent = new Intent(SettingsActivity.this, PhoneLoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); // 清除返回栈
startActivity(intent);
}
});
}
}
3.手机号登录页面做出相应修改
在原来的登录页面我们需要做出相应修改,把登录的状态写入到SharedPreferences中。
package com.example.personalexpensetracker.ui.activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.text.method.PasswordTransformationMethod;
import android.view.Gravity;
import android.view.View;
import android.widget.*;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.example.personalexpensetracker.R;
import com.example.personalexpensetracker.data.dao.UserDao;
import com.example.personalexpensetracker.data.database.AppDatabase;
import com.example.personalexpensetracker.data.model.User;
import com.example.personalexpensetracker.utils.AppExecutors;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.example.personalexpensetracker.utils.DialogUtils;
import android.util.Patterns;
public class PhoneLoginActivity extends AppCompatActivity {
private EditText phoneEditText, passwordEditText;
private CheckBox agreementCheckBox;
private Button loginButton, registerButton;
private ImageView backButton, passwordEyeIcon;
private UserDao userDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_phone_login);
// 设置输入框的圆角
MaterialShapeDrawable shapeDrawable = new MaterialShapeDrawable();
shapeDrawable.setFillColor(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.white_smoke)));
shapeDrawable.setShapeAppearanceModel(
shapeDrawable.getShapeAppearanceModel()
.toBuilder()
.setAllCornerSizes(24) // 设置圆角大小
.build());
passwordEyeIcon = findViewById(R.id.passwordEyeIcon);
phoneEditText = findViewById(R.id.phoneEditText);
phoneEditText.setBackground(shapeDrawable);
passwordEditText = findViewById(R.id.passwordEditText);
passwordEditText.setBackground(shapeDrawable);
backButton = findViewById(R.id.backButton);
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
AppDatabase db = AppDatabase.getInstance(this);
userDao = db.userDao();
agreementCheckBox = findViewById(R.id.agreementCheckBox);
loginButton = findViewById(R.id.loginButton);
registerButton = findViewById(R.id.registerButton);
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 执行登录逻辑
String phone = phoneEditText.getText().toString();
String password = passwordEditText.getText().toString();
if (phone.isEmpty() || !isValidPhone(phone)) {
showToast("手机号格式不正确!");
} else if (password.isEmpty()) {
showToast("请输入密码!");
} else if (!agreementCheckBox.isChecked()) {
DialogUtils.showAgreementDialog(PhoneLoginActivity.this, new Runnable() {
@Override
public void run() {
// 用户点击同意协议,勾选复选框并弹出手机号确认框
agreementCheckBox.setChecked(true);
checkLogin(phone, password);
}
}, new Runnable() {
@Override
public void run() {
// 用户点击取消,不做任何操作,保持状态
}
});
} else {
// 检查登录信息
checkLogin(phone, password);
}
}
});
registerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 跳转到注册页面
Intent intent = new Intent(PhoneLoginActivity.this, RegisterActivity.class);
startActivity(intent);
}
});
// 初始状态:眼睛是闭上的,密码不可见
passwordEyeIcon.setImageResource(R.drawable.ic_eye_close);
passwordEditText.setInputType(0x00000081); // 密码不可见
// 密码可见性切换
passwordEyeIcon.setOnClickListener(v -> togglePasswordVisibility(passwordEditText, passwordEyeIcon, R.drawable.ic_eye_open, R.drawable.ic_eye_close));
}
// 检查登录信息
private void checkLogin(String phone, String password) {
// 同样在子线程中执行
AppExecutors.getDiskIO().execute(() -> {
User user = userDao.getUserByPhone(phone); // 根据手机号获取该用户
if (user == null) {
runOnUiThread(() -> {
Toast.makeText(PhoneLoginActivity.this, "该手机号未注册", Toast.LENGTH_SHORT).show();
});
} else if (!user.getPassword().equals(password)) {
// 密码错误
runOnUiThread(() -> {
Toast.makeText(PhoneLoginActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
});
} else {
// 登录成功
runOnUiThread(() -> {
Toast.makeText(PhoneLoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
});
// 保存登录状态
SharedPreferences sharedPreferences = getSharedPreferences("AppPreferences", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("userId", String.valueOf(user.getFormattedId())); // 假设 user 对象有 getId() 方法
editor.putString("nickname", user.getNickname()); // 假设 user 对象有 getNickname() 方法
editor.putString("phone", user.getPhone()); // 假设 user 对象有 getPhone() 方法
editor.putBoolean("isLoggedIn", true);
editor.apply();
// 跳转到下一个页面
startActivity(new Intent(PhoneLoginActivity.this, ExpenseRecordDisplayActivity.class));
finish();
}
});
}
// 密码可见性切换
private void togglePasswordVisibility(EditText editText, ImageView eyeIcon, int visibleIcon, int hiddenIcon) {
// 根据点击的是哪个眼睛图标来切换相应输入框的可见性
boolean isVisible = editText.getTransformationMethod() instanceof PasswordTransformationMethod;
if (isVisible) {
// 设置为可见密码
editText.setTransformationMethod(null); // 不使用隐藏密码的转换方法
eyeIcon.setImageResource(visibleIcon); // 设置睁眼图标
} else {
// 设置为隐藏密码
editText.setTransformationMethod(new PasswordTransformationMethod()); // 使用隐藏密码的转换方法
eyeIcon.setImageResource(hiddenIcon); // 设置闭眼图标
}
// 将光标移到文本末尾,避免光标丢失
editText.setSelection(editText.getText().length());
}
// 检查手机号格式是否正确
private boolean isValidPhone(String phone) {
// 这里可以使用正则表达式来验证手机号格式
return phone.matches("[1][3-9][0-9]{9}");
}
// 显示Toast提示
private void showToast(String message) {
Toast toast = Toast.makeText(PhoneLoginActivity.this, message, Toast.LENGTH_SHORT);
// 设置Toast显示的位置,位置为屏幕顶部(Gravity.TOP)
toast.setGravity(Gravity.TOP, 0, 20); // 200是Y轴偏移量,控制距离顶部的距离
// 显示Toast
toast.show();
}
}
下篇文章开始,介绍app核心功能,记账。