Android View 四个构造函数详解

本文深入探讨了Android中View类的四个构造函数及其应用场景,特别是如何利用defStyleAttr和defStyleRes来设置样式和主题,详细分析了不同属性赋值方式的优先级。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载于:https://blog.csdn.net/zhao123h/article/details/52210732?locationNum=8&fps=1

在开发android开发过程中,很多人都会遇到自定义view,一般都需要继承自View类,而当你打开View类的源码时,发现会有四个构造函数,那么这四个构造函数是如何使用的呢,怎么合理的利用四个构造函数呢,本文将进行一定探究,希望能够抛砖引玉。

 

一 View类的四个构造函数

先从android源码中把四个构造函数拉出来看看。。

第一个构造函数

[java]  view plain  copy
  1. /** 
  2.      * Simple constructor to use when creating a view from code. 
  3.      * 
  4.      * @param context The Context the view is running in, through which it can 
  5.      *        access the current theme, resources, etc. 
  6.      */  
  7.     public View(Context context)   

二个构造函数

[java]  view plain  copy
  1. /** 
  2.     * Constructor that is called when inflating a view from XML. This is called 
  3.     * when a view is being constructed from an XML file, supplying attributes 
  4.     * that were specified in the XML file. This version uses a default style of 
  5.     * 0, so the only attribute values applied are those in the Context's Theme 
  6.     * and the given AttributeSet. 
  7.     * 
  8.     * <p> 
  9.     * The method onFinishInflate() will be called after all children have been 
  10.     * added. 
  11.     * 
  12.     * @param context The Context the view is running in, through which it can 
  13.     *        access the current theme, resources, etc. 
  14.     * @param attrs The attributes of the XML tag that is inflating the view. 
  15.     * @see #View(Context, AttributeSet, int) 
  16.     */  
  17.    public View(Context context, @Nullable AttributeSet attrs)  

3 第个构造函数

[java]  view plain  copy
  1. /** 
  2.      * Perform inflation from XML and apply a class-specific base style from a 
  3.      * theme attribute. This constructor of View allows subclasses to use their 
  4.      * own base style when they are inflating. For example, a Button class's 
  5.      * constructor would call this version of the super class constructor and 
  6.      * supply <code>R.attr.buttonStyle</code> for <var>defStyleAttr</var>; this 
  7.      * allows the theme's button style to modify all of the base view attributes 
  8.      * (in particular its background) as well as the Button class's attributes. 
  9.      * 
  10.      * @param context The Context the view is running in, through which it can 
  11.      *        access the current theme, resources, etc. 
  12.      * @param attrs The attributes of the XML tag that is inflating the view. 
  13.      * @param defStyleAttr An attribute in the current theme that contains a 
  14.      *        reference to a style resource that supplies default values for 
  15.      *        the view. Can be 0 to not look for defaults. 
  16.      * @see #View(Context, AttributeSet) 
  17.      */  
  18.     public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)  

4 第4个构造函数

[java]  view plain  copy
  1. /** 
  2.      * Perform inflation from XML and apply a class-specific base style from a 
  3.      * theme attribute or style resource. This constructor of View allows 
  4.      * subclasses to use their own base style when they are inflating. 
  5.      * <p> 
  6.      * When determining the final value of a particular attribute, there are 
  7.      * four inputs that come into play: 
  8.      * <ol> 
  9.      * <li>Any attribute values in the given AttributeSet. 
  10.      * <li>The style resource specified in the AttributeSet (named "style"). 
  11.      * <li>The default style specified by <var>defStyleAttr</var>. 
  12.      * <li>The default style specified by <var>defStyleRes</var>. 
  13.      * <li>The base values in this theme. 
  14.      * </ol> 
  15.      * <p> 
  16.      * Each of these inputs is considered in-order, with the first listed taking 
  17.      * precedence over the following ones. In other words, if in the 
  18.      * AttributeSet you have supplied <code><Button * textColor="#ff000000"></code> 
  19.      * , then the button's text will <em>always</em> be black, regardless of 
  20.      * what is specified in any of the styles. 
  21.      * 
  22.      * @param context The Context the view is running in, through which it can 
  23.      *        access the current theme, resources, etc. 
  24.      * @param attrs The attributes of the XML tag that is inflating the view. 
  25.      * @param defStyleAttr An attribute in the current theme that contains a 
  26.      *        reference to a style resource that supplies default values for 
  27.      *        the view. Can be 0 to not look for defaults. 
  28.      * @param defStyleRes A resource identifier of a style resource that 
  29.      *        supplies default values for the view, used only if 
  30.      *        defStyleAttr is 0 or can not be found in the theme. Can be 0 
  31.      *        to not look for defaults. 
  32.      * @see #View(Context, AttributeSet, int) 
  33.      */  
  34.     public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)  

