Android上下滑动引导页的实现:手势和SurfaceView技术

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

简介:在Android开发中,创建一个上下滑动的引导页用于展示应用程序的主要功能或特性是非常常见的。本文深入探讨了如何实现一个上下切换页面的引导页,包括自定义布局管理器以处理页面的滑动和切换逻辑,以及结合手势判断和SurfaceView技术的实现细节。我们将通过自定义ViewPager和SurfaceView,并结合动画和性能优化策略,创建一个具有交互性和视觉吸引力的引导体验。 android上下滑动引导页 上下切换页面 手势判断 SurfaceView实现

1. 上下滑动引导页的基本结构和实现

在现代移动应用开发中,引导页是用户首次打开应用时介绍应用特点和功能的重要界面。引导页通常具有上下滑动的交互特性,用户可以通过滑动来查看不同的引导信息页。本章将探讨实现上下滑动引导页的基本结构和关键点。

引导页的基本结构包括背景图、标题文本、描述文本和一个指示器,指示器显示当前页的位置和总页数。实现引导页需要处理以下方面:

  • 布局结构 :使用XML布局文件定义各个元素的位置、大小和样式。
  • 活动处理 :在Activity中初始化引导页的数据和视图。
  • 交互逻辑 :编写逻辑代码处理用户的滑动操作,以及根据用户的操作切换不同页面。

实现引导页通常涉及的类包括Activity和可能的自定义View。在Android开发中,一个简单的方法是利用ViewPager控件,它已经为我们提供了滑动切换视图的基本功能。然而,为了实现更好的交互效果和性能优化,我们可能需要自定义ViewPager或者在ViewPager的基础上添加监听和处理逻辑。

接下来的章节将详细介绍布局设计与触摸事件处理、手势判断逻辑的实现、SurfaceView的应用优化、页面切换效果与动画控制,以及性能优化与分页指示器的实现,逐步深入引导页的实现细节。

2. 布局设计与触摸事件处理

布局设计是引导页用户体验的关键因素之一,它需要直观、美观且功能性强。在引导页设计中,触摸事件处理是一个核心部分,它需要准确地响应用户的操作,提供流畅的交互体验。本章将深入探讨自定义ViewPager子类的创建以及如何重写onTouchEvent方法来优化触摸事件的处理。

2.1 自定义ViewPager子类

2.1.1 创建自定义ViewPager类继承关系

在Android开发中,ViewPager是一种常用的滑动布局组件,它允许用户水平滑动切换不同的视图页面。自定义ViewPager子类能够让我们更精细地控制滑动行为和动画效果。

首先,创建一个新的Java类,继承自 ViewPager 类。接下来,我们可以添加一些自定义的属性,比如滑动的摩擦系数、滑动速度等,以便于后续的自定义处理。代码示例如下:

public class CustomViewPager extends ViewPager {
    public CustomViewPager(Context context) {
        super(context);
        // 初始化代码
    }

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 属性解析代码
    }

    // 更多自定义属性和方法
}

2.1.2 重写onTouchEvent方法的必要性和作用

在自定义ViewPager子类中,重写 onTouchEvent 方法是十分必要的,因为这允许我们接管触摸事件的分发和处理。这样我们可以根据用户的滑动行为动态调整滑动阻力,实现更符合预期的滑动体验。重写 onTouchEvent 方法还可以帮助我们捕获滑动的方向,以便于实现特定的页面切换逻辑。

2.2 重写onTouchEvent()方法

2.2.1 触摸事件分发机制概述

在Android系统中,触摸事件的分发遵循一个从父视图到子视图的传递路径。当用户触摸屏幕时,系统会生成一个 MotionEvent ,然后这个事件会首先传递给最顶层的视图。如果顶层视图不处理该事件(即返回 false ),则事件会继续向下传递给父视图,这一过程会一直持续,直到事件被处理或传递到视图树的底部。

2.2.2 onInterceptTouchEvent与onTouchEvent的协作

onInterceptTouchEvent 方法中,父视图可以决定是否拦截触摸事件,以便自己处理。如果决定拦截,那么事件将不会传递给子视图,而是直接调用父视图的 onTouchEvent 进行处理。 onInterceptTouchEvent 通常在 onTouchEvent 之前调用,用于控制事件的分发路径。

在自定义ViewPager中,我们可能需要在某些情况下拦截触摸事件,以改变默认的滑动行为。例如,当用户尝试水平滑动时,我们可能会拦截这些事件,以避免错误地触发布局的垂直滚动。

2.2.3 重写onTouchEvent的示例代码

