Android中自定义ListView以展示多样视图布局的完整指南

AI助手已提取文章相关产品:

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在Android开发中,ListView是用于展示数据列表的常用组件,尤其以其视图复用机制提高滚动性能而著称。本文详细探讨了如何在单一ListView中显示不同类型的视图布局。我们首先了解ListView的工作原理,然后通过自定义Adapter来处理不同类型数据对象的显示。本文还包括了创建不同视图布局的XML文件,自定义Adapter的实现,以及如何在Adapter中处理数据对象和视图类型的映射,最终实现一个支持多样视图展示的ListView。
listview中显示不同的视图布局

1. ListView的工作原理与性能优化

1.1 ListView组件概述

ListView是Android开发中常用的视图组件,用于展示滚动列表。它通过适配器模式将数据与视图分离,允许开发者只需关注数据的处理,而ListView负责展示。

1.2 ListView的工作机制

当ListView被创建时,它会请求适配器数据并生成列表项。由于视图的重用机制,列表项的视图对象会在滚动过程中被重复使用,从而提高性能。

1.3 性能优化的重要性

在处理大量数据时,ListView的性能问题尤为突出。优化包括减少不必要的视图创建和渲染操作,降低内存消耗,并提高滚动流畅性。

graph LR
A[初始化ListView] --> B[请求适配器数据]
B --> C[适配器提供视图]
C --> D[视图重用]
D --> E[提高性能]

性能优化的方法涉及数据的加载方式(如使用异步任务加载数据)、视图的缓存处理(如ViewHolder模式)以及对布局文件的优化等。在下一章节中,我们将具体讨论如何通过自定义Adapter来实现这些优化措施。

2. 自定义Adapter的实现步骤

在Android开发中,Adapter模式是一种重要的设计模式,它允许我们创建一种独立于视图的、能够展示数据的组件。在列表视图(ListView)的使用过程中,Adapter扮演了数据与视图之间的桥梁角色,将来自数据源的数据绑定到特定的视图上。本章节将详细介绍自定义Adapter的实现步骤,并且涉及到Adapter模式的基本概念、数据结构的设计、接口方法的实现以及数据绑定到视图的逻辑编写。

2.1 Adapter的基本概念和作用

2.1.1 介绍Adapter模式及在Android中的应用

Adapter模式主要用于数据和视图之间的适配,使得数据能够以某种特定的格式展示出来。在Android开发中,Adapter模式广泛用于将数据集合映射到列表视图(如ListView、Spinner等)上。Adapter作为中间件,负责将数据源中的数据转换成视图组件能够接受的格式,并且在视图组件需要显示时提供数据。

适配器主要包含两个核心组件:数据源和目标视图。数据源负责提供数据,而目标视图负责显示数据。当目标视图需要显示数据时,它会向Adapter请求特定位置的数据,然后Adapter通过适当的数据转换过程,提供给目标视图进行展示。

2.1.2 分析ListView与Adapter的关系

ListView与Adapter之间的关系紧密,Adapter为ListView提供数据的填充和视图的绑定。在Android中,ListView组件用于展示一系列可滚动的列表项,每个列表项可以是一个独立的布局文件。当ListView需要显示一个列表项时,它会调用Adapter的相应方法,如 getView() ,让Adapter提供一个填充好的视图对象。

这里值得注意的是,ListView并不直接从数据源中获取数据,而是通过Adapter来间接获取。数据源可以是一个数组、ArrayList、数据库查询结果或其他数据集合。当列表项滚动进入视图时,ListView会请求Adapter提供对应的数据项视图,保证列表的滚动和数据展示效率。

2.2 自定义Adapter的基本框架搭建

2.2.1 设计Adapter的数据结构

自定义Adapter的数据结构取决于我们希望如何展示列表项。通常情况下,我们会创建一个数据模型类来封装列表项所需展示的数据。例如,如果我们正在构建一个联系人列表,我们可能需要一个名为 Contact 的类来存储每个人的名字、电话号码和图片。

public class Contact {
    private String name;
    private String phoneNumber;
    private int photoResource;

    // 构造方法、getter和setter略
}

2.2.2 实现基本的Adapter接口方法

在Android中,自定义Adapter需要实现 BaseAdapter 类,或者继承 ArrayAdapter CursorAdapter 等其他预定义的Adapter子类。以下是使用 BaseAdapter 的一个简单例子:

public class CustomAdapter extends BaseAdapter {
    private List<Contact> contacts;
    private LayoutInflater inflater;

    public CustomAdapter(Context context, List<Contact> contacts) {
        this.contacts = contacts;
        inflater = LayoutInflater.from(context);
    }

