实习杂记(28):PropertyAnimation学习

android中的属性动画,就是对   View对象  在属性上  随着时间变化而 改变产生动画效果,


首先必须要理解是   基于属性的,  是View的属性的,一般都需要有getre和setter方法才可以在这个属性上  作动画


1.View的层级结构和View的相关属性

所以第一步是要知道View的结构,第二步是要理解View 或者自定义View有哪些属性,


首先看层级结构:


然后再看View的相关函数,下面这张图中有些set函数,并不是属性,


其中有些子类的View还有自己相关的set函数,甚至你自己自定义的View也是有自己的属性,下面给出的是ImageView的属性


知道了View有这些属性,就可以在这些属性上  做些动画操作,随着时间的变化,来改变属性值,从而做出动画效果

其中关于属性,在做动画的时候,就是把set函数   前面的set去掉,去掉之后的函数名的第一个字母改成小写的,就是动画的target了

2.动画的要素有哪些?

我这里指的动画的要素是:动画的时间有多长,动画开始的点在哪里,动画结束的点在哪里,动画的对象是谁,动画的模式(缩放,平移等)是什么样的,动画的播放模式(播放几次,播放结束之后做什么)是什么样的,动画接收的数据类型是什么样的,动画的变化率是什么样的,动画的监听等等,


在学习动画之前,你肯定需要对这些要素搞清楚,否则想自己写一个动画那就比较头疼了,


首先是介绍些基础属性:这个我觉得android开发的官网上学习最好了:

SYNTAX:
<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

The file must have a single root element: either <set><objectAnimator>, or <valueAnimator>. You can group animation elements together inside the <set> element, including other <set> elements.

这里面是在android开发者上找到的一份说明:https://developer.android.com/guide/topics/resources/animation-resource.html#obj-animator-element

应该是把动画的基本要素列的很清楚,当然有些东西这个地方肯定看不到,比如说变化率什么的,但是先对上面的基本元素进行说明,

上面的语法:是在XML文件中定义的,在代码中动态写也是这些要素

android:ordering
Keyword. Specifies the play ordering of animations in this set.
Value Description
sequentially Play animations in this set sequentially   代表是依次执行动画
together (default) Play animations in this set at the same time. 代表动画是全部一起执行
这个属性是指  在使用动画集合的时候,也就是对同一个VIEW对象实施多个动画效果的时候才加上这个属性值的,在根节点上加上即可,

android:propertyName
StringRequired. The object's property to animate, referenced by its name. For example you can specify  "alpha" or "backgroundColor" for a View object. The  objectAnimator element does not expose a  target attribute, however, so you cannot set the object to animate in the XML declaration. You have to inflate your animation XML resource by calling loadAnimator() and call  setTarget() to set the target object that contains this property.这个属性就是说的,当前的动画是作用在View对象的哪个属性上面,官网说这个属性是必须的,它举了个例子,透明度 alpha和背景backgroundColor,

后面的英文大致意思是说:对象动画元素并没有给你提供target这个属性,所谓target是指动画作用在哪个view对象上,如果仅仅是在XML文件中定义这个propertyName还不行,还需要你再代码中使用setTarget来进行绑定view.       其中loadAnimator方法只是从xml文件初始化一个对象动画。

总之,一句话,这个属性就是 ” 属性动画“中,很关键的一个东西,“属性动画  ” 中的 “ 属性 ” 指的就是这个东西,例如下面的代码

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.anim.property_animator);
set.setTarget(myObject);
set.start();


