JetPack——Paging

Paging是什么?

Paging 用于加载和显示分页数据

  • 分页数据的内存中缓存。该功能有助于确保您的应用在处理分页数据时高效使用系统资源。
  • 内置的请求去重功能,可确保您的应用高效利用网络带宽和系统资源。
  • 可配置的 RecyclerView 适配器,会在用户滚动到已加载数据的末尾时自动请求数据。
  • 对 Kotlin 协程和数据流以及 LiveData 和 RxJava 的一流支持。
  • 内置对错误处理功能的支持,包括刷新和重试功能。

Paging架构

  • PagingSource 对象定义了数据源,以及如何从该数据源检索数据
  • RemoteMediator 对象会处理来自分层数据源(例如具有本地数据库缓存的网络数据源)的分页
  • Pager基于PagingSource和PagingConfig构造Flow<PagingData>
  • PagingData 是用于存放分页数据快照的容器。它会查询 PagingSource 对象并存储结果
  • PagingDataAdapter,是一种处理分页数据的RecyclerView.Adapter

Paging实现

依赖

复制代码
def paging_version = "3.4.1"

  implementation "androidx.paging:paging-runtime:$paging_version"

  // alternatively - without Android dependencies for tests
  testImplementation "androidx.paging:paging-common:$paging_version"

  // optional - RxJava2 support
  implementation "androidx.paging:paging-rxjava2:$paging_version"

  // optional - RxJava3 support
  implementation "androidx.paging:paging-rxjava3:$paging_version"

  // optional - Guava ListenableFuture support
  implementation "androidx.paging:paging-guava:$paging_version"

  // optional - Jetpack Compose integration
  implementation "androidx.paging:paging-compose:3.4.1"

Bean

复制代码
public class User {
    public int id;
    public String name;
    public int age;

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

PagingSource<Key, Value>

数据源,Key定义了用于加载数据的标识符,Value是数据本身的类型

  • 如需使用 RxJava,实现 RxPagingSource
  • 如需使用 Guava 中的 ListenableFuture,实现 ListenableFuturePagingSource
java 复制代码
public class UserPagingSource extends PagingSource<Integer, User> {
    private final static String TAG = "UserPagingSource";

    // 模拟从网络加载数据
    private List<User> loadUsersFromNetwork(int page, int pageSize) {
        List<User> users = new ArrayList<>();
        // 模拟每页返回 10 条数据
        int startIndex = (page - 1) * pageSize + 1;
        for (int i = 0; i < pageSize; i++) {
            users.add(new User(startIndex + i, "用户" + (startIndex + i), i + 1));
        }
        return users;
    }

    @Nullable
    @Override
    public Object load(@NonNull LoadParams<Integer> params,
                       @NonNull Continuation<? super LoadResult<Integer, User>> continuation) {
        try {
            // 获取页码(第一页是 null,第二页开始是具体的页码)
            int page = params.getKey() == null ? 1 : params.getKey();
            int pageSize = params.getLoadSize();
            Log.d(TAG, "正在加载第 " + page + " 页" + ", LoadSize = " + pageSize);

            // 模拟网络延迟
            Thread.sleep(2000); // 2秒延迟,模拟网络请求
            // 模拟网络请求(实际项目中这里是调用 API)
            List<User> users = loadUsersFromNetwork(page, pageSize);

            Log.d(TAG, "第 " + page + " 页加载完成,数据量: " + users.size());

            // 返回数据页
            return new LoadResult.Page<>(
                    users,                           // 数据列表
                    page > 1 ? page - 1 : null,    // 前一页(如果是第一页,传 null)
                    users.size() == pageSize ? page + 1 : null  // 后一页(还有数据就传下一页,否则传 null)
            );
        } catch (Exception e) {
            return new LoadResult.Error<>(e);
        }
    }


    // 确定刷新时从哪个位置开始加载数据,以保持用户的浏览位置
    @Nullable
    @Override
    public Integer getRefreshKey(@NonNull PagingState<Integer, User> state) {
        // 获取锚点位置(当前可见的第一个位置)
        Integer anchorPosition = state.getAnchorPosition();
        if (anchorPosition == null) {
            return null; // 没有可见位置,无法确定刷新键
        }

        // 找到距离锚点位置最近的页面
        LoadResult.Page<Integer, User> anchorPage = state.closestPageToPosition(anchorPosition);
        if (anchorPage == null) {
            return null; // 没有找到页面
        }

        // 获取前一页的键
        Integer prevKey = anchorPage.getPrevKey();
        if (prevKey != null) {
            return prevKey + 1; // 返回下一页的键
        }

        // 获取下一页的键
        Integer nextKey = anchorPage.getNextKey();
        if (nextKey != null) {
            return nextKey - 1; // 返回前一页的键
        }

        // 都没有键,返回 null(初始页)
        return null;
    }
}

load()获取分页数据

