自定义Android ListView下拉刷新与上拉加载更多功能实现

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

简介:在Android应用开发中,为了增强用户体验,通常需要实现下拉刷新和上拉加载更多数据的功能。本文将详细介绍如何创建自定义的ListView组件,包括创建自定义刷新视图、集成到ListView中、处理滑动事件、编写刷新和加载更多逻辑、添加动画效果、进行兼容性处理以及优化性能。通过这些步骤,开发者可以创建出一个既美观又高效的用户界面组件。 Android自定义下拉刷新上拉加载更多Listview

1. 下拉刷新和上拉加载概念

在移动应用开发领域,下拉刷新和上拉加载更多这两种交互模式已经成为提升用户体验的关键因素。用户通过简单的下拉或上拉操作,无需经历漫长的等待或繁琐的翻页流程,即可快速获取新内容。本章将详细介绍这两种模式的基本概念、设计目的以及在Android平台上的实现重要性。

1.1 下拉刷新与上拉加载的定义

下拉刷新(Pull to Refresh) 是一种用户界面模式,允许用户通过下拉列表顶部来触发一个刷新动作,通常是用来更新内容或数据。当用户执行下拉操作时,应用会显示一个正在刷新的指示器,表明数据正在重新加载。

上拉加载更多(Load More on Pull to End) 则是用户滚动至列表底部时触发的加载动作。为了优化性能和用户体验,开发者往往采用分页的方式加载数据,这种方式可以减少一次性加载过多内容所带来的延迟。

1.2 设计初衷

设计下拉刷新和上拉加载更多的初衷是为了简化用户的操作流程,提高信息获取的效率。这两个功能使得用户可以更加直观地与应用进行交互,同时也减少了等待时间,提升了应用的流畅性和响应速度。

1.3 在Android开发中的重要性

在Android开发中,这两种模式的引入对于构建用户友好的界面至关重要。通过合理的设计和实现,开发者可以创建出更加吸引人、易于操作的应用。此外,这种设计方式在符合用户习惯的同时,还能够提高应用的性能表现,特别是在处理大量数据时。

通过理解这两个模式的基本理念和实现方式,开发者可以更好地为用户提供连贯和高效的交互体验。接下来的章节将详细介绍如何在Android开发中实现自定义的刷新视图,进一步提升应用的交互品质。

2. 创建自定义刷新视图

2.1 设计自定义刷新头布局

2.1.1 分析刷新头布局需求

在移动应用中,自定义刷新头布局是用户体验的关键点之一。它通常需要满足以下几点基本需求:

  • 视觉吸引力 :吸引用户进行下拉操作,清晰表明有新的内容等待加载。
  • 功能性 :准确反馈当前刷新状态,包括是否正在加载,加载成功或失败等。
  • 适应性 :支持不同屏幕尺寸和分辨率,保证良好的显示效果。
2.1.2 利用XML定义布局结构

定义一个XML布局文件,使用布局组件来构建刷新头的基本结构。以Material Design风格为例:

<!-- refresh_header.xml -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:gravity="center"
    android:background="@color/white">
    <ProgressBar
        android:id="@+id/refresh_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true"
        style="@android:style/Widget.ProgressBar.Horizontal"/>
    <TextView
        android:id="@+id/refresh_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/loading"/>
</LinearLayout>
2.1.3 利用绘图API绘制UI元素

利用Android提供的绘图API,可以进一步自定义刷新头的UI元素,比如刷新动画:

// Draw the refresh animation, could be an SVG or drawable resource
private void drawRefreshAnimation(Canvas canvas) {
    // Code to draw an animation
}

2.2 触摸反馈与指示器设计

2.2.1 实现触摸事件监听

创建一个自定义的 SwipeRefreshLayout 类,用于监听用户的触摸事件,主要关注的是用户的下拉动作。

// Custom SwipeRefreshLayout class
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {

    private OnRefreshListener onRefreshListener;

    public CustomSwipeRefreshLayout(Context context) {
        super(context);
        init();
    }

    private void init() {
        // Initialize the layout, set color, etc.
    }

    @Override
    public boolean canChildScrollUp() {
        // Determine if the child view can be scrolled up
        // This is important to determine if a refresh should be triggered
        return false;
    }

    public void setOnRefreshListener(OnRefreshListener listener) {
        this.onRefreshListener = listener;
    }
    // ... other methods
}
2.2.2 设计加载指示器动画

加载指示器动画可以是一个旋转的进度条或自定义动画。利用 ValueAnimator ObjectAnimator 来实现动画效果。

// Example of loading indicator animation with ValueAnimator
private void animateLoadingIndicator() {
    ValueAnimator animator = ValueAnimator.ofInt(0, 100);
    animator.setDuration(1000);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int value = (int) animation.getAnimatedValue();
            // Update the loading bar or animation based on value
        }
    });
    animator.start();
}
2.2.3 动画与用户交互的同步

为了确保动画和用户交互同步,应当在动画开始时通知用户,动画结束时更新UI状态。

// Callback for animation completion
private void onRefreshAnimationComplete() {
    // Update UI components to reflect the new data has been loaded
    // Set the refresh state to false, and hide the loading indicator
}

在这个章节中,我们先从布局需求分析开始,到利用XML定义布局结构,再到使用绘图API绘制UI元素。然后深入到了触摸反馈与指示器设计,如何实现触摸事件监听,设计加载指示器动画,以及如何让动画与用户交互同步。这一系列的操作与设计细节都是为了提升用户体验,使得下拉刷新和上拉加载的操作更流畅、直观。

3. 集成自定义刷新视图到ListView

在移动应用开发中,列表视图(ListView)是一个经常使用的组件,用于展示大量数据。为了提升用户体验,常常需要集成自定义的刷新视图。本章将介绍如何将自定义的下拉刷新和上拉加载更多功能集成到ListView中。

3.1 ListView的适配器模式理解

适配器模式是Android开发中广泛使用的设计模式之一。它将数据源和UI组件之间进行解耦,使得数据的加载、展示和更新更加灵活。

3.1.1 适配器的基本组成和职责

适配器(Adapter)主要由以下几个部分组成:

  • ViewHolder : 缓存当前行的视图,减少视图的重复创建。
  • bindViewHolder() : 将数据与视图绑定,刷新单个视图。
  • getCount() : 返回数据源中的项数。
  • getItem(int position) : 返回特定位置的数据。
  • getItemId(int position) : 返回特定位置数据的ID。

适配器的职责如下:

  • 根据数据源构建视图。
  • 为ListView提供数据集。
  • 处理列表项点击事件。

3.1.2 自定义适配器与视图绑定

要创建自定义适配器,可以继承 BaseAdapter 类,并重写必要的方法:

public class MyAdapter extends BaseAdapter {
    private List<MyData> dataList;
    private LayoutInflater inflater;

    public MyAdapter(Context context, List<MyData> dataList) {
        this.dataList = dataList;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return dataList.size();
    }

    @Override
    public MyData getItem(int position) {
        return dataList.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.list_item, parent, false);
            holder = new ViewHolder();
            holder.textView = convertView.findViewById(R.id.text_view);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        MyData data = getItem(position);
        holder.textView.setText(data.getContent());
        return convertView;
    }
}

3.2 ListView数据绑定与更新

处理数据源和视图的同步更新是确保ListView正确显示信息的关键。

3.2.1 数据源的管理

数据源通常是一个集合,比如 ArrayList LinkedList 。管理数据源时,可能需要进行添加、删除、排序等操作。

3.2.2 适配器数据更新机制

更新数据源后,需要通知ListView重新绘制,以反映数据的变化。这可以通过调用 notifyDataSetChanged() 方法实现。

adapter.notifyDataSetChanged();

3.2.3 视图与数据的同步刷新

要实现视图与数据的同步刷新,可以通过修改 getView() 方法中绑定数据的逻辑。

3.3 自定义刷新视图的嵌入方法

集成自定义刷新视图到ListView需要自定义触摸事件以及触发下拉和上拉的逻辑。

3.3.1 重写ListView的触摸事件

可以通过覆写 onTouchEvent() 方法来处理触摸事件:

@Override
public boolean onTouchEvent(MotionEvent ev) {
    // 逻辑处理
    return super.onTouchEvent(ev);
}

3.3.2 实现下拉和上拉的触发逻辑

当检测到下拉或上拉动作时,触发刷新或加载更多的逻辑:

// 示例代码:检测下拉动作
if (ACTION_PULL_DOWN == action) {
    // 触发刷新逻辑
}

3.3.3 刷新视图的回调机制完善

完善回调机制,确保刷新视图能够在适当的时机被激活或停止。

// 示例代码:回调机制
if (shouldRefresh()) {
    refreshView.startRefresh();
} else {
    refreshView.stopRefresh();
}

通过本节的介绍,我们了解了如何将自定义刷新视图集成到ListView中,包括适配器模式的理解、数据绑定与更新以及自定义刷新视图的嵌入方法。接下来,我们将深入探讨滑动事件处理以及实现刷新和加载更多逻辑的细节。

4. 滑动事件处理