android:valueTo
float, int, or colorRequired. The value where the animated property ends. Colors are represented as six digit hexadecimal numbers (for example, #333333).这个要素很好理解,就是动画结束点在哪里,接受三种类型的值,float,int,color,,,实际上远远不止这三个类型,因为你可以自定义  类型,这个后面说


android:valueFrom
float, int, or color. The value where the animated property starts. If not specified, the animation starts at the value obtained by the property's get method. Colors are represented as six digit hexadecimal numbers (for example, #333333).这个要素:指的就是动画的起始点,这个属性有默认值,


android:duration
int. The time in milliseconds of the animation. 300 milliseconds is the default.动画的总时长,以毫秒为单位,这很重要的,,必须的,300 毫秒是默认值,

这个要素有一个东西需要强调:动画的总时长和   时间t变化   有一个变量叫做变化率,fraction ,

fraction = 当前运动时间/ 总运动时间,

这个变量在自定义TypeValue的时候非常重要的


android:startOffset
int. The amount of milliseconds the animation delays after  start() is called.这个是指动画启动,调用start()方法之后,延迟多少毫秒开始,类似一个时间偏移量

android:repeatCount
int. How many times to repeat an animation. Set to  "-1" to infinitely repeat or to a positive integer. For example, a value of  "1" means that the animation is repeated once after the initial run of the animation, so the animation plays a total of two times. The default value is  "0", which means no repetition.这个要素是指动画的播放模式,重复播放动画,

如果设定为  负数,表示 无限重复

如果设定为    0,表示  不重复,整个动画只有一次,

如果设定位   1,表示重复一次,整个动画播放2次,

后面的大于1  的整数,表示重复多少次,整个动画播放   N+1次

android:repeatMode
int. How an animation behaves when it reaches the end of the animation.  android:repeatCount must be set to a positive integer or  "-1" for this attribute to have an effect. Set to  "reverse" to have the animation reverse direction with each iteration or  "repeat" to have the animation loop from the beginning each time.这个要素是指重复播放的时候有一个模式可以设置的,

如果这个起作用,必须要先设置   repeatCount这个要素,而且不能设置为0,

reverse模式,就是动画播放第一次的时候,从头开始,然后重复播放的时候,从结束的地方开始,然后再次从头开始,......依次类推

repeat模式,就是动画每次重复播放的时候都是从头开始的,

android:valueType
Keyword. Do not specify this attribute if the value is a color. The animation framework automatically handles color values
Value Description
intType Specifies that the animated values are integers
floatType (default) Specifies that the animated values are floats

动画的属性接受的  数据类型,这个其实很好理解的,因为动画是作用在属性上的,不同的属性有不同的数据,比如说:透明度就是int或者folat,,背景就是颜色,跳动可能就是坐标Point等,如果是对象类型的 type 一般需要自己定义, new TypeEvaluator<T>


这个里面有句话,如果这个类型是颜色值的话,请不要指明  valueType类型,框架会自行处理,

后面给出的两种value值是不需要自定义的,其他的都需要的,


这些应该就是   属性动画                 最基本的动画要素了,


Tween animation 动画,有自己特定的要素:可以对比下,再去了解前面学习的东西应该就很容易明白了

-------------------------------------------------------下面的仅供对比,不是本文的核心
SYNTAX:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

The file must have a single root element: either an <alpha><scale><translate><rotate>, or <set> element that holds a group (or groups) of other animation elements (even nested <set> elements).



平移类型的动画:

android:interpolator 动画的渲染器 

accelerate_interpolator(动画加速器) 使动画在开始的时候 最慢,然后逐渐加速 

decelerate_interpolator(动画减速器)使动画在开始的时候 最快,然后逐渐减速 

accelerate_decelerate_interpolator(动画加速减速器) 

中间位置分层:  使动画在开始的时候 最慢,然后逐渐加速          

使动画在开始的时候 最快,然后逐渐减速  结束的位置最慢 

fromXDelta  动画起始位置的横坐标 

toXDelta    动画起结束位置的横坐标 

fromYDelta  动画起始位置的纵坐标 

toYDelta   动画结束位置的纵坐标 


缩放动画:

fromXScale:表示沿着x轴缩放的起始比例 

toXScale:表示沿着x轴缩放的结束比例 

fromYScale:表示沿着y轴缩放的起始比例 

toYScale:表示沿着y轴缩放的结束比例 

旋转动画:

fromDegrees:表示旋转的起始角度 

toDegrees:表示旋转的结束角度 

repeatCount:旋转的次数  默认值是0 代表旋转1次  如果值是repeatCount=4 旋转5次,值为-1或者infinite时,表示补间动画永不停止 

repeatMode 设置重复的模式。默认是restart。当repeatCount的值大于0或者为infinite时才有效。

repeatCount=-1 或者infinite循环了  还可以设成reverse,表示偶数次显示动画时会做与动画文件定义的方向相反的方向动行。

透明动画:

fromAlpha :起始透明度 

toAlpha:结束透明度 

1.0表示完全不透明

0.0表示完全透明

---------------------------------------------------------------------------------------------------------------------------------


3.属性动画的基本框架和基本原理

属性动画,这个是在Android 3.0中才引进的,以前学WPF时里面的动画机制好像就是这个,它更改的是对象的实际属性,在View Animation(Tween Animation)中,其改变的是View的绘制效果,真正的View的属性保持不变,比如无论你在对话中如何缩放Button的大小,Button的有效点击区域还是没有应用动画时的区域,其位置与大小都不变。而在Property Animation中,改变的是对象的实际属性,如Button的缩放,Button的位置与大小属性值都改变了。而且Property Animation不止可以应用于View,还可以应用于任何对象。Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。

1.基本框架

public abstract class Animator extends Object implements Cloneable

1.Known Direct Subclasses  直接子类,就是Animator的儿子只有下面这个两个类

  AnimatorSet   This class plays a set of Animator objects in the specified order. 

  ValueAnimator  This class provides a simple timing engine for running animations which calculate animated values and set them on target objects. 

2.Known Indirect Subclasses 间接子类,其实就是孙子,下面的孙子的父亲  是ValueAnimator

  ObjectAnimator  This subclass of ValueAnimator provides support for animating properties on target objects. 

  TimeAnimator  This class provides a simple callback mechanism to listeners that is synchronized with all other animators in the system. 

【其实除了这些动画,还有两个动画,一个LayoutAnimator,这个动画是比较常用的;】

2.属性动画的原理:

  对于下图的动画,这个对象的X坐标在40ms内从0移动到40 pixel.按默认的10ms刷新一次,这个对象会移动4次,每次移动40/4=10pixel。

  也可以改变属性值的改变方法,即设置不同的interpolation,在下图中运动速度先逐渐增大再逐渐减小

  下图显示了与上述动画相关的关键对象

ValueAnimator  表示一个动画,包含动画的开始值,结束值,持续时间等属性。

ValueAnimator封装了一个TimeInterpolator,TimeInterpolator定义了属性值在开始值与结束值之间的插值方法。

ValueAnimator还封装了一个TypeAnimator,根据开始、结束值与TimeIniterpolator计算得到的值计算出属性值。

ValueAnimator根据动画已进行的时间跟动画总时间(duration)的比计算出一个时间因子(0~1),然后根据TimeInterpolator计算出另一个因子,最后TypeAnimator通过这个因子计算出属性值,如上例中10ms时:

首先计算出时间因子,即经过的时间百分比:t=10ms/40ms=0.25

经插值计算(inteplator)后的插值因子:大约为0.15,上述例子中用了AccelerateDecelerateInterpolator,计算公式为(input即为时间因子):

(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;  

最后根据TypeEvaluator计算出在10ms时的属性值:0.15*(40-0)=6pixel。上例中TypeEvaluator为FloatEvaluator,计算方法为 :

public Float evaluate(float fraction, Number startValue, Number endValue) {
    float startFloat = startValue.floatValue();
    return startFloat + fraction * (endValue.floatValue() - startFloat);
}

参数分别为上一步的插值因子,开始值与结束值。

3.属性动画的各自特点:

他们有什么特点,我觉得应该是需要关注的:

  1)AnimatorSet  是属性动画集合,他可以让  几个动画合在一起,作用在某个对象上,如果用XML写,他的根节点是set;如果不是用XML定义,

             那就用ObjectAnimator定义很多个,然后加在一起,可以使用List<ObjectAniamtor>放在一起,也可以使用play().with()这种

             ;链式编程放在一起,但是链式编程只能每次2个动画连在一起,切记不能多个,

 AnimationSet提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放,顺序播放等。

  以下例子同时应用5个动画:

  1. 播放anim1;
  2. 同时播放anim2,anim3,anim4;
  3. 播放anim5。
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(anim1).before(anim2);
bouncer.play(anim2).with(anim3);
bouncer.play(anim2).with(anim4)
bouncer.play(anim5).after(amin2);
animatorSet.start();


  2)ValueAnimator 是数值动画,暂且这么说,他有个特点是  初始化的时候无法指定view对象的属性,需要自己去手动设置,但是也正是由于

             这个特点,使得这个动画变得非常的灵活,他可以实现  多动画的综合作用,类似上面的第一个AnimatorSet的作用

              这个里面的手动设置  哪个属性,是调用  addUpdateListener(new AnimatorUpdateListener(){});这个后面介绍