这四个构造函数在源码中都有注释,为了便于理解,我将注释也贴了出来,有点长,不过利于本文理解。

二 构造函数的调用

【第一个问题】

如果我们在继承了View类实现自定义类时,需要重写哪个构造函数?

回答这个问题,首先需要知道,在定义了View时,我们都调用了哪个构造函数。我这里做了一个简单的实验:

我们声明一个简单的View,继承自TextView,什么都不改,只是在4个constructor中打印几个tag,查看到底哪个构造函数被调用。

【实验】

[java]  view plain  copy
  1. package com.zhaohui.code.view;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.util.Log;  
  6. import android.widget.TextView;  
  7.   
  8. import com.example.zhaohui.player.R;  
  9.   
  10. /** 
  11.  * Created by zhaohui on 16-8-12. 
  12.  */  
  13. public class CustomView extends TextView {  
  14.     String tag = "customView";  
  15.   
  16.     public CustomView(Context context) {  
  17.         super(context);  
  18.         Log.d(tag,"First Constructor");  
  19.     }  
  20.   
  21.     public CustomView(Context context, AttributeSet attrs) {  
  22.         this(context,attrs,android.R.attr.textViewStyle);  
  23.         Log.d(tag,"Second Constructor");  
  24.   
  25.   
  26.         for(int i=0;i<attrs.getAttributeCount();i++){  
  27.             Log.d(tag, attrs.getAttributeName(i)+"   :   "+attrs.getAttributeValue(i));  
  28.         }  
  29.   
  30.     }  
  31.   
  32.     public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {  
  33.         this(context, attrs, defStyleAttr,0);  
  34.         Log.d(tag,"Third Constructor");  
  35.     }  
  36.   
  37.     public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  38.         super(context, attrs, defStyleAttr,defStyleRes);  
  39.         Log.d(tag,"Fourth Constructor");  
  40.     }  
  41.   
  42. }  

在布局文件layout中加上这个View

[html]  view plain  copy
  1. <com.zhaohui.code.view.CustomView  
  2.         android:layout_width="match_parent"  
  3.         android:layout_height="wrap_content"  
  4.         android:text="CustomView"  
  5.         android:textSize="30sp"/>  

[实验结果]


08-14 05:08:37.847 13687-13687/com.example.zhaohui.player D/customView: Second Constructor

08-14 05:08:37.847 13687-13687/com.example.zhaohui.player D/customView: textSize   :   30.0sp

08-14 05:08:37.847 13687-13687/com.example.zhaohui.player D/customView: layout_width   :   -1

08-14 05:08:37.848 13687-13687/com.example.zhaohui.player D/customView: layout_height   :   -2

08-14 05:08:37.848 13687-13687/com.example.zhaohui.player D/customView: text   :   CustomView


【实验—结果分析】

通过上面的实验输出中的Second Constructor,我们知道,当我们自定义一个View,且在布局文件中引用时,在系统初始化该View时,调用的是第二个构造函数,而且我还把第二个构造函数中的attrs参数也打了出来,可以看出其中的参数attrs是我们在xml中配置的参数。

