Android Room 框架表现层源码深度剖析(三)
一、引言
在 Android 应用开发中,表现层(Presentation Layer)扮演着至关重要的角色,它负责将数据以直观、友好的方式展示给用户,并处理用户的交互操作。Android Room 框架作为一个强大的数据库抽象层,为数据的持久化和访问提供了便利。而表现层与 Room 框架的结合,能够实现数据的实时更新和高效展示。
本文将深入剖析 Android Room 框架在表现层的应用和实现原理,从源码级别详细分析表现层中与 Room 相关的各个组件和流程。通过对源码的解读,我们可以更好地理解如何在表现层中合理运用 Room 框架,以及如何构建高效、美观的用户界面。
二、表现层概述
2.1 表现层的职责
表现层的主要职责是将数据以可视化的方式呈现给用户,并处理用户的交互事件。具体来说,表现层的职责包括:
- 数据展示:将从数据层获取的数据以合适的 UI 组件(如列表、卡片、图表等)展示给用户。
- 用户交互处理:处理用户的点击、滑动、输入等交互事件,并根据用户的操作更新 UI 或触发相应的业务逻辑。
- UI 状态管理:管理 UI 的状态,如加载状态、错误状态、空数据状态等,以提供良好的用户体验。
2.2 表现层与其他层的关系
在典型的 Android 架构中,表现层位于领域层之上,它从领域层获取数据,并将用户的交互反馈传递给领域层。同时,表现层还负责与 Android 系统的 UI 框架进行交互,如使用 Activity、Fragment、View 等组件来构建界面。
2.3 Room 框架在表现层的作用
Room 框架为表现层提供了数据的来源。表现层可以通过 Room 的 DAO(Data Access Object)接口获取数据库中的数据,并将其展示在 UI 上。同时,Room 的 LiveData 支持使得表现层能够实时响应数据的变化,自动更新 UI。
三、表现层中的数据展示
3.1 使用 RecyclerView 展示数据
RecyclerView 是 Android 中常用的用于展示列表数据的组件。结合 Room 框架,我们可以实现数据的实时更新和高效展示。
java
// 定义 RecyclerView 的 Adapter
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
// 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
private List<User> userList;
// 构造函数,接收用户数据列表
public UserAdapter(List<User> userList) {
this.userList = userList;
}
// 创建 ViewHolder
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 加载 Item 的布局文件
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new UserViewHolder(view);
}
// 绑定数据到 ViewHolder
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
// 获取当前位置的用户数据
User user = userList.get(position);
// 将用户数据显示在 TextView 上
holder.textViewName.setText(user.getName());
holder.textViewAge.setText(String.valueOf(user.getAge()));
}
// 获取数据项的数量
@Override
public int getItemCount() {
return userList != null ? userList.size() : 0;
}
// 更新数据列表并刷新 Adapter
public void setUserList(List<User> userList) {
this.userList = userList;
notifyDataSetChanged();
}
// 定义 ViewHolder 类
static class UserViewHolder extends RecyclerView.ViewHolder {
TextView textViewName;
TextView textViewAge;
// 构造函数,初始化 ViewHolder 中的视图组件
UserViewHolder(@NonNull View itemView) {
super(itemView);
textViewName = itemView.findViewById(R.id.textViewName);
textViewAge = itemView.findViewById(R.id.textViewAge);
}
}
}
java
// 在 Activity 中使用 RecyclerView 展示数据
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.List;
// 该 Activity 用于展示用户列表
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter userAdapter;
private UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化 RecyclerView
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
userAdapter = new UserAdapter(null);
recyclerView.setAdapter(userAdapter);
// 获取 ViewModel 实例
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察 LiveData 数据的变化
userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
// 当数据发生变化时,更新 Adapter 中的数据并刷新界面
userAdapter.setUserList(users);
}
});
}
}
3.2 源码分析
从源码的角度来看,UserAdapter
类继承自 RecyclerView.Adapter
,负责将 User
数据绑定到 RecyclerView
的每个 Item
上。UserListActivity
类中,我们使用 ViewModel
获取 LiveData
类型的用户数据,并通过 observe
方法监听数据的变化。当数据发生变化时,Observer
的 onChanged
方法会被调用,我们在该方法中更新 Adapter
中的数据并刷新界面。
3.3 使用 LiveData 实现数据的实时更新
LiveData
是 Android 架构组件中的一个可观察的数据持有者类,它具有生命周期感知能力,能够在 Activity 或 Fragment 的生命周期内自动管理数据的更新。
java
// 定义 UserViewModel
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
// 该 ViewModel 用于管理用户数据
public class UserViewModel extends ViewModel {
private UserRepository userRepository;
private LiveData<List<User>> allUsers;
// 构造函数,初始化 UserRepository 并获取所有用户数据
public UserViewModel() {
userRepository = new UserRepository();
allUsers = userRepository.getAllUsers();
}
// 获取所有用户数据的 LiveData 对象
public LiveData<List<User>> getAllUsers() {
return allUsers;
}
}
java
// 定义 UserRepository
import androidx.lifecycle.LiveData;
import java.util.List;
// 该 Repository 用于与数据层交互,获取用户数据
public class UserRepository {
private UserDao userDao;
// 构造函数,初始化 UserDao
public UserRepository() {
AppDatabase appDatabase = AppDatabase.getDatabase();
userDao = appDatabase.userDao();
}
// 获取所有用户数据的 LiveData 对象
public LiveData<List<User>> getAllUsers() {
return userDao.getAllUsers();
}
}
java
// 定义 UserDao
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
// 该 DAO 接口定义了与用户数据相关的数据库操作方法
@Dao
public interface UserDao {
// 查询所有用户数据,并返回 LiveData 类型的结果
@Query("SELECT * FROM user")
LiveData<List<User>> getAllUsers();
}
3.4 源码分析
在 UserViewModel
中,我们通过 UserRepository
获取 LiveData
类型的用户数据。UserRepository
负责与数据层交互,调用 UserDao
的 getAllUsers
方法。UserDao
是 Room 框架的 DAO 接口,使用 @Query
注解定义了查询所有用户数据的 SQL 语句,并返回 LiveData
类型的结果。当数据库中的数据发生变化时,LiveData
会自动通知所有的观察者,从而实现数据的实时更新。
四、表现层中的用户交互处理
4.1 处理 RecyclerView 中的点击事件
在 RecyclerView
中处理点击事件可以让用户与列表项进行交互。
java
// 在 UserAdapter 中添加点击事件处理
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
// 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上,并处理点击事件
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
private List<User> userList;
private OnItemClickListener onItemClickListener;
// 构造函数,接收用户数据列表和点击事件监听器
public UserAdapter(List<User> userList, OnItemClickListener onItemClickListener) {
this.userList = userList;
this.onItemClickListener = onItemClickListener;
}
// 创建 ViewHolder
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 加载 Item 的布局文件
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new UserViewHolder(view);
}
// 绑定数据到 ViewHolder
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
// 获取当前位置的用户数据
User user = userList.get(position);
// 将用户数据显示在 TextView 上
holder.textViewName.setText(user.getName());
holder.textViewAge.setText(String.valueOf(user.getAge()));
// 设置点击事件监听器
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
// 当点击事件发生时,调用监听器的回调方法
onItemClickListener.onItemClick(user);
}
}
});
}
// 获取数据项的数量
@Override
public int getItemCount() {
return userList != null ? userList.size() : 0;
}
// 更新数据列表并刷新 Adapter
public void setUserList(List<User> userList) {
this.userList = userList;
notifyDataSetChanged();
}
// 定义点击事件监听器接口
public interface OnItemClickListener {
// 当 Item 被点击时调用该方法
void onItemClick(User user);
}
// 定义 ViewHolder 类
static class UserViewHolder extends RecyclerView.ViewHolder {
TextView textViewName;
TextView textViewAge;
// 构造函数,初始化 ViewHolder 中的视图组件
UserViewHolder(@NonNull View itemView) {
super(itemView);
textViewName = itemView.findViewById(R.id.textViewName);
textViewAge = itemView.findViewById(R.id.textViewAge);
}
}
}
java
// 在 UserListActivity 中处理点击事件
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;
// 该 Activity 用于展示用户列表并处理点击事件
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter userAdapter;
private UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化 RecyclerView
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
@Override
public void onItemClick(User user) {
// 当 Item 被点击时,显示一个 Toast 消息
Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
}
});
recyclerView.setAdapter(userAdapter);
// 获取 ViewModel 实例
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察 LiveData 数据的变化
userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
// 当数据发生变化时,更新 Adapter 中的数据并刷新界面
userAdapter.setUserList(users);
}
});
}
}
4.2 源码分析
在 UserAdapter
中,我们定义了一个 OnItemClickListener
接口,并在 onBindViewHolder
方法中为每个 Item
设置点击事件监听器。当 Item
被点击时,会调用监听器的 onItemClick
方法。在 UserListActivity
中,我们实现了 OnItemClickListener
接口,并在 onItemClick
方法中显示一个 Toast
消息,以响应用户的点击操作。
4.3 处理表单输入和提交
在表现层中,我们经常需要处理用户的表单输入,并将输入的数据提交到数据层。
java
// 定义 AddUserActivity
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.lifecycle.ViewModelProvider;
// 该 Activity 用于添加新用户
public class AddUserActivity extends AppCompatActivity {
private EditText editTextName;
private EditText editTextAge;
private Button buttonAdd;
private UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_user);
// 初始化视图组件
editTextName = findViewById(R.id.editTextName);
editTextAge = findViewById(R.id.editTextAge);
buttonAdd = findViewById(R.id.buttonAdd);
// 获取 ViewModel 实例
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 设置按钮的点击事件监听器
buttonAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 获取用户输入的姓名和年龄
String name = editTextName.getText().toString().trim();
String ageStr = editTextAge.getText().toString().trim();
if (!name.isEmpty() && !ageStr.isEmpty()) {
int age = Integer.parseInt(ageStr);
// 创建新的用户对象
User user = new User(name, age);
// 调用 ViewModel 的方法添加用户
userViewModel.insertUser(user);
// 关闭当前 Activity
finish();
}
}
});
}
}
java
// 在 UserViewModel 中添加插入用户的方法
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
// 该 ViewModel 用于管理用户数据
public class UserViewModel extends ViewModel {
private UserRepository userRepository;
private LiveData<List<User>> allUsers;
// 构造函数,初始化 UserRepository 并获取所有用户数据
public UserViewModel() {
userRepository = new UserRepository();
allUsers = userRepository.getAllUsers();
}
// 获取所有用户数据的 LiveData 对象
public LiveData<List<User>> getAllUsers() {
return allUsers;
}
// 插入新用户的方法
public void insertUser(User user) {
userRepository.insertUser(user);
}
}
java
// 在 UserRepository 中添加插入用户的方法
import androidx.lifecycle.LiveData;
import java.util.List;
// 该 Repository 用于与数据层交互,获取用户数据
public class UserRepository {
private UserDao userDao;
// 构造函数,初始化 UserDao
public UserRepository() {
AppDatabase appDatabase = AppDatabase.getDatabase();
userDao = appDatabase.userDao();
}
// 获取所有用户数据的 LiveData 对象
public LiveData<List<User>> getAllUsers() {
return userDao.getAllUsers();
}
// 插入新用户的方法
public void insertUser(User user) {
userDao.insertUser(user);
}
}
java
// 在 UserDao 中添加插入用户的方法
import androidx.room.Dao;
import androidx.room.Insert;
// 该 DAO 接口定义了与用户数据相关的数据库操作方法
@Dao
public interface UserDao {
// 查询所有用户数据,并返回 LiveData 类型的结果
@Query("SELECT * FROM user")
LiveData<List<User>> getAllUsers();
// 插入新用户的方法
@Insert
void insertUser(User user);
}
4.4 源码分析
在 AddUserActivity
中,我们获取用户输入的姓名和年龄,创建新的 User
对象,并调用 UserViewModel
的 insertUser
方法将用户数据插入到数据库中。UserViewModel
调用 UserRepository
的 insertUser
方法,UserRepository
再调用 UserDao
的 insertUser
方法。UserDao
使用 @Insert
注解定义了插入用户数据的方法。
五、表现层中的 UI 状态管理
5.1 加载状态管理
在获取数据时,我们需要显示加载状态,以提高用户体验。
java
// 在 UserListActivity 中添加加载状态管理
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.util.List;
// 该 Activity 用于展示用户列表,添加了加载状态管理
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter userAdapter;
private UserViewModel userViewModel;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化 RecyclerView
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
@Override
public void onItemClick(User user) {
// 当 Item 被点击时,显示一个 Toast 消息
Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
}
});
recyclerView.setAdapter(userAdapter);
// 初始化 ProgressBar
progressBar = findViewById(R.id.progressBar);
// 获取 ViewModel 实例
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 显示加载状态
progressBar.setVisibility(View.VISIBLE);
// 观察 LiveData 数据的变化
userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
// 当数据加载完成后,隐藏加载状态
progressBar.setVisibility(View.GONE);
// 更新 Adapter 中的数据并刷新界面
userAdapter.setUserList(users);
}
});
}
}
5.2 源码分析
在 UserListActivity
中,我们添加了一个 ProgressBar
用于显示加载状态。在获取数据之前,将 ProgressBar
的可见性设置为 VISIBLE
,当数据加载完成后,将其可见性设置为 GONE
。这样可以让用户清楚地知道数据正在加载中。
5.3 错误状态管理
当数据获取失败时,我们需要显示错误状态。
java
// 在 UserViewModel 中添加错误状态管理
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
// 该 ViewModel 用于管理用户数据,添加了错误状态管理
public class UserViewModel extends ViewModel {
private UserRepository userRepository;
private LiveData<List<User>> allUsers;
private MutableLiveData<String> errorMessage;
// 构造函数,初始化 UserRepository 并获取所有用户数据
public UserViewModel() {
userRepository = new UserRepository();
allUsers = userRepository.getAllUsers();
errorMessage = new MutableLiveData<>();
// 模拟数据获取失败的情况
userRepository.getErrorLiveData().observeForever(new Observer<String>() {
@Override
public void onChanged(String error) {
errorMessage.setValue(error);
}
});
}
// 获取所有用户数据的 LiveData 对象
public LiveData<List<User>> getAllUsers() {
return allUsers;
}
// 获取错误消息的 LiveData 对象
public LiveData<String> getErrorMessage() {
return errorMessage;
}
// 插入新用户的方法
public void insertUser(User user) {
userRepository.insertUser(user);
}
}
java
// 在 UserRepository 中添加错误状态管理
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
// 该 Repository 用于与数据层交互,获取用户数据,添加了错误状态管理
public class UserRepository {
private UserDao userDao;
private MutableLiveData<String> errorLiveData;
// 构造函数,初始化 UserDao
public UserRepository() {
AppDatabase appDatabase = AppDatabase.getDatabase();
userDao = appDatabase.userDao();
errorLiveData = new MutableLiveData<>();
// 模拟数据获取失败的情况
try {
// 这里可以添加实际的错误处理逻辑
if (Math.random() < 0.1) {
throw new Exception("Data fetch failed");
}
} catch (Exception e) {
errorLiveData.setValue(e.getMessage());
}
}
// 获取所有用户数据的 LiveData 对象
public LiveData<List<User>> getAllUsers() {
return userDao.getAllUsers();
}
// 获取错误消息的 LiveData 对象
public LiveData<String> getErrorLiveData() {
return errorLiveData;
}
// 插入新用户的方法
public void insertUser(User user) {
userDao.insertUser(user);
}
}
java
// 在 UserListActivity 中处理错误状态
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
// 该 Activity 用于展示用户列表,处理错误状态
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter userAdapter;
private UserViewModel userViewModel;
private ProgressBar progressBar;
private TextView textViewError;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化 RecyclerView
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
@Override
public void onItemClick(User user) {
// 当 Item 被点击时,显示一个 Toast 消息
Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
}
});
recyclerView.setAdapter(userAdapter);
// 初始化 ProgressBar
progressBar = findViewById(R.id.progressBar);
// 初始化错误提示 TextView
textViewError = findViewById(R.id.textViewError);
// 获取 ViewModel 实例
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 显示加载状态
progressBar.setVisibility(View.VISIBLE);
// 观察 LiveData 数据的变化
userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
// 当数据加载完成后,隐藏加载状态
progressBar.setVisibility(View.GONE);
if (users != null) {
// 隐藏错误提示
textViewError.setVisibility(View.GONE);
// 更新 Adapter 中的数据并刷新界面
userAdapter.setUserList(users);
}
}
});
// 观察错误消息的变化
userViewModel.getErrorMessage().observe(this, new Observer<String>() {
@Override
public void onChanged(String error) {
// 隐藏加载状态
progressBar.setVisibility(View.GONE);
if (error != null) {
// 显示错误提示
textViewError.setVisibility(View.VISIBLE);
textViewError.setText(error);
}
}
});
}
}
5.4 源码分析
在 UserViewModel
和 UserRepository
中,我们添加了 MutableLiveData
类型的 errorMessage
用于存储错误消息。在 UserListActivity
中,我们观察 errorMessage
的变化,当有错误消息时,隐藏加载状态并显示错误提示。
六、表现层中的动画和过渡效果
6.1 RecyclerView 中的动画效果
在 RecyclerView
中添加动画效果可以提升用户体验,让数据的更新更加生动。Android 为 RecyclerView
提供了默认的动画效果,同时也允许开发者自定义动画。
6.1.1 使用默认动画
RecyclerView
默认使用 DefaultItemAnimator
来实现动画效果,当数据发生变化时(如插入、删除、更新),会自动播放相应的动画。
java
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;
// 该 Activity 用于展示用户列表,并使用 RecyclerView 默认动画
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter userAdapter;
private UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化 RecyclerView
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 设置默认的 Item 动画
recyclerView.setItemAnimator(new DefaultItemAnimator());
userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
@Override
public void onItemClick(User user) {
// 当 Item 被点击时,显示一个 Toast 消息
Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
}
});
recyclerView.setAdapter(userAdapter);
// 获取 ViewModel 实例
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察 LiveData 数据的变化
userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
// 当数据发生变化时,更新 Adapter 中的数据并刷新界面
userAdapter.setUserList(users);
}
});
}
}
6.1.2 源码分析
在上述代码中,通过 recyclerView.setItemAnimator(new DefaultItemAnimator())
设置了 RecyclerView
的默认动画。DefaultItemAnimator
是 RecyclerView.ItemAnimator
的一个实现类,它内部处理了 RecyclerView
中 Item
的插入、删除、移动和更新动画。当调用 userAdapter.setUserList(users)
并触发 notifyDataSetChanged()
时,DefaultItemAnimator
会根据数据的变化情况播放相应的动画。
6.1.3 自定义动画
如果默认动画不能满足需求,开发者可以自定义动画。以下是一个自定义 ItemAnimator
的示例:
java
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
// 自定义的 RecyclerView Item 动画类
public class CustomItemAnimator extends RecyclerView.ItemAnimator {
private List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
private List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
pendingAdditions.add(holder);
return true;
}
@Override
public boolean animateRemove(RecyclerView.ViewHolder holder) {
pendingRemovals.add(holder);
return true;
}
@Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
// 这里可以实现移动动画逻辑,暂不实现
dispatchMoveFinished(holder);
return false;
}
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
// 这里可以实现变更动画逻辑,暂不实现
dispatchChangeFinished(oldHolder, true);
dispatchChangeFinished(newHolder, false);
return false;
}
@Override
public void runPendingAnimations() {
if (!pendingAdditions.isEmpty()) {
for (RecyclerView.ViewHolder holder : pendingAdditions) {
// 为新增的 Item 播放动画
animateAddImpl(holder);
}
pendingAdditions.clear();
}
if (!pendingRemovals.isEmpty()) {
for (RecyclerView.ViewHolder holder : pendingRemovals) {
// 为删除的 Item 播放动画
animateRemoveImpl(holder);
}
pendingRemovals.clear();
}
}
private void animateAddImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
// 使用属性动画实现淡入效果
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
animator.setDuration(300);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationEnd(Animator animation) {
dispatchAddFinished(holder);
}
@Override
public void onAnimationCancel(Animator animation) {
dispatchAddFinished(holder);
}
@Override
public void onAnimationRepeat(Animator animation) {
// 不处理重复动画
}
});
animator.start();
}
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
// 使用属性动画实现淡出效果
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
animator.setDuration(300);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(Animator animation) {
dispatchRemoveFinished(holder);
}
@Override
public void onAnimationCancel(Animator animation) {
dispatchRemoveFinished(holder);
}
@Override
public void onAnimationRepeat(Animator animation) {
// 不处理重复动画
}
});
animator.start();
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
// 结束动画的逻辑
}
@Override
public void endAnimations() {
// 结束所有动画的逻辑
}
@Override
public boolean isRunning() {
return !pendingAdditions.isEmpty() || !pendingRemovals.isEmpty();
}
}
java
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;
// 该 Activity 用于展示用户列表,并使用自定义的 RecyclerView 动画
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter userAdapter;
private UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化 RecyclerView
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 设置自定义的 Item 动画
recyclerView.setItemAnimator(new CustomItemAnimator());
userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
@Override
public void onItemClick(User user) {
// 当 Item 被点击时,显示一个 Toast 消息
Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
}
});
recyclerView.setAdapter(userAdapter);
// 获取 ViewModel 实例
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察 LiveData 数据的变化
userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
// 当数据发生变化时,更新 Adapter 中的数据并刷新界面
userAdapter.setUserList(users);
}
});
}
}
6.1.4 源码分析
自定义 CustomItemAnimator
继承自 RecyclerView.ItemAnimator
,并重写了 animateAdd
、animateRemove
、animateMove
、animateChange
等方法来处理不同类型的动画。在 runPendingAnimations
方法中,会根据 pendingAdditions
和 pendingRemovals
列表中的 ViewHolder
执行相应的动画。animateAddImpl
和 animateRemoveImpl
方法分别使用 ObjectAnimator
实现了淡入和淡出的动画效果,并在动画开始和结束时调用相应的 dispatch
方法通知 RecyclerView
。
6.2 Activity 过渡动画
Activity 过渡动画可以让 Activity 之间的切换更加流畅和美观。Android 提供了多种过渡动画效果,如淡入淡出、滑动、缩放等。
6.2.1 使用系统默认过渡动画
在 Android 中,可以通过设置 Activity 的主题来使用系统默认的过渡动画。
xml
<!-- styles.xml -->
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- 设置 Activity 过渡动画 -->
<item name="android:windowActivityTransitions">true</item>
<item name="android:windowEnterTransition">@android:transition/fade</item>
<item name="android:windowExitTransition">@android:transition/fade</item>
</style>
</resources>
java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
// 该 Activity 用于演示 Activity 过渡动画
public class MainActivity extends AppCompatActivity {
private Button buttonOpenActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化按钮
buttonOpenActivity = findViewById(R.id.buttonOpenActivity);
buttonOpenActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 启动新的 Activity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
}
java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
// 第二个 Activity
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
6.2.2 源码分析
在 styles.xml
中,通过设置 android:windowActivityTransitions
为 true
开启 Activity 过渡动画,android:windowEnterTransition
和 android:windowExitTransition
分别设置了 Activity 进入和退出时的过渡动画为淡入淡出效果。当在 MainActivity
中启动 SecondActivity
时,系统会自动应用这些过渡动画。
6.2.3 自定义过渡动画
除了使用系统默认的过渡动画,还可以自定义过渡动画。
xml
<!-- res/transition/slide_transition.xml -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<slide
android:duration="300"
android:slideEdge="right" />
</transitionSet>
java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.transition.TransitionInflater;
import android.view.View;
import android.widget.Button;
// 该 Activity 用于演示自定义 Activity 过渡动画
public class MainActivity extends AppCompatActivity {
private Button buttonOpenActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化按钮
buttonOpenActivity = findViewById(R.id.buttonOpenActivity);
buttonOpenActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 启动新的 Activity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
// 设置进入过渡动画
getWindow().setEnterTransition(TransitionInflater.from(MainActivity.this).inflateTransition(R.transition.slide_transition));
// 设置退出过渡动画
getWindow().setExitTransition(TransitionInflater.from(MainActivity.this).inflateTransition(R.transition.slide_transition));
startActivity(intent);
}
});
}
}
6.2.4 源码分析
在 res/transition/slide_transition.xml
中定义了一个滑动过渡动画,android:slideEdge="right"
表示从右侧滑入。在 MainActivity
中,通过 getWindow().setEnterTransition
和 getWindow().setExitTransition
方法设置了进入和退出时的过渡动画。当启动 SecondActivity
时,会应用自定义的滑动过渡动画。
七、表现层中的响应式设计
7.1 布局的响应式设计
在不同的屏幕尺寸和方向下,应用的布局需要能够自适应,以提供一致的用户体验。
7.1.1 使用 ConstraintLayout
ConstraintLayout
是 Android 中一个强大的布局管理器,它可以根据约束条件来布局子视图,非常适合实现响应式设计。
xml
<!-- activity_main.xml -->
<androidx.constraintlayout.widget.ConstraintLayout 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">
<TextView
android:id="@+id/textViewTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp" />
<Button
android:id="@+id/buttonAction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Action"
app:layout_constraintTop_toBottomOf="@id/textViewTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
7.1.2 源码分析
在上述布局文件中,使用 ConstraintLayout
来布局 TextView
和 Button
。通过 app:layout_constraintTop_toTopOf
、app:layout_constraintStart_toStartOf
等约束条件,将 TextView
固定在布局的顶部中央,Button
固定在 TextView
的下方中央。这样,无论屏幕尺寸和方向如何变化,布局都会自适应调整。
7.1.3 使用不同的布局文件
除了使用 ConstraintLayout
,还可以根据不同的屏幕尺寸和方向提供不同的布局文件。
plaintext
res/
├── layout/
│ └── activity_main.xml
├── layout-sw600dp/
│ └── activity_main.xml
├── layout-land/
│ └── activity_main.xml
在 layout-sw600dp
目录下的 activity_main.xml
是针对屏幕最小宽度为 600dp 的设备的布局文件,layout-land
目录下的 activity_main.xml
是针对横屏的布局文件。系统会根据设备的屏幕尺寸和方向自动选择合适的布局文件。
7.2 数据的响应式设计
在表现层中,数据的展示也需要根据不同的情况进行响应式设计。例如,在不同的屏幕尺寸下,可能需要展示不同数量的数据项。
java
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.res.Configuration;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;
// 该 Activity 用于展示用户列表,并实现数据的响应式设计
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter userAdapter;
private UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化 RecyclerView
recyclerView = findViewById(R.id.recyclerView);
// 根据屏幕方向设置不同的列数
int spanCount = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? 2 : 3;
recyclerView.setLayoutManager(new GridLayoutManager(this, spanCount));
userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
@Override
public void onItemClick(User user) {
// 当 Item 被点击时,显示一个 Toast 消息
Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
}
});
recyclerView.setAdapter(userAdapter);
// 获取 ViewModel 实例
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察 LiveData 数据的变化
userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
// 当数据发生变化时,更新 Adapter 中的数据并刷新界面
userAdapter.setUserList(users);
}
});
}
}
7.2.1 源码分析
在 UserListActivity
中,根据屏幕的方向(竖屏或横屏)设置 GridLayoutManager
的列数。在竖屏时,列数为 2;在横屏时,列数为 3。这样,在不同的屏幕方向下,RecyclerView
会以不同的布局方式展示数据。
八、表现层中的性能优化
8.1 减少布局嵌套
布局嵌套过多会导致布局的测量和绘制时间增加,影响性能。可以使用 ConstraintLayout
等布局管理器来减少布局嵌套。
xml
<!-- 优化前的布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Action" />
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
xml
<!-- 优化后的布局 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textViewTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp" />
<Button
android:id="@+id/buttonAction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Action"
app:layout_constraintTop_toTopOf="@id/textViewTitle"
app:layout_constraintStart_toEndOf="@id/textViewTitle"
android:layout_marginStart="16dp" />
<ListView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/textViewTitle"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
8.1.1 源码分析
优化前的布局使用了两层 LinearLayout
嵌套,而优化后的布局使用 ConstraintLayout
直接布局子视图,减少了布局嵌套。这样可以提高布局的测量和绘制效率。
8.2 避免在主线程进行耗时操作
在表现层中,应该避免在主线程进行耗时操作,如网络请求、数据库查询等。可以使用 ViewModel
和 LiveData
结合 Coroutine
或 RxJava
来实现异步操作。
java
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.Dispatchers;
import kotlinx.coroutines.Job;
import kotlinx.coroutines.launch;
// 该 ViewModel 用于管理用户数据,并使用 Coroutine 进行异步操作
public class UserViewModel extends ViewModel {
private UserRepository userRepository;
private MutableLiveData<List<User>> allUsers;
private Job job;
// 构造函数,初始化 UserRepository 并获取所有用户数据
public UserViewModel() {
userRepository = new UserRepository();
allUsers = new MutableLiveData<>();
loadUsers();
}
// 获取所有用户数据的 LiveData 对象
public LiveData<List<User>> getAllUsers() {
return allUsers;
}
// 加载用户数据的方法
private void loadUsers() {
job = CoroutineScope(Dispatchers.IO).launch {
// 在 IO 线程中进行数据库查询
List<User> users = userRepository.getAllUsersSync();
// 将结果切换到主线程更新 LiveData
CoroutineScope(Dispatchers.Main).launch {
allUsers.setValue(users);
}
};
}
@Override
protected void onCleared() {
super.onCleared();
// 取消协程任务
if (job != null && job.isActive()) {
job.cancel();
}
}
}
8.2.2 源码分析
在 UserViewModel
中,使用 Coroutine
进行异步操作。loadUsers
方法在 IO
线程中进行数据库查询,查询完成后,将结果切换到主线程更新 LiveData
。这样可以避免在主线程进行耗时的数据库查询,保证 UI 的流畅性。同时,在 ViewModel
销毁时,取消协程任务,避免内存泄漏。
8.3 使用 RecyclerView 的视图缓存
RecyclerView
提供了视图缓存机制,可以复用已经创建的视图,减少视图的创建和销毁次数,提高性能。
java
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
// 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
private List<User> userList;
// 构造函数,接收用户数据列表
public UserAdapter(List<User> userList) {
this.userList = userList;
}
// 创建 ViewHolder
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 加载 Item 的布局文件
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new UserViewHolder(view);
}
// 绑定数据到 ViewHolder
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
// 获取当前位置的用户数据
User user = userList.get(position);
// 将用户数据显示在 TextView 上
holder.textViewName.setText(user.getName());
holder.textViewAge.setText(String.valueOf(user.getAge()));
}
// 获取数据项的数量
@Override
public int getItemCount() {
return userList != null ? userList.size() : 0;
}
// 更新数据列表并刷新 Adapter
public void setUserList(List<User> userList) {
this.userList = userList;
notifyDataSetChanged();
}
// 定义 ViewHolder 类
static class UserViewHolder extends RecyclerView.ViewHolder {
TextView textViewName;
TextView textViewAge;
// 构造函数,初始化 ViewHolder 中的视图组件
UserViewHolder(@NonNull View itemView) {
super(itemView);
textViewName = itemView.findViewById(R.id.textViewName);
textViewAge = itemView.findViewById(R.id.textViewAge);
}
}
}
8.3.3 源码分析
在 UserAdapter
中,RecyclerView
会自动管理视图的缓存。当 RecyclerView
滚动时,会复用已经创建的 ViewHolder
,只需要调用 onBindViewHolder
方法更新视图的数据。这样可以避免频繁创建和销毁视图,提高性能。
九、表现层中的无障碍设计
9.1 为视图添加内容描述
java
// 在 RecyclerView Adapter 中设置内容描述(关键源码)
class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_user, parent, false);
// 为整个 Item 设置内容描述(辅助功能)
itemView.setContentDescription("用户项:姓名 ${user.name},年龄 ${user.age}岁");
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
User user = userList.get(position);
// 为姓名 TextView 添加无障碍标签
holder.nameView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setText("用户名:" + user.name);
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
}
});
}
}
// Android 框架中 ContentDescription 的处理逻辑(View.java)
public void setContentDescription(CharSequence contentDescription) {
mContentDescription = contentDescription;
// 触发无障碍节点更新
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CONTENT_DESCRIPTION_CHANGED);
}
// RecyclerView 辅助功能更新机制(RecyclerView.java)
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
// 自动聚合子项的无障碍信息
info.setCollectionInfo(CollectionInfo.obtain(
getAdapter().getItemCount(),
getChildCount(),
isLayoutRtl()
));
}
9.2 动态字体适配(源码实现)
xml
<!-- 布局文件中启用自动字体大小 -->
<TextView
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autofillHints="@string/hint_name"
android:textSize="@dimen/font_size_normal"
android:fontVariationSettings="wdth 100, wght 400"
tools:text="张三" />
java
// 系统字体适配核心类(Configuration.java)
public class Configuration {
public float fontScale; // 字体缩放比例(用户设置)
// 框架内部处理逻辑(ActivityThread.java)
private void handleConfigurationChanged(Configuration config) {
ViewRootImpl[] roots = mRoots.getArray();
for (ViewRootImpl root : roots) {
root.setLayoutParams(null, config, null);
}
// 触发全局字体更新
applyOverrideConfiguration(config);
}
}
// 在 Activity 中监听字体变化
public class UserActivity extends AppCompatActivity {
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 重新绑定数据触发字体更新
userViewModel.getAllUsers().observe(this, users -> adapter.setUsers(users));
}
}
十、Jetpack Compose 与 Room 的深度集成
10.1 基于 Compose 的数据绑定(核心源码)
kotlin
// Compose 界面组件
@Composable
fun UserListScreen(viewModel: UserViewModel = viewModel()) {
val users by viewModel.users.collectAsState(emptyList())
val context = LocalContext.current
RecyclerView(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(users) { user ->
UserItem(
user = user,
onClick = { viewModel.onUserClick(user) }
)
}
}
// 加载状态处理
if (viewModel.isLoading.value) {
CircularProgressIndicator(modifier = Modifier.centerInParent())
}
}
// ViewModel 中的 State 管理
class UserViewModel : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users = _users.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading = _isLoading.asStateFlow()
init {
loadUsers()
}
private fun loadUsers() {
viewModelScope.launch {
_isLoading.value = true
try {
val users = userRepository.getAllUsers()
_users.value = users
} finally {
_isLoading.value = false
}
}
}
}
// Room 协程支持(Compose 专用扩展)
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsersFlow(): Flow<List<User>> // 直接返回 Flow
}
10.2 Compose 与 LiveData 的互操作
kotlin
// LiveData 转 Compose State
@Composable
fun <T> LiveData<T>.asComposeState(
context: Context = LocalContext.current,
initialValue: T
): State<T> {
val state = remember { mutableStateOf(initialValue) }
val lifecycleOwner = rememberUpdatedState(context as LifecycleOwner)
DisposableEffect(this) {
val observer = Observer<T> { state.value = it }
observe(lifecycleOwner.value, observer)
onDispose { removeObserver(observer) }
}
return state
}
// 使用示例
val users = userViewModel.allUsers.asComposeState(emptyList())
十一、表现层性能优化深度解析
11.1 RecyclerView 预布局优化(源码级)
java
// 自定义 RecyclerView(优化预布局)
class OptimizedRecyclerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : RecyclerView(context, attrs, defStyle) {
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
// 禁用预布局(针对固定高度列表)
setHasFixedSize(true);
super.onMeasure(widthSpec, heightSpec);
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
// 跳过不必要的布局计算
if (!changed) return;
super.onLayout(changed, l, t, r, b);
}
}
// 框架预布局逻辑(RecyclerView.java)
void processLayout(Recycler recycler, State state) {
if (mState.mRunPredictiveAnimations) {
// 预布局用于动画计算
performPredictiveLayout(recycler, state);
}
// 正式布局
performLayout(recycler, state);
}
11.2 数据变更的细粒度更新(DiffUtil 源码)
java
// Adapter 中的 DiffUtil 实现
class UserAdapter : ListAdapter<User, UserAdapter.ViewHolder>(USER_DIFF_CALLBACK) {
companion object {
val USER_DIFF_CALLBACK = object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id // 基于唯一标识判断
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem // 基于内容比较
}
}
}
// 框架内部 Diff 计算(DiffUtil.java)
public static DiffResult calculateDiff(Callback callback) {
return new DiffUtil(callback).calculate();
}
// 差异分发(RecyclerView.java)
public void dispatchUpdateRanges(List<UpdateOp> ops) {
mAdapterHelper.calculateDiff(ops);
// 触发局部刷新
for (UpdateOp op : ops) {
dispatchSingleUpdate(op);
}
}
}
十二、表现层与 Room 的生命周期协同
12.1 ViewModel 与 Room 的绑定
java
// ViewModel 中的 Room 初始化(关键源码)
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val database by lazy {
AppDatabase.getInstance(application) // 生命周期感知的数据库实例
}
val users: LiveData<List<User>> = database.userDao().getAllUsers()
override fun onCleared() {
super.onCleared()
// 释放数据库资源(可选)
database.close()
}
}
// 数据库单例实现(AppDatabase.java)
public class AppDatabase {
private static volatile AppDatabase INSTANCE;
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
AppDatabase.class, "user.db"
).addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
// 初始化数据(可选)
}
}).build();
}
}
}
return INSTANCE;
}
}
12.2 LiveData 的生命周期安全(源码解析)
java
// LiveData observe 方法(LiveData.java)
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
// 检查生命周期状态
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer");
}
owner.getLifecycle().addObserver(wrapper);
}
// 生命周期事件处理(LifecycleBoundObserver.java)
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (source.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
十三、表现层单元测试(源码级验证)
13.1 纯 UI 测试(不带 Room)
java
@RunWith(AndroidJUnit4::class)
public class UserAdapterTest {
private UserAdapter adapter;
@Before
public void setup() {
adapter = new UserAdapter(Collections.emptyList());
}
@Test
public void testViewHolderBinding() {
// 创建测试 View
View itemView = LayoutInflater.from(ApplicationProvider.getApplicationContext())
.inflate(R.layout.item_user, null);
UserAdapter.ViewHolder holder = new UserAdapter.ViewHolder(itemView);
// 绑定数据
User user = new User(1, "张三", 25);
adapter.onBindViewHolder(holder, 0);
// 验证 UI 显示
assertEquals("张三", holder.nameView.getText());
assertEquals("25", holder.ageView.getText());
}
@Test
public void testDiffUtil() {
User oldUser = new User(1, "张三", 25);
User newUser = new User(1, "张三", 26);
// 测试内容变更
assertEquals(false, USER_DIFF_CALLBACK.areContentsTheSame(oldUser, newUser));
}
}
13.2 集成测试(结合 Room 测试库)
java
@RunWith(AndroidJUnit4::class)
public class UserActivityTest {
private final UserDao dao = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase.class
).allowMainThreadQueries().build().userDao();
@Test
public void testUserListUpdate() {
// 插入测试数据
dao.insert(new User(1, "李四", 30));
// 启动 Activity
ActivityScenario.launch(UserActivity.class);
// 验证列表显示
onView(withText("李四")).check(matches(isDisplayed()));
}
@Test
public void testAddUserFlow() {
// 启动添加用户 Activity
ActivityScenario.launch(AddUserActivity.class);
// 模拟输入
onView(withId(R.id.edit_name)).perform(typeText("王五"));
onView(withId(R.id.edit_age)).perform(typeText("28"));
onView(withId(R.id.btn_add)).perform(click());
// 验证列表更新
onView(withText("王五")).check(matches(isDisplayed()));
}
}
十四、表现层设计模式实战
14.1 状态模式(UI 状态管理)
java
// UI 状态枚举
enum UiState {
LOADING,
CONTENT,
ERROR
}
// Activity 中的状态管理
public class UserActivity extends AppCompatActivity {
private UiState currentState = UiState.LOADING;
private void updateState(UiState newState) {
currentState = newState;
runOnUiThread(() -> {
switch (newState) {
case LOADING:
showLoading();
hideContent();
hideError();
break;
case CONTENT:
hideLoading();
showContent();
hideError();
break;
case ERROR:
hideLoading();
hideContent();
showError();
break;
}
});
}
// 框架内部的状态更新(ActivityThread.java)
public void handleResumeActivity(IBinder token, boolean finalStateRequest) {
// 恢复 Activity 状态
performResumeActivity(token, finalStateRequest);
// 触发 UI 状态更新
mMainThreadHandler.obtainMessage(H.RESUME_ACTIVITY, token).sendToTarget();
}
}
14.2 策略模式(UI 样式切换)
java
// 主题策略接口
interface ThemeStrategy {
int getBackgroundColor();
int getTextColor();
}
// 浅色主题实现
class LightThemeStrategy implements ThemeStrategy {
@Override
public int getBackgroundColor() {
return Color.WHITE;
}
@Override
public int getTextColor() {
return Color.BLACK;
}
}
// Activity 中的策略应用
public class UserActivity extends AppCompatActivity {
private ThemeStrategy themeStrategy = new LightThemeStrategy();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
// 应用主题策略
userList.setBackgroundColor(themeStrategy.getBackgroundColor());
titleView.setTextColor(themeStrategy.getTextColor());
}
// 动态切换主题
public void switchToDarkTheme() {
themeStrategy = new DarkThemeStrategy();
recreate();
}
}
十五、表现层异常处理最佳实践
15.1 全局异常捕获(源码实现)
java
// 全局异常处理器
public class AppExceptionHandler implements Thread.UncaughtExceptionHandler {
private final Thread.UncaughtExceptionHandler defaultHandler;
private final Context context;
public AppExceptionHandler(Context context) {
this.context = context;
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (ex instanceof SQLiteException) {
// 处理 Room 数据库异常
showDatabaseError(ex);
return;
}
defaultHandler.uncaughtException(thread, ex);
}
private void showDatabaseError(Throwable ex) {
new AlertDialog.Builder(context)
.setTitle("数据库错误")
.setMessage("数据加载失败:" + ex.getMessage())
.setPositiveButton("重试", (dialog, which) -> recreate())
.show();
}
}
// 在 Application 中注册
public class AppApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Thread.setDefaultUncaughtExceptionHandler(new AppExceptionHandler(this));
}
}
15.2 Room 异常转换(表现层适配)
java
// 在 Repository 中封装异常
class UserRepository {
public LiveData<List<User>> getUsers() {
return Transformations.map(dao.getAllUsers(), users -> {
try {
return users;
} catch (SQLiteException e) {
// 转换为表现层可识别的异常
throw new UiDataException("数据库查询失败", e);
}
});
}
}
// Activity 中的异常处理
userViewModel.users.observe(this, users -> {
if (users instanceof UiDataException) {
showError((UiDataException) users);
return;
}
updateUI(users);
});
十六、总结:表现层的 Room 集成哲学
16.1 源码架构总览
plaintext
表现层
├─ Activity/Fragment (UI 宿主)
│ ├─ ViewModel (数据与逻辑)
│ └─ LiveData/State (数据订阅)
├─ RecyclerView/Compose (数据展示)
│ ├─ Adapter (数据绑定)
│ └─ DiffUtil (增量更新)
├─ DataBinding (视图绑定)
└─ 无障碍/动画/性能 (体验优化)
Room 依赖
├─ DAO (数据访问接口)
├─ LiveData/Flow (数据订阅源)
└─ TypeConverter (数据转换)
16.2 核心设计原则
- 单向数据流:Room → ViewModel → UI,确保状态可追溯(参考
LiveData
源码) - 生命周期感知:通过
LifecycleOwner
管理 Room 查询(ViewModel
内部实现) - 增量更新:利用
DiffUtil
和RecyclerView
局部刷新(减少绘制操作) - 异步抽象:通过协程 / LiveData 隐藏 Room 线程细节(
SuspendSupport
源码) - 防御性编程:在 UI 层处理 Room 异常(
SQLiteException
转换)
16.3 性能优化清单
优化点 | 实现方式 | 源码位置 |
---|---|---|
列表更新 | DiffUtil + RecyclerView 局部刷新 | ListAdapter.java |
数据订阅 | LiveData 自动生命周期绑定 | LifecycleBoundObserver.java |
布局性能 | DataBinding 替代 findViewById | DataBinderMapperImpl.java |
内存管理 | ViewModel 绑定 Room 单例 | ViewModelStore.java |
动画优化 | 默认 ItemAnimator + 自定义过渡 | DefaultItemAnimator.java |
16.4 反模式规避
- ❌ 在 Activity 直接操作 Room DAO(紧耦合)
✅ 通过 ViewModel 间接访问(参考UserViewModel
源码) - ❌ 忽略 DiffUtil 导致全量刷新(性能损耗)
✅ 使用ListAdapter
强制差分更新(源码强制实现getItemId
) - ❌ 在 UI 线程执行 Room 查询(ANR 风险)
✅ 通过 LiveData / 协程自动切换线程(RoomDatabase
内部检查) - ❌ 复杂布局嵌套(过度绘制)
✅ 使用 ConstraintLayout + 扁平化布局(减少层级) - ❌ 内存泄漏(未取消订阅)
✅ LiveData 自动解绑(LifecycleOwner
机制)