ValueAnimator包含Property Animation动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。应用Property Animation有两个步聚:

  1. 计算属性值
  2. 根据属性值执行相应的动作,如改变对象的某一属性。

  ValuAnimiator只完成了第一步工作,如果要完成第二步,需要实现ValueAnimator.onUpdateListener接口,这个接口只有一个函数onAnimationUpdate(),在这个函数中会传入ValueAnimator对象做为参数,通过这个ValueAnimator对象的getAnimatedValue()函数可以得到当前的属性值如:

复制代码
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("update", ((Float) animation.getAnimatedValue()).toString());
}
});
animation.setInterpolator(new CycleInterpolator(3));
animation.start();

  3)ObjectAnimator  这个在初始化的是可以指明作用在View对象的哪个属性上,就是前面介绍的 要素 propertyName,他的父类就是上面的

              valueAnimator

 继承自ValueAnimator,要指定一个对象及该对象的一个属性,当属性值计算完成时自动设置为该对象的相应属性,即完成了Property Animation的全部两步操作。实际应用中一般都会用ObjectAnimator来改变某一对象的某一属性,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,应该满足以下条件:

  • 对象应该有一个setter函数:set<PropertyName>(驼峰命名法)
  • 如上面的例子中,像ofFloat之类的工场方法,第一个参数为对象名,第二个为属性名,后面的参数为可变参数,如果values…参数只设置了一个值的话,那么会假定为目的值,属性值的变化范围为当前值到目的值,为了获得当前值,该对象要有相应属性的getter方法:get<PropertyName>
  • 如果有getter方法,其应返回值类型应与相应的setter方法的参数类型一致。

  如果上述条件不满足,则不能用ObjectAnimator,应用ValueAnimator代替。