其实第一个构造函数用途并不大,主要是在java代码中声明一个View时所用,不过如果只用第一个构造函数,声明的View并没有任何的参数,基本是个空的View对象。

三 View的第三和第四个构造函数

在回答了第一个问题后,还有后两个构造函数,这是本文的重点。

1 View的属性和主题

     在说后两个构造函数之前,先说说View的属性,在View中有不同的属性,比如layout_width等,TextView还有textColor这些特有的属性,我们可以对这些属性进行不同的配置进而实现不同的效果。而且属性也可以在不同的位置进行配置。以TextView为例,android:textColor这个属性可以在多个地方配置,可以直接写在xml中,可以在xml中以style的形式定义,这两种是我们平时见得较多的,其实还有一种背后的力量可以给属性赋值,那就是主题。

     我们在android中可以配置一个主题,从而使得一些View即使你不对其进行任何配置,它都会有一些已经默认赋值的属性,这就是主题的功劳。

     View类的后两个构造函数都是与主题相关的,也就是说,在你自定义View时,如果不需要你的View随着主题变化而变化,有前两个构造函数就OK了,但是如果你想你的View随着主题变化而变化,就需要利用后两个构造函数了。

属性赋值的优先级

         当可以在多个地方赋值属性时,一个问题就不可避免的出现了:优先级!!!

         一个属性可以在多个地方赋值,xml定义,xml中引入style,theme中直接指定,defStyleAttr,defStyleRes 这5个地方。(后面会将这几个地方的用处)

      【第二个问题】

        属性在多个地方被赋值后,系统以哪个属性为准呢?

我将用一个实验,利用多个TextView整体说明属性赋值的优先级,这个实验将贯穿文章后面,我将分块讲解。

【实验】

首先我们定义一个style文件,从style中可以看出,我们定义了一个主题,主题中有两种定义textView颜色的形式,一种是对textViewStyle进行定义(蓝色),一种是直接对textColor进行定义(紫色)。这是主题运用的两种方式,后面详述,现在我们只需要知道,我们的主题默认不是白色的!!!!

后面的几种style,每种对应一个颜色,用来区分优先级。

【实验 - style文件】

[html]  view plain  copy
  1. <resources>  
  2.   
  3.     <!-- Base application theme. -->  
  4.     <style name="AppTheme" parent="@android:style/Theme.Holo">  
  5.         <item name="android:textViewStyle">@style/BlueTextStyle</item>  
  6.         <item name="android:textColor">@android:color/holo_purple</item>  
  7.     </style>  
  8.   
  9.     <style name ="BlueTextStyle">  
  10.         <item name="android:textColor">@android:color/holo_blue_light</item>  
  11.     </style>  
  12.   
  13.     <style name ="RedTextStyle">  
  14.         <item name="android:textColor">@android:color/holo_red_light</item>  
  15.     </style>  
  16.   
  17.     <style name ="OrangeTextStyle">  
  18.         <item name="android:textColor">@android:color/holo_orange_light</item>  
  19.     </style>  
  20.   
  21.     <style name ="GreenTextStyle">  
  22.         <item name="android:textColor">@android:color/holo_green_light</item>  
  23.     </style>  
  24. </resources>  


【实验 - 布局文件】

