Android属性动画 Property animation

转载于:http://www.open-open.com/lib/view/open1329994048671.html

http://blog.sina.com.cn/s/blog_5da93c8f0102uxio.html

http://blog.sina.com.cn/s/blog_b991f82a0101gqa3.html

3.0以前,android支持两种动画模式,tween animation,frame animation,在android3.0中又引入了一个新的动画系统:property animation,这三种动画模式在SDK中被称为property animation,view animation,drawable animation。

1. View Animation(Tween Animation)

    View Animation(Tween Animation):补间动画,给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。
 View animation只能应用于View对象,而且只支持一部分属性,如支持缩放旋转而不支持背景颜色的改变。
 对于View animation,它只是改变了View对象绘制的位置,而没有改变View对象本身,比如,你有一个Button,坐标(100,100),Width:200,Height:50,而你有一个动画使其变为Width:100,Height:100,你会发现动画过程中触发按钮点击的区域仍是(100,100)-(300,150)。
 View Animation就是一系列View形状的变换,如大小的缩放,透明度的改变,位置的改变,动画的定义既可以用代码定义也可以用XML定义,当然,建议用XML定义
 可以给一个View同时设置多个动画,比如从透明至不透明的淡入效果,与从小到大的放大效果,这些动画可以同时进行,也可以在一个完成之后开始另一个。
 用XML定义的动画放在/res/anim/文件夹内,XML文件的根元素可以为<alpha>,<scale>,<translate>,<rotate>,interpolator元素或<set>(表示以上几个动画的集合,set可以嵌套)。默认情况下,所有动画是同时进行的,可以通过startOffset属性设置各个动画的开始偏移(开始时间)来达到动画顺序播放的效果。
 可以通过设置interpolator属性改变动画渐变的方式,如AccelerateInterpolator,开始时慢,然后逐渐加快。默认为AccelerateDecelerateInterpolator。
 定义好动画的XML文件后,可以通过类似下面的代码对指定View应用动画。

ImageView spaceshipImage = (ImageView)findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation=AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);

2. Drawable Animation(Frame Animation)

    Drawable Animation(Frame Animation):帧动画,就像GIF图片,通过一系列Drawable依次显示来模拟动画的效果。在XML中的定义方式如下:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
必须以<animation-list>为根元素,以<item>表示要轮换显示的图片,duration属性表示各项显示的时间。XML文件要放在/res/drawable/目录下。示例:
protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub         super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        imageView = (ImageView) findViewById(R.id.imageView1);
        imageView.setBackgroundResource(R.drawable.drawable_anim);
        anim = (AnimationDrawable) imageView.getBackground();
    }

    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            anim.stop();
            anim.start();
            return true;
        }
        return super.onTouchEvent(event);
    }
在实验中遇到的几点问题:
1)  要在代码中调用Imageview的setBackgroundResource方法,如果直接在XML布局文件中设置其src属性当触发动画时会强制停止运行。
2)  在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次。
3)  最后一点是SDK中提到的,不要在onCreate中调用start,因为AnimationDrawable还没有完全跟Window相关联,如果想要界面显示时就开始动画的话,可以在onWindowFoucsChanged()中调用start()。

3. Property Animation

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

3.1 Property Animation的工作方式

      在Property Animation中,可以对动画应用以下属性:
      * Duration:动画的持续时间
      TimeInterpolation:属性值的计算方式,如先快后慢
      TypeEvaluator:根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值
      Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放。
      Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
      Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响。

    一个简单示例说明():

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


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


3.2 ValueAnimator

      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);
    }
参数分别为上一步的插值因子,开始值与结束值。

      应用Property Animation有两个步聚:
      1)  计算属性值
      2)  根据属性值执行相应的动作,如改变对象的某一属性。

     ValuAnimiator只完成了第一步工作,如果要完成第二步,需要实现ValueAnimator.onUpdateListener接口。

     ValueAnimator.AnimatorUpdateListener  

onAnimationUpdate()  //通过监听这个事件在属性的值更新时执行相应的操作,对于ValueAnimator一般要监听此事件执行相应的动作
    示例代码:   
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();

    还给Animator添加一个AnimatorListener,让它能够对Animator的几个关键点进行响应,分别是开始,结束,重复,取消时的响应函数。

    Animator.AnimatorListener  