在移动应用中,滑动事件是用户与界面交互的基本方式之一。对于包含下拉刷新和上拉加载更多功能的列表,正确处理滑动事件是实现流畅用户体验的关键。本章将介绍如何捕捉和判断滑动事件,以及如何逻辑处理这些事件,以确保刷新和加载的触发时机准确无误。

4.1 滑动事件的捕捉与判断

4.1.1 利用Scroller检测滑动

在Android开发中, Scroller 是一个用于处理滚动操作的类,它可以在一段时间内平滑滚动视图。要捕捉滑动事件,首先需要通过 Scroller 来检测滑动的发生。

private Scroller mScroller;
private int mLastY; // 上一次触摸位置的Y坐标

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 按下时记录当前Y坐标
            mLastY = (int) event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            int currentY = (int) event.getY();
            int deltaY = currentY - mLastY;
            // 当前滑动距离大于某个阈值才进行滑动处理
            if (Math.abs(deltaY) > SCROLL_THRESHOLD) {
                // 使用Scroller来滚动视图
                if (deltaY > 0) {
                    // 下滑时
                    mScroller.startScroll(getScrollX(), getScrollY(), 0, -deltaY);
                } else {
                    // 上滑时
                    mScroller.startScroll(getScrollX(), getScrollY(), 0, -deltaY);
                }
                invalidate(); // 重绘视图
            }
            mLastY = currentY;
            break;
    }
    return true;
}

在上述代码中, SCROLL_THRESHOLD 是一个设定的滑动距离阈值,用于判断用户的滑动是否足够大到需要响应。只有当滑动超过这个阈值时,才会调用 Scroller 进行滚动处理。

4.1.2 确定滑动的方向和范围

在确定滑动事件发生后,接下来需要确定滑动的方向和范围,这对于后续判断是触发下拉刷新还是上拉加载更多至关重要。

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        int currY = mScroller.getCurrY();
        if (mLastScrollY == 0) {
            mLastScrollY = currY;
        }
        int diff = currY - mLastScrollY;
        if (Math.abs(diff) > SCROLL_THRESHOLD) {
            if (diff > 0) {
                // 向下滑动
                handleScrollDown(diff);
            } else {
                // 向上滑动
                handleScrollUp(diff);
            }
        }
        mLastScrollY = currY;
        postInvalidate(); // 通知视图重绘
    }
}

handleScrollDown handleScrollUp 方法将分别处理向下滑动和向上滑动的情况,这些方法将包含触发刷新或加载更多的逻辑。

4.2 滑动事件的逻辑处理

4.2.1 触发刷新与加载更多的逻辑

在滑动事件的逻辑处理中,我们需要编写特定的代码来决定何时触发刷新或加载更多。

private void handleScrollDown(int scrollY) {
    // 滑动距离向下且达到刷新的条件时
    if (scrollY > 0 && shouldRefresh()) {
        startRefreshAnimation();
        // 执行后台刷新逻辑
        refreshListData();
    }
}

private void handleScrollUp(int scrollY) {
    // 滑动距离向上且达到加载更多的条件时
    if (scrollY < 0 && shouldLoadMore()) {
        startLoadMoreAnimation();
        // 执行加载更多数据的逻辑
        loadMoreData();
    }
}

在这段伪代码中, shouldRefresh shouldLoadMore 方法会根据当前列表的显示状态来决定是否满足触发条件。 startRefreshAnimation startLoadMoreAnimation 方法将启动相应的动画效果,而 refreshListData loadMoreData 则是具体的数据处理方法。

4.2.2 滑动冲突的解决策略

在实际应用中,可能会遇到滑动事件与列表的滚动冲突问题。例如,用户在列表底部上拉时,应触发加载更多而不是继续滚动列表。

public boolean onInterceptTouchEvent(MotionEvent event) {
    if (isAtListBottom()) {
        // 如果当前在列表底部,则拦截事件,执行加载更多逻辑
        handleScrollUp(event.getY() - mLastY);
        return true;
    }
    return super.onInterceptTouchEvent(event);
}

isAtListBottom 方法用于检查当前是否滚动到列表底部。如果是,则拦截事件,直接调用 handleScrollUp 来加载更多数据,而不是让列表继续滚动。

4.2.3 优化滑动体验的细节处理

最后,要实现最佳的用户体验,还需要关注一些细节的处理,例如过度滚动效果、滑动时的阻尼感等。

@Override
public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
    super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
    // 添加过度滚动效果,例如弹性效果
    float scale = Math.min(1, (float) Math.abs(scrollY) / OVERSCROLL_DISTANCE);
    setScaleX(scale);
    setScaleY(scale);
}