我们声明一个layout文件,里面有多个TextView和我自定义的View,现在我们自需要现在只看前三个TextView

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical"  
  7.     android:fitsSystemWindows="true"  
  8.     >  
  9.   
  10.     <TextView  
  11.         android:layout_width="wrap_content"  
  12.         android:layout_height="wrap_content"  
  13.         android:text="Normal TextView"  
  14.         android:textSize="30sp"/>  
  15.     <TextView  
  16.         style="@style/RedTextStyle"  
  17.         android:layout_width="wrap_content"  
  18.         android:layout_height="wrap_content"  
  19.         android:text="Red style TextView"  
  20.         android:textSize="30sp"/>  
  21.   
  22.     <TextView  
  23.         style="@style/RedTextStyle"  
  24.         android:textColor="@android:color/holo_orange_light"  
  25.         android:layout_width="wrap_content"  
  26.         android:layout_height="wrap_content"  
  27.         android:text="XML defined orangeTextView"  
  28.         android:textSize="30sp"/>  
  29.   
  30.     <com.zhaohui.code.view.CustomView  
  31.         android:layout_width="match_parent"  
  32.         android:layout_height="wrap_content"  
  33.         android:text="CustomView"  
  34.         android:textSize="30sp"/>  
  35.     <com.zhaohui.code.view.CustomBlankView  
  36.         android:layout_width="match_parent"  
  37.         android:layout_height="wrap_content"  
  38.         android:text="CustomBlankView !"  
  39.         android:textSize="30sp"/>  
  40.     <com.zhaohui.code.view.CustomGreenView  
  41.         android:layout_width="match_parent"  
  42.         android:layout_height="wrap_content"  
  43.         android:text="CustomGreenView !"  
  44.         android:textSize="30sp"/>  
  45.     <com.zhaohui.code.view.CustomGreenView  
  46.         android:textColor="@android:color/holo_orange_light"  
  47.         android:layout_width="match_parent"  
  48.         android:layout_height="wrap_content"  
  49.         android:text="Custom styled Red TextView !"  
  50.         android:textSize="30sp"/>  
  51.     <com.zhaohui.code.view.CustomGreenView  
  52.         style="@style/RedTextStyle"  
  53.         android:layout_width="match_parent"  
  54.         android:layout_height="wrap_content"  
  55.         android:text="Custom styled Red TextView !"  
  56.         android:textSize="30sp"/>  
  57. </LinearLayout>  

【实验结果


【实验 - 结果分析1

我们现在只看前三个TextView

第一个由于主题的原因,是蓝色

第二个 style="@style/RedTextStyle",颜色是红色,说明优先级style>theme

第三个style和xml定义同时存在,

style="@style/RedTextStyle

android:textColor="@android:color/holo_orange_light"

显示 橙色,说明优先级xml定义>style>theme

因此我们得到结论1:

【结论1】:优先级xml定义>style>theme


【实验 - 结果分析2

但是theme的分析远没有结束,我们刚才在定义主题时,有两个赋值,

[html]  view plain  copy
  1. <style name="AppTheme" parent="@android:style/Theme.Holo">  
  2.     <item name="android:textViewStyle">@style/BlueTextStyle</item>  
  3.     <item name="android:textColor">@android:color/holo_purple</item>  
  4. </style>  

为什么TextView默认的颜色是蓝色,而非紫色呢?????

这就需要研究View是如何利用系统主题,这时候需要回到我们今天的主题:View的构造函数!!!!

View中如何体现主题的信息,需要就通过实验对View进行彻底探究。这是一个复杂的问题,需要看看View的第三和第四个构造函数,在看这两个构造函数时,就不可避免的看到两个让人懵b的参数:defStyleAttr和defStyleRes。这时引入我们的第三个问题。

【第三个问题】

那么在View的第四个构造函数中的后面两个的参数都是什么意思呢?

我们首先看看View的注释:

[java]  view plain  copy
  1. @param defStyleAttr An attribute in the current theme that contains a  
  2. *        reference to a style resource that supplies default values for  
  3. *        the view. Can be 0 to not look for defaults.</span><span style="background:rgb(255,255,255)">  

个构造函数中第三个参数defStyleAttr,从名字就能看出,是一个属性资源。

这个属性资源跟主题有一个奇妙的协议:只要在主题中对这个属性赋值,该View就会自动应用这个属性的值。

再看看在第四个构造函数中有一个参数defStyleRes,这个参数是什么作用呢?

先看注释:

[java]  view plain  copy
  1. @param defStyleRes A resource identifier of a style resource that  
  2.     *        supplies default values for the view, used only if  
  3.     *        defStyleAttr is 0 or can not be found in the theme. Can be 0  
  4.     *        to not look for defaults.</span>  

这个参数说,只有在第三个参数defStyleAttr为0,或者主题中没有找到这个defStyleAttr属性的赋值时,才可以启用。而且这个参数不再是Attr了,而是真正的style。其实这也是一种低级别的“默认主题”,即在主题未声明属性值时,我们可以主动的给一个style,使用这个构造函数定义出的View,其主题就是这个定义的defStyleRes是一种写死的style,因此优先级被调低

【源码实例】

我们看一下在TextView中是如何给defStyleAttr和defStyleRes这两个参数赋值的。查看TextView的源码中,四个构造函数:

[java]  view plain  copy
  1.  public TextView(Context context) {  
  2.         this(context, null);  
  3.     }  
  4.   
  5.     public TextView(Context context, @Nullable AttributeSet attrs) {  
  6.         this(context, attrs, com.android.internal.R.attr.textViewStyle);  
  7.     }  
  8.   
  9.     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {  
  10.         this(context, attrs, defStyleAttr, 0);  
  11.     }  
  12.   
  13.     @SuppressWarnings("deprecation")  
  14.     public TextView(  
  15.             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  16.         super(context, attrs, defStyleAttr, defStyleRes);  
  17.         ......  
  18. }  


可以看出,TextView的第2个构造函数直接调用了第3个构造函数,只是传了一个 com.android.internal.R.attr.textViewStyle 的参数第三个在调用第四个构造函数时,最后一个参数是0.


【TextView 的defStyleAttr和defStyleRes】

也就是说在TextView中,TextView的第二个构造函数传入的defStyleAttrcom.android.internal.R.attr.textViewStyle

那么这个com.android.internal.R.attr.textViewStyle是个什么鬼呢,我们从源码中看看。

查看/Sdk/platforms/android-23/data/res/values这个路径中找个一个attrs.xml文件,打开看看,找到textViewStyle,如下所示,哦,原来是一个reference类型的属性,因此在给这个属性赋值时,在xml中一般使用@style/xxx形式就可以了。

[html]  view plain  copy
  1. <declare-styleable name="Theme">  
  2. ......  
  3. <attr name="textViewStyle" format="reference"/>  
  4. ......  
  5. </declare-styleable>  

app的主题可以为这个textViewStyle属性提供一套默认的style资源。比如在本例中,我们的主题继承自Theme.Holo中,在Theme.Holo中有一个item如下:

[html]  view plain  copy
  1. <item name="textViewStyle">@style/Widget.Holo.TextView</item>  
明在Theme.Holo中,textViewStyle 指向@style/Widget.Holo.TextView

因此默认情况下,TextView的属性都是在Widget.Holo.TextView这个Style中(但是其实这个style中并没有对textColor进行定义,有兴趣的可以自己去看看)。

在本例中我们自己定义了主题,通过继承Theme.Holo主题,修改这个textViewStylereference使得textViewStyle指向了蓝色主题,如下所示。因此本文中appTextView默认颜色是蓝色。

[html]  view plain  copy
  1. <style name="AppTheme" parent="@android:style/Theme.Holo">  
  2.    <item name="android:textViewStyle">@style/BlueTextStyle</item>  
  3.    <item name="android:textColor">@android:color/holo_purple</item>  
  4. </style>  

但是,我们的主题的内容并没有完结,很明显,我们在主题中还有一个android:textColor的赋值。

在同时使用了defStyleAttr(即主题中定义的textViewStyle)和主题直接定义时,显示了defStyleAttr的定义,说明使用了defStyleAttr的优先级要比直接在主题中声明优先级高。因此我们又得到一个结论。

【结论2】:优先级 defStyleAttr>theme直接定义


【第个问题】

     从上文中我们知道了defStyleAttr和theme的顺序,那么defStyleRes的优先级呢?

     现在需要确定defStyleRes的优先级了,我们重新回到我们的实验,我们的实验里,构造了三种自定义的View, CustomView, CustomBlankView和CustomGreenView。

CustomView的代码上面已写,现在将剩下两种自定义的View代码展示如下:

