Android Room 框架表现层源码深度剖析(三)

发布于:2025-03-17 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、引言

在 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 方法监听数据的变化。当数据发生变化时,ObserveronChanged 方法会被调用,我们在该方法中更新 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 负责与数据层交互,调用 UserDaogetAllUsers 方法。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 对象,并调用 UserViewModelinsertUser 方法将用户数据插入到数据库中。UserViewModel 调用 UserRepositoryinsertUser 方法,UserRepository 再调用 UserDaoinsertUser 方法。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 源码分析

UserViewModelUserRepository 中,我们添加了 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 的默认动画。DefaultItemAnimatorRecyclerView.ItemAnimator 的一个实现类,它内部处理了 RecyclerViewItem 的插入、删除、移动和更新动画。当调用 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,并重写了 animateAddanimateRemoveanimateMoveanimateChange 等方法来处理不同类型的动画。在 runPendingAnimations 方法中,会根据 pendingAdditionspendingRemovals 列表中的 ViewHolder 执行相应的动画。animateAddImplanimateRemoveImpl 方法分别使用 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:windowActivityTransitionstrue 开启 Activity 过渡动画,android:windowEnterTransitionandroid: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().setEnterTransitiongetWindow().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 来布局 TextViewButton。通过 app:layout_constraintTop_toTopOfapp: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 避免在主线程进行耗时操作

在表现层中,应该避免在主线程进行耗时操作,如网络请求、数据库查询等。可以使用 ViewModelLiveData 结合 CoroutineRxJava 来实现异步操作。

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/FragmentUI 宿主)
│  ├─ ViewModel (数据与逻辑)
│  └─ LiveData/State (数据订阅)
├─ RecyclerView/Compose (数据展示)
│  ├─ Adapter (数据绑定)
│  └─ DiffUtil (增量更新)
├─ DataBinding (视图绑定)
└─ 无障碍/动画/性能 (体验优化)

Room 依赖
├─ DAO (数据访问接口)
├─ LiveData/Flow (数据订阅源)
└─ TypeConverter (数据转换)

16.2 核心设计原则

  1. 单向数据流:Room → ViewModel → UI,确保状态可追溯(参考 LiveData 源码)
  2. 生命周期感知:通过 LifecycleOwner 管理 Room 查询(ViewModel 内部实现)
  3. 增量更新:利用 DiffUtilRecyclerView 局部刷新(减少绘制操作)
  4. 异步抽象:通过协程 / LiveData 隐藏 Room 线程细节(SuspendSupport 源码)
  5. 防御性编程:在 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 反模式规避

  1. ❌ 在 Activity 直接操作 Room DAO(紧耦合)
    ✅ 通过 ViewModel 间接访问(参考 UserViewModel 源码)
  2. ❌ 忽略 DiffUtil 导致全量刷新(性能损耗)
    ✅ 使用 ListAdapter 强制差分更新(源码强制实现 getItemId
  3. ❌ 在 UI 线程执行 Room 查询(ANR 风险)
    ✅ 通过 LiveData / 协程自动切换线程(RoomDatabase 内部检查)
  4. ❌ 复杂布局嵌套(过度绘制)
    ✅ 使用 ConstraintLayout + 扁平化布局(减少层级)
  5. ❌ 内存泄漏(未取消订阅)
    ✅ LiveData 自动解绑(LifecycleOwner 机制)

网站公告

今日签到

点亮在社区的每一天
去签到