    // 实现BaseAdapter中的抽象方法
    @Override
    public int getCount() {
        return contacts.size();
    }

    @Override
    public Object getItem(int position) {
        return contacts.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.contact_item, parent, false);
        }

        TextView nameView = convertView.findViewById(R.id.name);
        TextView phoneView = convertView.findViewById(R.id.phone);
        ImageView photoView = convertView.findViewById(R.id.photo);

        Contact contact = contacts.get(position);
        nameView.setText(contact.getName());
        phoneView.setText(contact.getPhoneNumber());
        photoView.setImageResource(contact.getPhotoResource());

        return convertView;
    }
}

2.2.3 绑定数据到视图的逻辑编写

绑定数据到视图是Adapter中最核心的部分。在 getView() 方法中,我们根据当前位置从数据源中获取数据对象,并填充到对应的视图组件中。上面的代码例子中,我们根据 Contact 对象中的数据填充了视图中的 name phone photo

需要注意的是,为了提高列表的滚动效率,我们应该尽量复用视图(使用 convertView ),避免在每次调用 getView() 时都创建新的视图对象。同时,可以使用 ViewHolder 模式来减少 findViewById() 的调用次数,优化视图的查找和绑定过程。

以上为自定义Adapter实现的基本步骤,通过这个过程,我们可以实现数据和视图的灵活绑定,按照自己的需求定制列表项的显示方式。在后续的章节中,我们将继续深入探讨如何在自定义Adapter中进行数据对象的设计、优化视图创建过程和实现不同类型视图的动态展示。

3. 创建不同视图布局的XML文件

在Android应用开发中,XML布局文件是定义用户界面结构的基石。它们不仅决定了用户界面的外观,还影响到应用的性能和扩展性。本章节将深入探讨如何通过XML文件创建不同视图布局,包括基础语法、控件属性、布局类型以及如何根据应用需求设计多样化布局。

3.1 分析XML布局文件的作用和结构

3.1.1 介绍XML布局文件的基础语法

可扩展标记语言(XML)是一种广泛使用的标记语言,用于存储和传输数据。在Android开发中,XML用于定义用户界面布局,允许开发者以清晰、模块化的方式描述界面元素。一个XML布局文件通常包含以下几个基本组成部分:

  • 根元素:每一个XML布局文件必须有一个根元素,例如 <LinearLayout> 。它代表了布局的最外层容器。
  • 属性:XML标签可以包含属性来定义特定的特征。例如, android:id 定义了一个视图的唯一标识符, android:layout_width android:layout_height 定义了视图的宽度和高度。
  • 子元素:根元素可以包含多个子元素,每个子元素代表布局中的一个视图组件,如按钮、文本框等。
  • 命名空间:XML布局文件中通常会声明 xmlns:android="http://schemas.android.com/apk/res/android" ,这指定了XML文件中使用的Android属性和元素的命名空间。

代码示例:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

    <!-- 其他视图组件 -->
</LinearLayout>

在这个例子中,我们定义了一个垂直方向的LinearLayout作为根元素。它包含了一个TextView子元素。

3.1.2 讲解布局文件中的控件属性和布局类型

为了创建更加丰富和灵活的用户界面,Android提供了多种布局类型,每种布局都有其特定的属性和使用场景。常见的布局类型包括:

  • LinearLayout :线性布局,按照垂直或水平方向排列其子元素。
  • RelativeLayout :相对布局,通过相对定位的方式排列子元素,提供了更多的灵活性。
  • FrameLayout :框架布局,通常用于包含单个子视图。
  • ConstraintLayout :约束布局,允许开发者通过指定约束而不是嵌套关系来安排视图的位置。

每个布局类型都有一系列特定的属性,用来控制其子视图的排列和尺寸。例如, android:layout_weight 属性在LinearLayout中用于按比例分配空间; android:layout_gravity 属性在FrameLayout中用于设置子视图在父容器中的位置。

表格:不同布局类型的属性对比

布局类型 适用场景 特性属性
LinearLayout 基础、简单布局 android:orientation, android:layout_weight
RelativeLayout 相对定位布局 android:layout_toRightOf, android:layout_below
FrameLayout 显示单个子视图 android:layout_gravity
ConstraintLayout 复杂布局 app:layout_constraintStart_toStartOf, app:layout_constraintTop_toTopOf

3.2 设计多样化的视图布局

随着应用功能的复杂化,对用户界面的视觉体验要求越来越高。为了设计出既美观又实用的用户界面,开发者需要灵活运用各种控件属性和布局类型,创造出多样化和功能性的视图布局。