在上面的 onOverScrolled 方法中, OVERSCROLL_DISTANCE 代表过度滚动的距离。当用户滑动超过这个距离时,视图会有一个弹性效果,让滑动看起来更有质感。

以上是第四章滑动事件处理的主要内容,从捕捉和判断滑动事件的细节,到逻辑处理以及优化滑动体验的措施,都是为了确保刷新和加载更多的交互自然流畅。通过本章节的介绍,开发者可以更深入地理解在Android开发中处理滑动事件的重要性,并掌握相关技术和最佳实践。

5. 刷新和加载更多逻辑实现

5.1 刷新逻辑的实现

刷新功能是用户与应用交互中重要的一个环节,它能够帮助用户获取最新的信息或数据。在移动应用中,常见的刷新场景有接收即时消息、查看最新动态、同步数据等。为了实现这一功能,开发者需要处理好触发刷新的条件、后台数据处理逻辑以及用户界面的更新。

5.1.1 触发刷新的条件设置

触发刷新的条件可以是用户显式操作,如用户下拉列表,也可以是应用内部自动触发,比如在一些社交应用中,当有新的消息或者动态时,应用会自动提示用户进行刷新操作。为了设置这些条件,我们可以使用Android中的 SwipeRefreshLayout 控件,它可以识别手指的滑动动作来触发刷新。

SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    @Override
    public void onRefresh() {
        // 在这里执行刷新逻辑
    }
});

5.1.2 后台数据刷新与数据状态监听

在实际应用中,刷新通常涉及到后台数据的更新。这可能涉及到网络请求,数据库更新,或者本地文件的读写操作。在Android中,推荐使用异步任务来处理这些操作,以避免阻塞主线程。 AsyncTask 或者 java.util.concurrent 包中的相关类都是不错的选择。

private class RefreshDataTask extends AsyncTask<Void, Void, Boolean> {

    @Override
    protected Boolean doInBackground(Void... voids) {
        // 执行后台数据刷新操作
        return true;
    }

    @Override
    protected void onPostExecute(Boolean success) {
        swipeRefreshLayout.setRefreshing(false);
        // 根据返回结果更新UI
    }
}

5.1.3 刷新完成后的UI更新

一旦后台数据刷新完成,我们需要及时更新用户界面以反映最新的数据状态。这通常涉及更新列表视图、显示或隐藏提示信息、重新绘制数据图表等操作。

public void updateUI() {
    // 更新ListView、RecyclerView等数据源
    // 刷新完成后重新加载适配器
    adapter.notifyDataSetChanged();
}

5.2 加载更多逻辑的实现

加载更多与下拉刷新有相似之处,但是它通常用于分页加载数据,以提高数据加载的效率和用户体验。实现加载更多的逻辑需要考虑分页逻辑、数据状态管理以及加载结束的UI反馈。

5.2.1 实现分页加载机制

分页加载机制可以根据不同的需求进行设计。在Android开发中,我们可以在用户滚动到列表末尾时,通过监听滚动事件来触发数据加载。通常,这涉及到计算当前加载的页码以及剩余的可加载数量。

private int currentPage = 1;
private boolean isLoading = false;

listView.setOnScrollListener(new AbsListView.OnScrollListener() {
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // 当滚动到列表底部时触发加载更多
        if (firstVisibleItem + visibleItemCount == totalItemCount && !isLoading) {
            isLoading = true;
            currentPage++;
            loadMoreData(currentPage);
        }
    }
});

5.2.2 数据加载结束的标记与UI反馈

数据加载结束后,我们需要通知用户加载已完成。这可以通过在加载函数中添加特定的标记和UI操作来实现。

private void loadMoreData(int page) {
    // 执行加载更多数据的操作
    if (isDataLoaded) {
        // 数据加载完成的处理
        isLoading = false;
        // 更新UI,例如隐藏加载动画
    }
}

5.2.3 异常处理及用户提示

在数据加载过程中,可能会遇到各种异常情况,如网络错误、服务器错误、数据解析异常等。对此,我们需要在设计加载逻辑时考虑异常处理机制,以及如何向用户反馈这些异常信息。

catch (Exception e) {
    // 异常处理逻辑
    Toast.makeText(context, "加载数据失败,请稍后重试", Toast.LENGTH_SHORT).show();
    isLoading = false;
}

综上所述,实现刷新和加载更多逻辑是提升移动应用用户体验的关键步骤。通过对触发条件、数据处理和用户界面更新等方面的精心设计和编码,我们可以为用户提供流畅、高效且直观的数据交互体验。接下来,我们将深入探讨如何通过动画效果的添加和性能优化来进一步提升用户体验。