通过重写 onTouchEvent 方法,我们可以自定义触摸事件的处理逻辑。以下是一个简化的示例代码,展示了如何根据用户滑动方向和速度动态调整ViewPager的滑动效果。

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 记录按下时的位置
            break;
        case MotionEvent.ACTION_MOVE:
            // 处理移动时的滑动阻力变化
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            // 根据滑动速度和方向决定是否需要自动滑动
            break;
    }
    return super.onTouchEvent(event);
}

在该方法中,我们需要根据不同的触摸事件类型进行不同的处理。当检测到手指按下时,我们可以记录下初始位置;在手指移动时,我们可以计算滑动的速度和方向,并据此调整滑动阻力;在手指抬起时,我们可以根据记录的数据来决定是否需要自动滑动到下一个页面。

此外,我们还可以添加一些阈值判断,比如滑动距离必须超过一定长度才视为有效的滑动操作,以及判断滑动速度来决定是快速滑动还是缓慢滑动,从而提供更加流畅和准确的用户体验。

3. 手势判断逻辑的实现与解析

手势控制是实现滑动引导页互动性的核心,良好的手势响应和正确的滑动方向判断能够显著提升用户的体验。本章将深入探讨手势判断逻辑的实现,解释其背后的原理,并提供实际代码示例。

3.1 捕获触摸事件

要实现手势判断,首先需要捕获触摸事件。在Android中,触摸事件主要由 MotionEvent 类提供,而这些事件的捕获则发生在 View 的触摸事件监听器中。

3.1.1 触摸事件类型的介绍

MotionEvent 类可以报告多种触摸事件类型,包括以下几种:

  • ACTION_DOWN :手指触摸屏幕时触发。
  • ACTION_MOVE :手指在屏幕上移动时触发。
  • ACTION_UP :手指从屏幕上抬起时触发。
  • ACTION_CANCEL :事件由于某种原因被取消时触发。

理解这些事件类型对于实现精确的手势控制至关重要。

3.1.2 如何监听触摸事件的发生

可以通过在自定义的 ViewPager 类中重写 onTouchEvent() 方法来监听这些事件,代码示例如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int action = event.getActionMasked();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            // 捕获按下事件
            break;
        case MotionEvent.ACTION_MOVE:
            // 捕获移动事件
            break;
        case MotionEvent.ACTION_UP:
            // 捕获抬起事件
            break;
    }
    return super.onTouchEvent(event);
}

此代码段通过 MotionEvent getActionMasked() 方法获取处理过的事件类型,以过滤掉重复的事件。接下来将详细解析如何解析手势和滑动方向。

3.2 解析手势与滑动方向

实现滑动引导页的一个关键挑战是如何判断用户的滑动方向,以便在上滑或下滑时触发相应的页面切换。

3.2.1 上滑和下滑手势的基本判定逻辑

滑动方向的判断通常涉及到 onInterceptTouchEvent 方法,此方法可以在触摸事件传递给子视图之前由父视图拦截:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            // 初始化一些参数,例如mLastX和mLastY
            break;
        case MotionEvent.ACTION_MOVE:
            // 计算滑动的距离和方向
            float xDiff = ev.getX() - mLastX;
            float yDiff = ev.getY() - mLastY;
            if (Math.abs(xDiff) > Math.abs(yDiff)) {
                // 如果水平滑动距离大于垂直滑动距离,则视为水平滑动
            } else {
                // 否则视为垂直滑动
                if (yDiff > 0) {
                    // 认为是向下滑动
                } else {
                    // 认为是向上滑动
                }
            }
            break;
    }
    return super.onInterceptTouchEvent(ev);
}

3.2.2 利用MotionEvent类的API进行手势解析

MotionEvent 类中提供的方法可以帮助我们获取手指在屏幕上的具体位置:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int action = event.getAction();
    switch (action) {
        case MotionEvent.ACTION_MOVE:
            float currentX = event.getX();
            float currentY = event.getY();
            // 通过currentX和currentY计算手指移动距离
            break;
    }
    return true; // 返回true表示事件已被消费
}

3.2.3 处理连续滑动的挑战

连续滑动的处理较为复杂,因为需要判断用户是想继续滑动还是已经完成了滑动操作。我们可以通过设置一个时间阈值来解决这个问题,当连续两次滑动动作间隔超过此阈值时,我们认为用户的滑动操作已经完成。

实际应用中,手势处理与判断逻辑的实现需要考虑多种因素,如屏幕尺寸、滑动速度、用户的滑动习惯等,这都需要在实际编码过程中不断调整和优化。