3.2.1 根据需求设计单个条目视图

单个条目视图是许多应用界面中常见的元素,例如列表中的每一项。设计单个条目视图时,需要考虑到内容的展示、视觉效果和交互体验。通常,这涉及到对TextView、ImageView、按钮等基本控件的合理布局和样式设置。

代码示例:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="16dp">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:src="@drawable/icon" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:paddingStart="16dp"
        android:text="示例文本"
        android:textSize="16sp" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="操作" />
</LinearLayout>

在这个例子中,我们设计了一个水平排列的LinearLayout,其中包含了一个图标、一段文本和一个按钮。这种布局方式在很多界面中都是很常见的。

3.2.2 实现复杂的列表项布局

随着界面需求的扩展,列表项的布局可能需要变得更加复杂,例如,包含多行文本、嵌套的布局、不同方向的排列等。这要求开发者必须掌握多种布局的组合使用和深层次的属性控制。

代码示例:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@drawable/icon" />

        <TextView
            android:id="@+id/title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_toEndOf="@id/image"
            android:layout_toRightOf="@id/image"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:text="标题"
            android:textSize="18sp"
            android:textStyle="bold" />
    </RelativeLayout>

    <TextView
        android:id="@+id/description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="这里是描述文本..."
        android:textSize="14sp"
        android:lineSpacingMultiplier="1.2" />

    <!-- 其他控件 -->
</LinearLayout>

在这个例子中,我们使用了LinearLayout和RelativeLayout的组合来创建一个复杂的列表项布局。其中,RelativeLayout用来实现图标和标题的横向排列,而TextView则放在了下一层级,用于显示描述性文本。

通过上述示例,我们可以看到,XML布局文件的灵活性和丰富性使得开发者可以根据需求设计出各种各样的界面。而且,对于XML文件中的每一个属性和子元素,开发者都应该了解其背后的原理和使用场景,这样才能够充分利用XML布局文件的强大功能,提升应用的用户体验。

4. 自定义Adapter中的数据对象设计

在构建复杂的Android UI界面时,Adapter是将数据模型与视图进行映射的重要桥梁。因此,高效且合理地设计数据对象以及管理其动态更新是实现高性能和动态交互式界面的关键。本章将深入探讨自定义Adapter中数据对象的设计方法,以及如何通过这些对象实现数据与视图的同步更新。

4.1 数据对象的定义和封装

4.1.1 设计数据对象的属性和方法

数据对象是业务逻辑的实体化表示,它通常对应于现实世界中的某些事物。例如,在一个社交应用中,用户对象可能包括姓名、头像、状态信息等属性。在设计数据对象时,需要考虑以下几个核心要素:

  • 属性(Properties) :表示数据对象的状态信息,如字符串、数值、布尔值等。
  • 方法(Methods) :对数据进行操作的行为,比如获取或设置某些属性值。
public class User {
    private String name;
    private String avatar;
    private String status;

    // 构造函数
    public User(String name, String avatar, String status) {
        this.name = name;
        this.avatar = avatar;
        this.status = status;
    }

    // Getter 和 Setter 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}

在上述代码中,我们定义了一个 User 类,其中包含了三个属性: name (姓名)、 avatar (头像)、 status (状态信息)。每个属性都通过getter和setter方法进行封装,以保证数据的安全性和可维护性。

4.1.2 数据对象与视图的映射关系

数据对象与视图之间存在着直接的映射关系。换言之,视图上显示的信息需要根据数据对象中的信息动态生成。这种映射关系通常在Adapter的 getView() 方法中实现。在实现映射时,要确保UI的响应性和数据的一致性。

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(context);
        convertView = inflater.inflate(R.layout.list_item_user, null);
        holder = new ViewHolder();
        holder.nameTextView = convertView.findViewById(R.id.nameTextView);
        holder.avatarImageView = convertView.findViewById(R.id.avatarImageView);
        holder.statusTextView = convertView.findViewById(R.id.statusTextView);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    User user = users.get(position);
    holder.nameTextView.setText(user.getName());
    holder.avatarImageView.setImageResource(user.getAvatarResId()); // 假设有一个方法返回对应的图片资源ID
    holder.statusTextView.setText(user.getStatus());
    return convertView;
}

上述代码段展示了如何将 User 对象的数据映射到一个ListView的item布局上。这里使用了 ViewHolder 模式来优化性能,避免重复查找视图。

4.2 数据对象的动态更新和管理

4.2.1 数据源的构建和更新策略