复制代码
tv=(TextView)findViewById(R.id.textview1);
btn=(Button)findViewById(R.id.button1);
btn.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    ObjectAnimator oa=ObjectAnimator.ofFloat(tv, "alpha", 0f, 1f);
    oa.setDuration(3000);
    oa.start();
  }
});
复制代码

  把一个TextView的透明度在3秒内从0变至1。

  根据应用动画的对象或属性的不同,可能需要在onAnimationUpdate函数中调用invalidate()函数刷新视图。

4.属性动画的相关监听事件:

了解这些之后,接着学习下:动画的某些相关的接口,因为动画肯定有开始,结束等事件, 

1.Animator.AnimatorListener 这个接口是用于监听动画开始,结束,重复,取消四个事件的,

  onAnimationCancel(Animator animation)
  onAnimationEnd(Animator animation)
  onAnimationRepeat(Animator animation)
  onAnimationStart(Animator animation)

注意他是一个接口,implements之后,四个函数都需要override,实际上不需要那么多,只需要几个事件即可,所以android又提供了一个实现这个接口的类

AnimatorListenerAdapter  This adapter class provides empty implementations of the methods from Animator.AnimatorListener

这个类实现了上面的接口,但是方法都是空的,我们只需要重载这个函数,想要那个事件只需要单独处理那件事即可

2.Animator.AnimatorPauseListener  动画暂停的时候监控事件,跟第一个接口一样的作用,只是处理的事件不一样


他也提供了一个接口实现的类,那么就不用把所有的事件都写出来了,

3.ValueAnimator.AnimatorUpdateListener 这个接口是用来在手动设置动画过程中发生的事,


5.相关函数介绍

1.AnimatorInflater   负责XML动画文件的初始化,

public class AnimatorInflater extends Object 

This class is used to instantiate animator XML files into Animator objects.

方法1:static Animator loadAnimator(Context context, int id) 从资源文件中初始化一个动画类

方法2:staticStateListAnimator loadStateListAnimator(Context context, int id) 也是初始化一个动画,但是这个动画有状态,例如选中不选中分别是不同的动画效果

2.基类动画的系列方法:Animator

Public methods

  addListener(Animator.AnimatorListener listener)

Adds a listener to the set of listeners that are sent events through the life of an animation, such as start, repeat, and end.

  addPauseListener(Animator.AnimatorPauseListener listener)

Adds a pause listener to this animator.

  cancel()

Cancels the animation.

  clone()

Creates and returns a copy of this object.

  end()

Ends the animation.

  getDuration()

Gets the duration of the animation.

  getInterpolator()

Returns the timing interpolator that this animation uses.

  getListeners()

Gets the set of Animator.AnimatorListener objects that are currently listening for events on thisAnimator object.

  getStartDelay()