以上是本章对手势判断逻辑实现与解析的详细介绍,下一章将进入另一个关键内容,解析SurfaceView在引导页中的应用与优化。

4. SurfaceView在引导页中的应用与优化

随着移动设备性能的提升,用户对应用中图形渲染效果的期望也在不断提高。在引导页中,高质量的图形渲染不仅可以吸引用户,还能提供流畅的用户体验。SurfaceView作为Android系统中用于高性能图形渲染的组件,因其在独立线程中渲染而不会阻塞主线程,特别适用于引导页的场景。本章节将深入探讨SurfaceView的基础知识、高性能图形渲染的实现以及其生命周期与销毁逻辑,帮助开发者更好地在引导页中使用并优化SurfaceView。

4.1 SurfaceView的基础知识

4.1.1 SurfaceView与View的区别

在Android应用开发中,View是所有UI组件的基类,它负责处理绘制和触摸事件。而SurfaceView是一种特殊的View,它提供了一种可以进行独立于UI线程的绘图操作的方式。当涉及到复杂的图形渲染操作时,如视频播放或3D动画,SurfaceView相比常规的View具有性能上的优势。

主要区别可以概括为:

  • 线程模型 :常规的View在主线程(UI线程)中绘制,而SurfaceView则通过另一个独立线程进行绘制,这可以避免因复杂图形计算导致的UI线程阻塞。
  • 绘制机制 :View的绘制是同步的,绘制指令会被加入到UI线程的队列中按顺序执行。SurfaceView的绘制是异步的,不需要等待其他UI操作。
  • 视图层次 :View作为视图层次的一部分,显示在当前窗口之上。而SurfaceView则创建了一个单独的窗口,显示在所有View之上。

4.1.2 SurfaceView的使用场景

SurfaceView最适合的使用场景是对实时渲染要求较高的应用,如动态图形、视频播放和动画。它支持多个生产者(如相机预览、视频播放器)与单个消费者(用户界面)之间的并发访问,这在常规View中是不可能实现的。

以下是SurfaceView的一些典型使用场景:

  • 视频播放 :视频播放需要实时解码并渲染大量图像数据,使用SurfaceView可以保证视频播放的流畅性。
  • 游戏开发 :游戏通常需要复杂的图形渲染和实时的用户交互,SurfaceView能够提供更加平滑的游戏体验。
  • 实时数据可视化 :对于需要实时反映数据变化的图表,如股票行情,使用SurfaceView可以有效降低UI的延迟。

4.2 高性能图形渲染的实现

4.2.1 SurfaceView的初始化与大小变化处理

在使用SurfaceView之前,需要进行初始化,并处理其大小变化。以下是初始化SurfaceView的典型步骤:

  1. 创建自定义的SurfaceView类并重写相关方法。
  2. 在Activity中初始化SurfaceView,并设置其LayoutParams以确定大小。
  3. 将SurfaceHolder.Callback添加到SurfaceView中,以便监听Surface的创建和销毁。
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder surfaceHolder;

    public MySurfaceView(Context context) {
        super(context);
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // Surface创建后的初始化操作
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // Surface大小变化时的操作
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // Surface销毁前的操作
    }
}

当设备的配置发生变化(如屏幕方向改变)时,SurfaceView的大小可能会变化,这时需要重新计算绘制的参数。

4.2.2 SurfaceView的线程模型与渲染循环

为了在SurfaceView中实现高质量的图形渲染,需要通过线程模型来控制渲染循环。关键点在于:

  • 创建工作线程 :用于处理图形渲染的逻辑。
  • 同步机制 :保证主线程与工作线程之间的同步,避免竞态条件。
  • 渲染循环 :工作线程中持续运行的循环,用于定期更新图形内容。

下面是一个简单的渲染循环实现示例:

public class RendererThread extends Thread {
    private SurfaceHolder surfaceHolder;
    private boolean running = true;

    public RendererThread(SurfaceHolder surfaceHolder) {
        this.surfaceHolder = surfaceHolder;
    }