onAnimationStart()

onAnimationEnd()

onAnimationRepeat()

onAnimationCancel
    AnimatorListener中的4个函数都必须有实现,可以继承AnimatorListenerAdapter而不是实现AnimatorListener接口来简化操作,这个类对AnimatorListener中的函数都定义了一个空函数体,这样我们就只用定义想监听的事件而不用实现每个函数却只定义一空函数体。
ObjectAnimator oa=ObjectAnimator.ofFloat(tv, "alpha", 0f, 1f);
oa.setDuration(3000);
oa.addListener(new AnimatorListenerAdapter(){
    public void on AnimationEnd(Animator animation){
        Log.i("Animation","end");
    }
});
oa.start();
3.3 TypeEvalutors3.5 TypeEvalutors

          根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值,android提供了以下几个evalutor:
      * IntEvaluator:属性的值类型为int;
      * FloatEvaluator:属性的值类型为float;
      * ArgbEvaluator:属性的值类型为十六进制颜色值;
      * TypeEvaluator:一个接口,可以通过实现该接口自定义Evaluator。
   自定义TypeEvalutor很简单,只需要实现一个方法,如FloatEvalutor的定义:
    

public class FloatEvaluator implements TypeEvaluator {
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
    }
}
根据动画执行的时间跟应用的Interplator,会计算出一个0~1之间的因子,即evalute函数中的fraction参数,通过上述FloatEvaluator应该很好看出其意思。

3.4 TimeInterplator
      TimeInterplator定义了属性值变化的方式,如线性均匀改变,开始慢然后逐渐快等。在Property Animation中是TimeInterplator,在View Animation中是Interplator,这两个是一样的,在3.0之前只有Interplator,3.0之后实现代码转移至了TimeInterplator。Interplator继承自TimeInterplator,内部没有任何其他代码。
      AccelerateInterpolator,加速,开始时慢中间加速。
      DecelerateInterpolator,减速,开始时快然后减速。
      AccelerateDecelerateInterolator,先加速后减速,开始结束时慢,中间加速。
      AnticipateInterpolator,反向 ,先向相反方向改变一段再加速播放。
      AnticipateOvershootInterpolator,反向加超越,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值。
      BounceInterpolator,跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100。
      CycleIinterpolator,循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2 * mCycles * Math.PI * input)。
      LinearInterpolator,线性,线性均匀改变。
      OvershottInterpolator,超越,最后超出目的值然后缓慢改变到目的值。
      TimeInterpolator,一个接口,允许你自定义interpolator,以上几个都是实现了这个接口。

3.5 ObjectAnimator 

      ObjectAnimator继承自ValueAnimator,要指定一个对象及该对象的一个属性,当属性值计算完成时自动设置为该对象的相应属性,即完成了Property Animation的全部两步操作。实际应用中一般都会用ObjectAnimator来改变某一对象的某一属性,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,应该满足以下条件:
      * 对象应该有一个setter函数:set<PropertyName>(驼峰命名法)。
      * 对象要有相应属性的getter方法:get<PropertyName>。

        ObjectAnimator类中有如下2个静态方法:       

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)

public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);

第一个参数为对象名,第二个为属性名,后面的参数为可变参数,如果values…参数只设置了一个值的话,那么会假定为目的值,属性值的变化范围为当前值到目的值,因此为了获得当前值,该对象要有相应属性的getter方法。
      * 如果有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()函数刷新视图。