6. 动画效果添加与性能优化

6.1 动画效果的添加

6.1.1 动画在刷新加载中的作用

动画效果是提升用户体验的关键组成部分,尤其在刷新和加载过程中,合理的动画能够给用户明确的反馈,指示操作进度。在自定义刷新加载视图中添加动画,不仅可以使界面看起来更加流畅,还能缓解用户的等待感。

6.1.2 实现流畅的动画过渡效果

为了实现流畅的动画过渡效果,我们通常会使用Android内置的 Animation 类,或者更先进的 ObjectAnimator AnimatorSet 类。下面是一个简单的示例代码,展示如何使用 ObjectAnimator 来实现一个旋转动画效果:

ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
rotationAnim.setDuration(300); // 设置动画时长为300毫秒
rotationAnim.setInterpolator(new DecelerateInterpolator()); // 动画减速插值器
rotationAnim.start(); // 启动动画

6.1.3 动画性能的监控与优化

虽然动画能提升用户体验,但如果处理不当,可能会对性能造成影响,尤其是在资源受限的移动设备上。为了避免这种情况,开发者需要监控动画的性能。可以通过以下步骤进行性能优化:

  • 减少视图层级,降低渲染成本。
  • 重用视图和动画,避免不必要的创建和销毁。
  • 优化动画计算,减少计算量大的操作,比如使用 setX setY 代替复杂的 translationX translationY
  • 使用 ViewPropertyAnimator 进行链式调用,优化动画实现。

6.2 性能优化策略

6.2.1 刷新加载机制的性能瓶颈分析

在刷新加载机制中,性能瓶颈通常出现在数据处理和视图渲染上。如果数据量很大,或者处理逻辑过于复杂,那么用户在使用应用时可能会遇到卡顿现象。分析性能瓶颈时,开发者需要关注以下几个方面:

  • CPU和内存使用情况。
  • 数据处理和解析的时间消耗。
  • 视图渲染的时间开销。

6.2.2 优化数据加载与渲染策略

为了优化数据加载和渲染,我们可以采取以下策略:

  • 分批加载数据,避免一次性加载过多数据造成延迟。
  • 使用异步加载机制,比如 AsyncTask HandlerThread ,将耗时的数据处理放在后台线程执行。
  • 渲染数据前进行脏检查,只更新变更的部分。
  • 利用视图缓存来减少布局的重复创建。

6.2.3 异步处理与线程池的应用

在进行复杂的网络请求或者数据处理时,合理利用异步处理和线程池是保证应用流畅运行的关键。例如,我们可以使用 ExecutorService 来管理线程池:

ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(new CallableTask());
executor.shutdown();

6.3 兼容性处理

6.3.1 兼容不同版本Android系统

为了确保我们的刷新加载视图能够兼容不同版本的Android系统,需要遵守以下原则:

  • 避免使用已经废弃的API。
  • 检测运行环境的Android版本,并根据版本差异进行适配。
  • 使用支持库和兼容库来解决API兼容性问题。

6.3.2 兼容不同设备的布局适配

设备的屏幕尺寸和分辨率千差万别,因此,我们需要设计灵活的布局来适应不同的设备。这可以通过使用 wrap_content match_parent 和权重比例等布局参数来实现,同时可以通过 dimens.xml 文件为不同屏幕定义尺寸。

6.3.3 兼容性测试与问题修复

在完成开发后,进行广泛的兼容性测试是必不可少的。这包括:

  • 使用不同版本的Android模拟器进行测试。
  • 使用真实的设备进行实地测试。
  • 使用自动化测试工具,如Appium或Espresso进行持续集成测试。

在测试过程中,如果发现兼容性问题,需要根据具体情况分析原因,并及时修复。修复过程中可能需要调整代码逻辑,优化资源文件,甚至修改布局结构。

通过以上章节的分析与实践,你将能够为用户提供流畅、高效且美观的下拉刷新和上拉加载更多功能。在开发过程中,要不断测试并优化,以确保在各种设备和Android版本上都能提供优秀的用户体验。

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

简介:在Android应用开发中,为了增强用户体验,通常需要实现下拉刷新和上拉加载更多数据的功能。本文将详细介绍如何创建自定义的ListView组件,包括创建自定义刷新视图、集成到ListView中、处理滑动事件、编写刷新和加载更多逻辑、添加动画效果、进行兼容性处理以及优化性能。通过这些步骤,开发者可以创建出一个既美观又高效的用户界面组件。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值