    @Override
    public void run() {
        while (running) {
            Canvas canvas = null;
            try {
                canvas = surfaceHolder.lockCanvas();
                synchronized (surfaceHolder) {
                    // 执行绘制操作
                    // ...
                }
            } catch (Exception e) {
                // 处理异常
                // ...
            } finally {
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }

    public void setRunning(boolean running) {
        this.running = running;
    }
}

这里需要注意的是 lockCanvas() 方法会锁定Surface,阻止其他线程同时访问,而 unlockCanvasAndPost() 方法则会解锁并发布新的Canvas,让其他线程可以进行绘制操作。

4.3 SurfaceView的生命周期与销毁逻辑

4.3.1 SurfaceView的生命周期管理

SurfaceView的生命周期管理需要同时关注Activity和SurfaceView本身的生命周期。为了确保资源被正确释放,在Activity的生命周期回调中(如 onPause() onStop() )需要通知SurfaceView停止渲染并释放资源。

@Override
public void onPause() {
    super.onPause();
    if (rendererThread != null) {
        rendererThread.setRunning(false);
        try {
            rendererThread.join();
        } catch (InterruptedException e) {
            // 异常处理
            // ...
        }
    }
}

4.3.2 SurfaceView在Activity销毁时的处理

当Activity被销毁时,必须确保SurfaceView得到恰当的处理。具体来说,需要在 onDestroy() 中停止工作线程并清除资源。

@Override
public void onDestroy() {
    super.onDestroy();
    if (rendererThread != null) {
        rendererThread.interrupt();
        rendererThread = null;
    }
    // 其他清理操作
}

在此阶段,还需要关注SurfaceView中引用的资源,如Bitmap、Drawable等,确保它们也被正确释放。这样可以避免内存泄漏和其他潜在问题。

通过以上各节的分析,我们可以看出SurfaceView在引导页中实现高性能图形渲染时的适用性和优势。开发者可以通过理解并应用本章节介绍的知识点,更好地掌握SurfaceView的使用和优化,最终提供流畅、高质量的用户体验。

5. 页面切换效果与动画控制

页面切换效果与动画控制是引导页用户体验的关键组成部分,它能够使页面转换流畅自然,增加用户对产品的兴趣。在本章中,我们将深入探讨动画属性的控制,以及如何利用现有的动画类来实现复杂的页面切换效果。

5.1 动画属性变化的控制

5.1.1 动画属性概述

在Android中,动画主要分为三种类型:Tween动画、Frame动画和Property动画。Tween动画和Frame动画主要用于简单的动画效果,而Property动画则是从Android 3.0(Honeycomb)开始引入的一种更为强大和灵活的动画实现方式。它允许我们对几乎所有的对象属性进行动画处理,并且可以对属性进行监听,从而在动画执行过程中修改其他属性。

5.1.2 使用ObjectAnimator控制动画属性

ObjectAnimator是Property动画中最为常用的类之一,它可以通过改变对象的属性来实现动画效果。其基本用法是通过创建ObjectAnimator实例,指定要操作的对象以及属性名,然后就可以控制该对象在动画过程中的属性变化。

下面是一个使用ObjectAnimator实现简单动画的示例代码:

ObjectAnimator animation = ObjectAnimator.ofFloat(view, "translationX", 0f, 300f);
animation.setDuration(1000); // 设置动画持续时间为1000毫秒
animation.start(); // 启动动画

在上述代码中,我们创建了一个ObjectAnimator对象,并指定了要动画的对象(view)以及要改变的属性(translationX)。该动画会将view的水平位置从0f移动到300f,持续时间为1000毫秒。

ObjectAnimator不仅限于基本的属性,它还可以操作自定义属性。这是通过PropertyValuesHolder类来实现的,可以同时对多个属性进行动画处理。

5.2 利用ValueAnimator实现平滑过渡

5.2.1 ValueAnimator与ObjectAnimator的比较

ValueAnimator与ObjectAnimator在核心上是相同的,它们都属于Property动画框架的一部分。不同之处在于,ValueAnimator并不直接与任何对象属性关联,而是通过监听动画过程中的值变化来手动更新界面。因此,它提供了一种更为灵活的方式来实现复杂的动画效果。

5.2.2 实现页面切换动画的详细步骤

当需要实现页面切换动画时,我们可以使用ValueAnimator来逐步改变页面的位置、透明度等属性,从而达到平滑过渡的效果。以下是一个简单的示例,展示了如何使用ValueAnimator来实现一个页面从右滑入的动画效果。

ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.setDuration(300);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        // 假设有一个名为pageView的View,我们通过改变其缩放和透明度来模拟滑动效果
        pageView.setScaleX(value);
        pageView.setScaleY(value);
        pageView.setAlpha(value);
    }
});
animator.start();

在这个例子中,我们创建了一个从1.0f到0.0f的ValueAnimator,当动画更新时,我们根据当前的动画值来调整pageView的缩放和透明度,从而实现一个逐渐消失的滑动效果。