数据源是Adapter中数据的来源。它可能是一个数组、列表或其他任何数据集合。在Android应用中,数据源的动态更新是常见的需求,比如在接收网络数据更新界面时。为了有效地管理数据源,我们需要实现一些更新策略,例如添加、删除和刷新数据。

// 假设这是一个数据源列表
private List<User> users = new ArrayList<>();

// 添加一个用户到列表
public void addUser(User user) {
    users.add(user);
    notifyDataSetChanged(); // 通知数据已经改变
}

// 删除一个用户
public void removeUser(int position) {
    users.remove(position);
    notifyDataSetChanged(); // 通知数据已经改变
}

// 刷新整个列表
public void refreshUsers(List<User> newUsers) {
    users.clear();
    users.addAll(newUsers);
    notifyDataSetChanged(); // 通知数据已经改变
}

在这个简单的例子中,我们定义了一个 users 列表作为数据源,添加了添加、删除和刷新方法,每一个方法最后都调用了 notifyDataSetChanged() 来通知Adapter数据已经发生变化,进而更新UI。

4.2.2 实现数据与视图的同步更新

同步更新数据与视图是确保用户界面反映最新数据的关键。在Android中,Adapter机制允许开发者通过调用 notifyDataSetChanged() 方法来通知数据已改变。然而,这个方法会触发整个列表的重新绘制,可能会导致性能问题。因此,应谨慎使用,并结合具体场景来实现数据与视图的同步更新。

// 在数据更新后,适当地更新视图
public void updateUserStatus(int position, String newStatus) {
    User user = users.get(position);
    user.setStatus(newStatus);
    notifyDataSetChanged(); // 如果只是单个数据项更新,可以更具体地指定更新范围或更新方式
}

在这个示例中,我们只更新了单个用户的 status 属性,并通知了整个列表的数据变化。在实际应用中,为了更高效地更新UI,我们可以实现更具体的更新策略,例如只更新发生变化的数据项,或者使用 notifyItemChanged() 方法来只更新某个特定位置的视图。

通过以上内容,我们可以看到,在自定义Adapter中设计合适的数据对象对于保证应用性能和用户体验至关重要。掌握数据与视图的同步更新策略,将有助于我们创建更加动态和响应迅速的Android应用。

5. 在Adapter中使用ViewHolder模式优化视图创建过程

5.1 ViewHolder模式的原理和优势

5.1.1 解释ViewHolder模式的工作机制

ViewHolder模式是一种常用于Android开发中,特别是在Adapter中优化列表视图显示性能的模式。其核心思想是通过缓存视图引用以减少findViewById的次数,从而提高性能。在一个典型的ListView或RecyclerView的Adapter实现中,每一个列表项都需要通过findViewById来获取视图引用,这在列表项较多时会非常耗时。

ViewHolder模式通过定义一个ViewHolder内部类来保存这些视图引用,这样一来,在列表滚动时,相同的视图可以快速地通过Holder找到,避免了重复的findViewById操作。具体实现时,可以将ViewHolder作为Adapter内部类,或者在绑定视图数据时使用一个静态内部类来实现。

5.1.2 比较ViewHolder模式与传统方法的性能差异

使用传统的findViewById方法,每次滚动列表时都会重新从布局文件中查找视图元素,这不仅涉及大量的计算,还会消耗更多的内存。而ViewHolder模式将这些视图元素缓存到了内存中,当列表项需要被重用时,只需要获取缓存中的ViewHolder对象即可。在性能对比实验中,使用ViewHolder模式相较于传统方法,可以在滚动列表时减少超过60%的布局加载时间。

在Android的官方文档中,也推荐开发者使用ViewHolder模式来实现Adapter,因为它可以显著提高列表的滚动性能,减少卡顿现象,提高用户体验。

5.2 ViewHolder模式的具体实现

5.2.1 创建ViewHolder类及其缓存机制

以下是一个简化的ViewHolder实现示例代码:

public class MyAdapter extends BaseAdapter {
    // ...省略其他代码

    // ViewHolder类定义
    static class ViewHolder {
        TextView textView;
        ImageView imageView;
        // 构造函数,初始化控件引用
        public ViewHolder(View view) {
            textView = view.findViewById(R.id.textView);
            imageView = view.findViewById(R.id.imageView);
        }
    }

    // ...省略其他代码
}

在实际应用中,ViewHolder类的构造函数通常通过传入的View参数来初始化控件引用。为了更好地复用视图,通常会将ViewHolder类定义为静态的内部类。这样做的好处是,它不会持有外部类的任何引用,从而避免内存泄漏。

5.2.2 在Adapter中集成ViewHolder模式