  • LoadParams 为加载信息,包含键和项数等
  • LoadResult 为加载结果,成功返回 LoadResult.Page,失败返回 LoadResult.Error

getRefreshKey()当数据在初始加载后刷新或失效时,返回传递给 load() 方法的键。在后续刷新数据时,会自动调用此方法

PagingData

通过Pager 获取来自 PagingSource 的 PagingData 对象的响应式流,如返回LiveData

java 复制代码
public class UserRepository {

    public LiveData<PagingData<User>> getUsers() {
        // 创建分页配置
        Pager<Integer, User> pager = new Pager<>(
                new PagingConfig(
                        10,     // 每次加载的数据条数
                        5,            // 距离列表末尾还有多少条数据时开始预加载
                        true,         // 加载期间是否使用空白占位符
                        20,          // 首次加载的数据量
                        200         // 内存中最多缓存多少条数据
                ),
                () -> new UserPagingSource() // 数据源
        );
        return PagingLiveData.getLiveData(pager);
    }
}
java 复制代码
public class UserViewModel extends ViewModel {
    private final UserRepository repository = new UserRepository();

    public LiveData<PagingData<User>> getUsers() {
        return repository.getUsers();
    }
}

PagingDataAdapter

RecycleView适配器继承PagingDataAdapter

java 复制代码
public class UserAdapter extends PagingDataAdapter<User, UserAdapter.UserViewHolder> {

    // 比较规则(用于判断数据是否相同)
    private static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = new DiffUtil.ItemCallback<User>() {
        @Override
        public boolean areItemsTheSame(User oldItem, User newItem) {
            return oldItem.id == newItem.id;
        }

        @Override
        public boolean areContentsTheSame(User oldItem, User newItem) {
            return oldItem.name.equals(newItem.name) && oldItem.age == newItem.age;
        }
    };

    public UserAdapter() {
        super(DIFF_CALLBACK);
    }

    @NonNull
    @Override
    public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
        return new UserViewHolder(view);
    }

    @Override
    public void onBindViewHolder(UserViewHolder holder, int position) {
        User user = getItem(position);
        if (user != null) {
            holder.bind(user);
        }
    }

    public static class UserViewHolder extends RecyclerView.ViewHolder {
        TextView tvName, tvAge;

        UserViewHolder(View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tv_name);
            tvAge = itemView.findViewById(R.id.tv_age);
        }

        void bind(User user) {
            tvName.setText(user.name);
            tvAge.setText(String.valueOf(user.age));
        }
    }
}

MainActivity

获取LiveData,通过submitData提交数据

java 复制代码
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private UserAdapter adapter;
    private UserViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewModel = new ViewModelProvider(this).get(UserViewModel.class);
        initView();
        initData();
        initListener();
    }

    private void initListener() {
        viewModel.getUsers().observe(this, pagingData -> {
            adapter.submitData(getLifecycle(), pagingData);
        });
    }

    private void initData() {
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new UserAdapter();
        recyclerView.setAdapter(adapter);
    }

    private void initView() {
        recyclerView = findViewById(R.id.recyclerView);
    }
}

activity_main.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

item_user.xml

xml 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:gravity="left"
    android:layout_marginTop="25dp"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_gravity="center"
        android:text="用户A"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tv_age"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="50dp"
        android:text="20"
        android:textSize="14sp" />

</LinearLayout>
相关推荐
城东米粉儿2 小时前
Android Kotlin DSL 笔记
android
城东米粉儿2 小时前
Android Gradle 笔记
android
城东米粉儿2 小时前
Android Monkey 笔记
android
城东米粉儿2 小时前
Android 组件化 笔记
android
编程小风筝2 小时前
Android移动端如何实现多线程编程?
android
城东米粉儿3 小时前
Android 模块化 笔记
android
城东米粉儿3 小时前
Android HandlerThread 笔记
android
城东米粉儿3 小时前
Android Condition 笔记
android
肖。35487870943 小时前
html中onclick误区,后续变量会更改怎么办?
android·java·javascript·css·html