请注意,对于复杂的页面切换效果,如翻页效果,通常需要结合多个动画同时进行。在这样的情况下,我们可能需要使用AnimatorSet来组合多个动画,并控制它们的播放顺序以及是否同步播放。

最终,我们将通过将动画与ViewPager结合,根据用户的滑动动作来触发动画,从而实现一套自然流畅的页面切换动画效果。

6. 引导页性能优化与分页指示器

在移动应用中,引导页往往是一个吸引用户的第一印象窗口,其性能表现和用户体验对整个应用的成功至关重要。本章节将深入探讨引导页的性能优化策略,包括异步加载图片以减少阻塞,以及如何实现一个高效且美观的RecyclerView分页指示器。

6.1 异步加载图片的策略

图片资源通常占据较大的内存和存储空间,如果在主线程中直接加载,将严重影响引导页的滑动流畅度,甚至出现卡顿现象。因此,实现图片的异步加载是引导页性能优化的首要任务。

6.1.1 图片异步加载的意义与方法

异步加载图片可以将图片加载操作放到后台线程中执行,避免阻塞主线程,提高应用响应速度。在Android中,常用的图片异步加载库包括Glide和Picasso等,它们不仅提供了简便的API,还内置了缓存机制,能够有效提升图片加载效率。

6.1.2 实现图片加载缓存机制的策略

图片加载缓存机制可以减少网络请求次数,提高图片加载速度,降低数据流量消耗。Glide和Picasso都内置了强大的缓存策略,如内存缓存和磁盘缓存,我们可以通过配置来优化这些策略。以下是一个使用Glide进行图片异步加载和缓存处理的示例代码:

// 使用Glide加载图片并应用缓存策略
Glide.with(context)
    .load(imageUrl)
    .placeholder(R.drawable.placeholder) // 占位图
    .diskCacheStrategy(DiskCacheStrategy.ALL) // 设置缓存策略
    .into(imageView);

6.2 RecyclerView分页指示器的实现

在多页的引导页中,分页指示器对于用户理解当前页位置和导航至关重要。而RecyclerView作为Android中非常强大的列表组件,其自定义和扩展功能非常丰富,非常适合用来实现复杂的分页指示器。

6.2.1 RecyclerView与ItemDecoration的使用

RecyclerView.ItemDecoration 是一个可以让我们自定义添加装饰到 RecyclerView 的子项间的工具。通过继承 ItemDecoration 类并重写 getItemOffsets 方法,我们可以给分页指示器添加空间。以下是一个简单的分页指示器项装饰的实现示例:

public class DotIndicatorDecoration extends RecyclerView.ItemDecoration {
    private final int mPadding;

    public DotIndicatorDecoration(int padding) {
        mPadding = padding;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.right = mPadding;
    }
}

6.2.2 分页指示器的动画效果实现

动画效果可以提升用户体验,使分页指示器看起来更加自然流畅。我们可以使用 RecyclerView.OnScrollListener 结合 ObjectAnimator 来实现指示器的动画效果。在监听器的 onScrolled 方法中,我们可以更新指示器的位置并触发动画:

private void updateIndicator(int position) {
    // 动画更新指示器位置的代码
    ObjectAnimator animator = ObjectAnimator.ofFloat(indicator, "translationX", indicator.getX(), position * indicatorWidth);
    animator.setDuration(300);
    animator.start();
}

// RecyclerView的滚动监听
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        int position = layoutManager.findFirstVisibleItemPosition();
        updateIndicator(position);
    }
});

6.2.3 指示器与页面切换效果的联动实现

为了使分页指示器与页面切换动画联动,我们可以在页面切换动画结束后,调用更新分页指示器位置的方法。通常这可以在 AnimatorListener onAnimationEnd 方法中实现。这样,用户就可以直观地看到当前页面与分页指示器之间的关联,提高了用户体验。

ValueAnimator animator = ... // 页面切换动画的实现
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        // 更新分页指示器位置
        int position = ...; // 新的页面位置
        updateIndicator(position);
    }
});
animator.start();

通过上述策略,我们可以实现一个既高效又美观的引导页,不仅提升了用户体验,也保证了应用的性能表现。

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

简介:在Android开发中,创建一个上下滑动的引导页用于展示应用程序的主要功能或特性是非常常见的。本文深入探讨了如何实现一个上下切换页面的引导页,包括自定义布局管理器以处理页面的滑动和切换逻辑,以及结合手势判断和SurfaceView技术的实现细节。我们将通过自定义ViewPager和SurfaceView,并结合动画和性能优化策略,创建一个具有交互性和视觉吸引力的引导体验。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值