一、引言
在 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 替代 findViewByIdDataBinderMapperImpl.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 机制)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |