一、引言
在 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@Overridepublic 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@Overridepublic 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()));}// 获取数据项的数量@Overridepublic int getItemCount() {return userList != null ? userList.size() : 0;}// 更新数据列表并刷新 Adapterpublic 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user_list);// 初始化 RecyclerViewrecyclerView = 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>>() {@Overridepublic 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;// 构造函数,初始化 UserDaopublic 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@Overridepublic 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@Overridepublic 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() {@Overridepublic void onClick(View v) {if (onItemClickListener != null) {// 当点击事件发生时,调用监听器的回调方法onItemClickListener.onItemClick(user);}}});}// 获取数据项的数量@Overridepublic int getItemCount() {return userList != null ? userList.size() : 0;}// 更新数据列表并刷新 Adapterpublic 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user_list);// 初始化 RecyclerViewrecyclerView = findViewById(R.id.recyclerView);recyclerView.setLayoutManager(new LinearLayoutManager(this));userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {@Overridepublic 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>>() {@Overridepublic 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;@Overrideprotected 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() {@Overridepublic 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);// 关闭当前 Activityfinish();}}});}
}
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;// 构造函数,初始化 UserDaopublic 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();// 插入新用户的方法@Insertvoid 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user_list);// 初始化 RecyclerViewrecyclerView = findViewById(R.id.recyclerView);recyclerView.setLayoutManager(new LinearLayoutManager(this));userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {@Overridepublic void onItemClick(User user) {// 当 Item 被点击时,显示一个 Toast 消息Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();}});recyclerView.setAdapter(userAdapter);// 初始化 ProgressBarprogressBar = 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>>() {@Overridepublic 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>() {@Overridepublic 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;// 构造函数,初始化 UserDaopublic 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user_list);// 初始化 RecyclerViewrecyclerView = findViewById(R.id.recyclerView);recyclerView.setLayoutManager(new LinearLayoutManager(this));userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {@Overridepublic void onItemClick(User user) {// 当 Item 被点击时,显示一个 Toast 消息Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();}});recyclerView.setAdapter(userAdapter);// 初始化 ProgressBarprogressBar = findViewById(R.id.progressBar);// 初始化错误提示 TextViewtextViewError = 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>>() {@Overridepublic 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>() {@Overridepublic 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user_list);// 初始化 RecyclerViewrecyclerView = findViewById(R.id.recyclerView);recyclerView.setLayoutManager(new LinearLayoutManager(this));// 设置默认的 Item 动画recyclerView.setItemAnimator(new DefaultItemAnimator());userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {@Overridepublic 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>>() {@Overridepublic 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<>();@Overridepublic boolean animateAdd(RecyclerView.ViewHolder holder) {pendingAdditions.add(holder);return true;}@Overridepublic boolean animateRemove(RecyclerView.ViewHolder holder) {pendingRemovals.add(holder);return true;}@Overridepublic boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {// 这里可以实现移动动画逻辑,暂不实现dispatchMoveFinished(holder);return false;}@Overridepublic boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {// 这里可以实现变更动画逻辑,暂不实现dispatchChangeFinished(oldHolder, true);dispatchChangeFinished(newHolder, false);return false;}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {dispatchAddStarting(holder);}@Overridepublic void onAnimationEnd(Animator animation) {dispatchAddFinished(holder);}@Overridepublic void onAnimationCancel(Animator animation) {dispatchAddFinished(holder);}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {dispatchRemoveStarting(holder);}@Overridepublic void onAnimationEnd(Animator animation) {dispatchRemoveFinished(holder);}@Overridepublic void onAnimationCancel(Animator animation) {dispatchRemoveFinished(holder);}@Overridepublic void onAnimationRepeat(Animator animation) {// 不处理重复动画}});animator.start();}@Overridepublic void endAnimation(RecyclerView.ViewHolder item) {// 结束动画的逻辑}@Overridepublic void endAnimations() {// 结束所有动画的逻辑}@Overridepublic 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user_list);// 初始化 RecyclerViewrecyclerView = findViewById(R.id.recyclerView);recyclerView.setLayoutManager(new LinearLayoutManager(this));// 设置自定义的 Item 动画recyclerView.setItemAnimator(new CustomItemAnimator());userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {@Overridepublic 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>>() {@Overridepublic 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化按钮buttonOpenActivity = findViewById(R.id.buttonOpenActivity);buttonOpenActivity.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 启动新的 ActivityIntent 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 {@Overrideprotected 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"><slideandroid: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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化按钮buttonOpenActivity = findViewById(R.id.buttonOpenActivity);buttonOpenActivity.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 启动新的 ActivityIntent 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"><TextViewandroid: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" /><Buttonandroid: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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user_list);// 初始化 RecyclerViewrecyclerView = 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() {@Overridepublic 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>>() {@Overridepublic void onChanged(List<User> users) {// 当数据发生变化时,更新 Adapter 中的数据并刷新界面userAdapter.setUserList(users);}});}
}
7.2.1 源码分析
在 UserListActivity
中,根据屏幕的方向(竖屏或横屏)设置 GridLayoutManager
的列数。在竖屏时,列数为 2;在横屏时,列数为 3。这样,在不同的屏幕方向下,RecyclerView
会以不同的布局方式展示数据。
八、表现层中的性能优化
8.1 减少布局嵌套
布局嵌套过多会导致布局的测量和绘制时间增加,影响性能。可以使用 ConstraintLayout
等布局管理器来减少布局嵌套。
xml
<!-- 优化前的布局 -->
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Title" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Action" /></LinearLayout><ListViewandroid:layout_width="match_parent"android:layout_height="match_parent" />
</LinearLayout>
xml
<!-- 优化后的布局 -->
<androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid: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" /><Buttonandroid: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" /><ListViewandroid: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();// 将结果切换到主线程更新 LiveDataCoroutineScope(Dispatchers.Main).launch {allUsers.setValue(users);}};}@Overrideprotected 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@Overridepublic 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@Overridepublic 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()));}// 获取数据项的数量@Overridepublic int getItemCount() {return userList != null ? userList.size() : 0;}// 更新数据列表并刷新 Adapterpublic 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@Overridepublic 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);}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {User user = userList.get(position);// 为姓名 TextView 添加无障碍标签holder.nameView.setAccessibilityDelegate(new View.AccessibilityDelegate() {@Overridepublic 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
<!-- 布局文件中启用自动字体大小 -->
<TextViewandroid: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 {@Overridepublic 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.currentRecyclerView(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 = truetry {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() {@Overridepublic 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;@Beforepublic void setup() {adapter = new UserAdapter(Collections.emptyList());}@Testpublic void testViewHolderBinding() {// 创建测试 ViewView 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());}@Testpublic 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();@Testpublic void testUserListUpdate() {// 插入测试数据dao.insert(new User(1, "李四", 30));// 启动 ActivityActivityScenario.launch(UserActivity.class);// 验证列表显示onView(withText("李四")).check(matches(isDisplayed()));}@Testpublic void testAddUserFlow() {// 启动添加用户 ActivityActivityScenario.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 {@Overridepublic int getBackgroundColor() {return Color.WHITE;}@Overridepublic int getTextColor() {return Color.BLACK;}
}// Activity 中的策略应用
public class UserActivity extends AppCompatActivity {private ThemeStrategy themeStrategy = new LightThemeStrategy();@Overrideprotected 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();}@Overridepublic 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 {@Overridepublic 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
机制)