3.6 Animating Views

     在View Animation中,对View应用Animation并没有改变View的属性,动画的实现是通过其Parent View实现的,在View被drawn时Parents View改变它的绘制参数,draw后再改变参数invalidate,这样虽然View的大小或旋转角度等改变了,但View的实际属性没变,所以有效区域还是应用动画之前的区域,比如你把一按钮放大两倍,但还是放大这前的区域可以触发点击事件。为了改变这一点,在Android 3.0中给View增加了一些参数并对这些参数增加了相应的getter/setter函数(ObjectAnimator要用这些函数改变这些属性):
    1)  translationX,translationY:转换坐标,这两个属性控制了View所处的位置,它们的值是由layout容器设置的,是相对于坐标原点(0,0左上角)的一个偏移量。
    2)  rotation,rotationX,rotationY:旋转,rotation用于2D旋转角度,3D中用到后两个。
    3)  scaleX,scaleY:缩放,控制View基于pivotX和pivotY的缩放
    4)  x,y:描述了view在其父容器中的最终位置,是左上角左标和偏移量(translationX,translationY)的和。
    5)  alpha:透明度,1是完全不透明,0是完全透明。

3.7 通过AnimationSet应用多个动画

      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();
3.8 PropertyValuesHolder和ViewPropertyAnimator

     如果需要对一个View的多个属性进行动画,除了AnimatorSet,还可以用PropertyValuesHolder和ViewPropertyAnimator类,ViewPropertyAnimator类对多属性动画进行了优化,会合并一些invalidate()来减少刷新视图。

使用PropertyValuesHolder :   

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
使用 ViewPropertyAnimator:
myView.animate().x(50f).y(100f);
两种方式实现的动画效果一样,后面myView.animate() 方法返回的是一个ViewPropertyAnimator对象,也可以为这个对象添加Listener。

Property Animation也可以在XML中定义
<set> - AnimatorSet
<animator> - ValueAnimator
<objectAnimator> - ObjectAnimator
XML文件应放大/res/animator/中,通过以下方式应用动画:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.anim.property_animator);
set.setTarget(myObject);
set.start();
3.9 当Layout改变时应用动画

      Property animation系统还提供了对ViewGroup中的View改变加入动画的功能。可以使用 LayoutTransition 对ViewGroup中的View改变进行动画显示。这里所说的动画效果都是设置给容器(ViewGroup),然而效果是通过容器存放的View来体现的。

      当添加或者移除ViewGroup中的View时,或者调用View的setVisibility()方法来控制其显示或消失时,就处于一个转换状态。这种事件就有可能会激发动画。
      当前被增加或者移除的View可以经历一个出现的动画或者一个消失的动画;而且不止是当前要控制的View,ViewGroup中的其他View也可以随之进行变动,比如经历一个动画移动到新的位置。所以一共有四种相关的动画类型:

      1) View本身的出现动画;
      2) 消失动画;
      3) 由于新增了其他View而需要改变位置的动画;
      4) 由于移除了其他View而需要改变位置的动画。如果增加或移除了其他View之后,当前View的位置不需要改变,则无动画。

     我们可以自定义这些动画,通过setAnimator() 方法把它们设置进一个 LayoutTransition 对象中去。 

layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, customDisappearingAnim);
第一个参数表示应用的情境,可以以下4种类型:
* APPEARING,当一个元素变为Visible时对其应用的动画。
* CHANGE_APPEARING,当一个元素变为Visible时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画。
* DISAPPEARING,当一个元素变为InVisible时对其应用的动画。
* CHANGE_DISAPPEARING,当一个元素变为Gone时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画 disappearing from the container。

第二个参数为一Animator。

layoutTransitioner.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
此函数设置动画持续时间,参数分别为类型与时间。

      最后通过setLayoutTransition(LayoutTransition)方法把这些动画以一个 LayoutTransition 对象的形式设置给一个ViewGroup即可。

      示例代码:      

mTransitioner = new LayoutTransition();
setupCustomAnimations();
container.setLayoutTransition(mTransitioner);

// 为mTransitioner生成自定义动画
    private void setupCustomAnimations() {
        // 动画:CHANGE_APPEARING
        // Changing while Adding
        PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
        PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
        PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0,
                1);
        PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom",
                0, 1);
        PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX",
                1f, 0f, 1f);
        PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofFloat("scaleY",
                1f, 0f, 1f);

        final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(
                this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScaleX,
                pvhScaleY).setDuration(
                mTransitioner.getDuration(LayoutTransition.CHANGE_APPEARING));
        mTransitioner.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);
        changeIn.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator anim) {
                View view = (View) ((ObjectAnimator) anim).getTarget();
                view.setScaleX(1f);
                view.setScaleY(1f);
            }
        });

        // 动画:CHANGE_DISAPPEARING
        // Changing while Removing
        Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
        Keyframe kf1 = Keyframe.ofFloat(.9999f, 360f);
        Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
        PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe(
                "rotation", kf0, kf1, kf2);
        final ObjectAnimator changeOut = ObjectAnimator
                .ofPropertyValuesHolder(this, pvhLeft, pvhTop, pvhRight,
                        pvhBottom, pvhRotation)
                .setDuration(
                        mTransitioner
                                .getDuration(LayoutTransition.CHANGE_DISAPPEARING));
        mTransitioner.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
                changeOut);
        changeOut.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator anim) {
                View view = (View) ((ObjectAnimator) anim).getTarget();
                view.setRotation(0f);
            }
        });

        // 动画:APPEARING
        // Adding
        ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "rotationY", 90f,
                0f).setDuration(
                mTransitioner.getDuration(LayoutTransition.APPEARING));
        mTransitioner.setAnimator(LayoutTransition.APPEARING, animIn);
        animIn.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator anim) {
                View view = (View) ((ObjectAnimator) anim).getTarget();
                view.setRotationY(0f);
            }
        });

        // 动画:DISAPPEARING
        // Removing
        ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "rotationX", 0f,
                90f).setDuration(
                mTransitioner.getDuration(LayoutTransition.DISAPPEARING));
        mTransitioner.setAnimator(LayoutTransition.DISAPPEARING, animOut);
        animOut.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator anim) {
                View view = (View) ((ObjectAnimator) anim).getTarget();
                view.setRotationX(0f);
            }
        });

    }
如果不执行setupCustomAnimations()方法 ,那么设置给container的动画就全部是默认动画。

如果要使用默认的动画,还有一个非常简单的方式是在ViewGroup的XML布局文件中把android:animateLayoutchanges 属性设置为true。
这样就自动地按照默认方式来对要移除或添加的View,还有Group中的其他View进行动画。比如ApiDemos中的LayoutAnimationsByDefault。

3.10 Keyframes

      keyFrame是一个时间/值对,通过它可以定义一个在特定时间的特定状态,而且在两个keyFrame之间可以定义不同的Interpolator,就相当多个动画的拼接,第一个动画的结束点是第二个动画的开始点。KeyFrame是抽象类,要通过ofInt(),ofFloat(),ofObject()获得适当的KeyFrame,然后通过PropertyValuesHolder.ofKeyframe获得PropertyValuesHolder对象,如以下例子:

Keyframe kf0 = Keyframe.ofInt(0, 400);
Keyframe kf1 = Keyframe.ofInt(0.25f, 200);
Keyframe kf2 = Keyframe.ofInt(0.5f, 400);
Keyframe kf4 = Keyframe.ofInt(0.75f, 100);
Keyframe kf3 = Keyframe.ofInt(1f, 500);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(btn2, pvhRotation);
rotationAnim.setDuration(2000);
上述代码的意思为:设置btn对象的width属性值使其:
开始时 Width=400
动画开始1/4时 Width=200
动画开始1/2时 Width=400
动画开始3/4时 Width=100
动画结束时 Width=500
第一个参数为时间百分比,第二个参数是在第一个参数的时间时的属性值。
定义了一些Keyframe后,通过PropertyValuesHolder类的方法ofKeyframe封装,然后通过ObjectAnimator.ofPropertyValuesHolder获得Animator。
用下面的代码可以实现同样的效果:
ObjectAnimator oa=ObjectAnimator.ofInt(btn2, "width", 400,200,400,100,500);
oa.setDuration(2000);
oa.start();

最后介绍下NineOldAndroids包:

NineOldAndroids将android3.0才有的动画Api,扩展到了Android1.0以上,可以在Android1.0以上的系统使用3.0的Api,增强动画效果,自定义各种的动画。这个库的作者是ActionBarSherlock的作者。

Animator使用项目实例参考:http://www.cnblogs.com/mengdd/archive/2013/09/05/3303191.html。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值