public class MyAdapter extends BaseAdapter {
    // ...省略其他代码

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // 缓存机制,提高性能
        ViewHolder holder;
        if (convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(getContext());
            convertView = inflater.inflate(R.layout.item_layout, parent, false);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        // 获取当前位置的数据并设置到视图上
        Item item = getItem(position);
        holder.textView.setText(item.getText());
        holder.imageView.setImageResource(item.getImageId());
        return convertView;
    }

    // ...省略其他代码
}

在这段代码中,首先检查 convertView 是否为 null ,若为 null ,则通过 LayoutInflater 来创建视图,并初始化ViewHolder对象。然后将ViewHolder对象设置到 convertView 的Tag中。如果 convertView 不为 null ,则直接从Tag中取出ViewHolder对象。这样做的好处是,无论列表滚动到哪里,都可以快速地找到ViewHolder,并且只需要在第一次加载时通过findViewById来初始化视图,大大减少了性能开销。

通过以上的实现,我们看到ViewHolder模式能够有效地减少列表的滚动卡顿,提升滚动性能,对于需要滚动显示大量数据的列表尤其有效。接下来的章节将讨论如何在Adapter中展示不同类型视图的实现方法。

6. 动态展示不同类型视图的实现方法

6.1 动态视图展示的基本原理

6.1.1 分析ListView中不同类型视图的切换逻辑

在Android开发中,ListView组件常用于展示列表数据。为了提升用户体验,开发者可能需要在同一列表中展示多种不同类型的数据视图。动态视图展示的关键在于自定义的Adapter类能够识别不同类型的数据,并为每种数据类型加载相应的布局文件。

6.1.2 讨论在Adapter中处理不同视图类型的策略

处理不同类型视图的基本策略是在Adapter中重写 getView() 方法。这个方法会检查传入位置的数据类型,并返回相应类型的视图对象。具体来说,需要以下步骤:
1. 定义一个用于标识视图类型的常量。
2. 在 getView() 方法中,根据数据类型获取对应的视图资源ID。
3. 利用LayoutInflater来加载布局文件,创建视图对象。
4. 将数据绑定到视图上,并返回视图对象。

6.2 实现动态视图展示的代码逻辑

6.2.1 编写检测视图类型的方法

在Adapter中,需要编写一个方法来检测传入位置的数据类型。这可以通过switch语句或者if-else语句来实现。通常,数据类型的检测是基于数据模型对象的不同属性或者类型来进行的。

@Override
public int getItemViewType(int position) {
    // 假设数据模型有一个类型字段
    DataType data = getItem(position);
    if (data.getType() == DataType.TYPE_ONE) {
        return VIEW_TYPE_ONE;
    } else if (data.getType() == DataType.TYPE_TWO) {
        return VIEW_TYPE_TWO;
    }
    // 其他类型...
    return super.getItemViewType(position);
}

6.2.2 根据不同的数据类型加载对应的布局文件

getView() 方法中,使用 getItemViewType() 方法来决定加载哪个布局文件。通过LayoutInflater来加载布局,并创建视图对象。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    int type = getItemViewType(position);
    if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        switch (type) {
            case VIEW_TYPE_ONE:
                convertView = inflater.inflate(R.layout.view_type_one, parent, false);
                break;
            case VIEW_TYPE_TWO:
                convertView = inflater.inflate(R.layout.view_type_two, parent, false);
                break;
            // 其他类型...
        }
    }
    // 绑定数据到视图...
    return convertView;
}

6.2.3 测试和调试动态视图展示功能

最后,在实际设备或模拟器上测试Adapter的功能,确保它能够正确地展示不同类型的数据视图。调试过程中要确保以下几点:
- 所有的数据类型都能被正确识别。
- 对应的布局文件是否被正确加载。
- 数据是否准确地绑定到视图上。

通过以上步骤,我们可以实现一个动态展示不同类型视图的ListView。为了达到最佳性能,还可以考虑使用ViewHolder模式来缓存视图,这样可以进一步提高列表滚动时的性能表现。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在Android开发中,ListView是用于展示数据列表的常用组件,尤其以其视图复用机制提高滚动性能而著称。本文详细探讨了如何在单一ListView中显示不同类型的视图布局。我们首先了解ListView的工作原理,然后通过自定义Adapter来处理不同类型数据对象的显示。本文还包括了创建不同视图布局的XML文件,自定义Adapter的实现,以及如何在Adapter中处理数据对象和视图类型的映射,最终实现一个支持多样视图展示的ListView。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

您可能感兴趣的与本文相关内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值