The amount of time, in milliseconds, to delay processing the animation after start() is called.

  getTotalDuration()

Gets the total duration of the animation, accounting for animation sequences, start delay, and repeating.

  isPaused()

Returns whether this animator is currently in a paused state.

  isRunning()

Returns whether this Animator is currently running (having been started and gone past any initial startDelay period and not yet ended).

  isStarted()

Returns whether this Animator has been started and not yet ended.

  pause()

Pauses a running animation.

  removeAllListeners()

Removes all listeners and pauseListeners from this object.

  removeListener(Animator.AnimatorListener listener)

Removes a listener from the set listening to this animation.

  removePauseListener(Animator.AnimatorPauseListener listener)

Removes a pause listener from the set listening to this animation.

  resume()

Resumes a paused animation, causing the animator to pick up where it left off when it was paused.

  setDuration(long duration)

Sets the duration of the animation.

  setInterpolator(TimeInterpolator value)

The time interpolator used in calculating the elapsed fraction of the animation.

  setStartDelay(long startDelay)

The amount of time, in milliseconds, to delay processing the animation after start() is called.

  setTarget(Object target)

Sets the target object whose property will be animated by this animation.

  setupEndValues()

This method tells the object to use appropriate information to extract ending values for the animation.

  setupStartValues()

This method tells the object to use appropriate information to extract starting values for the animation.

  start()

Starts this animation.

接下来很重要的是ObjectAnimator的方法,of系列的方法:

of后面接的是什么东西,代表typeValue就是什么类型,同时属性变化的也就是这个值


这些方法大家还是去官网看吧,不复杂,也不多,https://developer.android.com/reference/android/animation/Animator.html

6.TypeValue  

动画接受的类型 应该叫作用在属性上数值类型


他是一个泛型的,其实这里举个例子就容易明白了:

        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setDuration(3000);
        valueAnimator.setObjectValues(new PointF(0, 0));//动画接受的数值类型,抛物线里面接受的是点坐标
        valueAnimator.setInterpolator(new LinearInterpolator());//变化率,线性的
        valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {//自定义TypeValue
            // fraction = t / duration
            @Override
            public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
                Log.e("TAG", fraction * 3 + "");
                // x方向200px/s ,则y方向0.5 * 10 * t
                PointF point = new PointF();
                point.x = 200 * fraction * 3;
                point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3);
                return point;
            }
        });


valueAnimator.setObjectValues(new PointF(0, 0));
valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
这两行代码是相互对应的,PointF指的是坐标中X是int类型,Y是float类型

https://developer.android.com/reference/android/animation/TypeEvaluator.html

这个里面的原理到底是怎样的:我觉得网上有个图可以借用下:

下面是官方文档提供的一张原理图:

从图中可以看到ValueAnimator类封装了:

一个TimeInerpolator,

一个TypeEvaluator,

动画执行的时间duration,

属性的起始值startPropertyValue,属性终止值endPropertyValue。

当我们使用属性动画就需要提供这些信息,可以是系统提供的,我们也可以自己实现,那么当一个ValueAnimator设置了这些属性,它门是如何协同工作的呢?

首先系统根据已经流失的时间和持续时间计算出一个elapsed fraction,将它传递给时间插值器的getInterpolation(float input)方法,该方法对输入值进行映射到0-1之间的interpolated fraction,接着该值被传递给Evaluator的evaluate(float fraction, Point start, Point end)方法,又该方法返回本次计算的属性值,可以在AnimatorUpdateListener的onAnimationUpdate(ValueAnimator anition)方法的参数animation的getAnimatedValue()方法获得最新的属性,然后重新给对象设置该属性,完成对对象属性的修改。

7.PropertyValuesHolder

这个东西也是实现多个动画作用在一个view对象上,举个例子:

     public void propertyValuesHolder(View view)
    {
        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
        PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
        ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY,pvhZ).setDuration(1000).start();
    } 




动画到这里基本就学完了,具体的操作,我觉得网上有个鸿洋大神的写得蛮清楚,照着走一次基本就清楚了,

参考:http://blog.csdn.net/lmj623565791/article/details/38067475/

参考:http://blog.csdn.net/jiabailong/article/details/14168707

参考:http://www.cnblogs.com/gatsbydhn/p/5078473.html


github代码下载:https://github.com/wustnlp/animation












































  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值