【实验 - CustemGreenView类

[java]  view plain  copy
  1. package com.zhaohui.code.view;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.util.Log;  
  6. import android.widget.TextView;  
  7.   
  8. import com.example.zhaohui.player.R;  
  9.   
  10. /** 
  11.  * Created by zhaohui on 16-8-12. 
  12.  */  
  13. public class CustomGreenView extends TextView {  
  14.     String tag = "customGreen";  
  15.   
  16.     public CustomGreenView(Context context) {  
  17.         super(context);  
  18.     }  
  19.   
  20.     public CustomGreenView(Context context, AttributeSet attrs) {  
  21.         this(context,attrs,0);  
  22.   
  23.     }  
  24.   
  25.     public CustomGreenView(Context context, AttributeSet attrs, int defStyleAttr) {  
  26.         this(context, attrs, defStyleAttr,R.style.GreenTextStyle);  
  27.     }  
  28.   
  29.     public CustomGreenView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  30.         super(context, attrs, defStyleAttr,defStyleRes);  
  31.     }  
  32.   
  33. }  

【实验 - CustomBlankView 类】

[java]  view plain  copy
  1. package com.zhaohui.code.view;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.widget.TextView;  
  6.   
  7. /** 
  8.  * Created by zhaohui on 16-8-12. 
  9.  */  
  10. public class CustomBlankView extends TextView {  
  11.     String tag = "customGreen";  
  12.   
  13.     public CustomBlankView(Context context) {  
  14.         super(context);  
  15.     }  
  16.   
  17.     public CustomBlankView(Context context, AttributeSet attrs) {  
  18.         this(context,attrs,0);  
  19.   
  20.     }  
  21.   
  22.     public CustomBlankView(Context context, AttributeSet attrs, int defStyleAttr) {  
  23.         this(context, attrs, defStyleAttr,0);  
  24.     }  
  25.   
  26.     public CustomBlankView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  27.         super(context, attrs, defStyleAttr,defStyleRes);  
  28.     }  
  29.   
  30. }  

【实验-结果分析3

CustomGreenView中,defStyleAttr被赋值为0,defStyleRes赋值为R.style.GreenTextStyle,即我们的绿色style

在CustomBlankView中,defStyleAttr和defStyleRes都为0,此时的颜色是紫色,即直接在theme中声明的颜色。

说明在同时在defStyleRes和主题中声明时,优先显示defStyleRes.由此又得出一个结论。

【结论3】:优先级 defStyleRes>theme直接定义

 

在实验的最后两个还是说明了直接在xml中写属性的优先级较高,即

【结论4】:优先级 xml直接定义>xml的style定义>theme直接定义


【小结】

在Theme中的优先级主要涉及到三个部分:defStyleAttr,defStyleRes和主题直接定义

我们需要分种情况,在构造函数中,

1 当defStyleAttr!=0时,

主题中如果对defStyleAttr属性进行赋值,显示对defStyleAttr的赋值,优先级最高!

2 当(defStyleAttr==0或主题没有对defStyleAttr进行赋值)&& defStyleRes!=0而且theme中没有定义时时,显示defStyleRes,优先级

3 如果defStyleAttr==0且defStyleRes==0时,显示theme直接定义,优先级最低

由此我们得到属性赋值总体优先级:

【结论 总属性赋值优先级   Xml定义 > xml的style定义 > defStyleAttr > defStyleRes> theme直接定义


四 总结

View类中有四个构造函数,涉及到多个参数,

Context:上线文,这个不用多说

AttributeSet attrs 从xml中定义的参数

int defStyleAttr :主题中优先级最高的属性

int defStyleRes  : 优先级次之的内置于View的style

在android中的属性可以在多个地方进行赋值,涉及到的优先级排序为:

Xml直接定义 > xml中style引用 > defStyleAttr > defStyleRes > theme直接定义

总体来说,本文是对android中View的四个构造函数的探究,主要涉及到View属性的优先级问题,希望大家发现问题或不足时及时跟我联系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值