android自定义验证码输入框

本文分享了一种自定义验证码输入框的实现方法,详细介绍了如何通过创建自定义View实现输入框自动换行、锁定光标等功能,并展示了如何在布局文件中使用及添加监听器。

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

公司的项目有个关于验证码的自定义文本框,要求实现锁定一次光标,输入框自动换行,哎呦,对我这个菜鸟来说,真是着急死了,各种扒拉,各种调试,最后还好实现了,但是感觉跟预期的还是差了那么一点点。。。。记录一哈哈吧,感谢各位技术大佬们。。。。

public class CodeInputView extends View {

    private Paint mPaint;   //绘制对象

    private Handler mHandler; //用于指示输入提示

    private boolean isDrawText;//是否绘制文本

    private boolean isInputState = false;//是否输入状态

    private boolean mDrawRemindLineState;  //竖线状态控制  true 显示

    private int mInputStateBoxColor;  //输入状态下框颜色
    private int mNoInputStateBoxColor;//未输入状态下框颜色

    private int mRemindLineColor;  //提示输入线颜色
    private int mInputStateTextColor;  //输入后文字图案提示颜色


    private int mBoxDrawType = 0;//盒子图画类型 0 矩形 2圆形 3横线
    private int mShowPassType = 0;// 0 提示图案为实心圆 1提示图案为*

    private boolean isShowRemindLine = true;// true 显示提示光标 默认显示

    private int mWidth = 40;
    private int mheight = 40;

    private String mPassText = "";//要绘制的文字

    private Context mContext;

    private int mDrawTxtSize = 25;

    private int mDrawBoxLineSize = 4;

    public void setInputStateColor(int inputColor) {
        this.mInputStateBoxColor = inputColor;
    }

    public void setmPassText(String mPassText) {
        this.mPassText = mPassText;
    }

    public void setNoinputColor(int noinputColor) {
        this.mNoInputStateBoxColor = noinputColor;
    }

    public void setInputState(boolean input) {
        isInputState = input;
    }

    public void setRemindLineColor(int line_color) {
        this.mRemindLineColor = line_color;
    }


    public void setInputStateTextColor(int drc_color) {
        this.mInputStateTextColor = drc_color;
    }


    public CodeInputView(Context context) {
        this(context, null);
    }

    public CodeInputView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public void setmBoxDrawType(int mBoxDrawType) {
        this.mBoxDrawType = mBoxDrawType;
    }


    public void setmShowPassType(int mShowPassType) {
        this.mShowPassType = mShowPassType;
    }

    public void setmDrawTxtSize(int mDrawTxtSize) {
        this.mDrawTxtSize = mDrawTxtSize;
    }


    public void setmIsShowRemindLine(boolean mIsShowShan) {
        this.isShowRemindLine = mIsShowShan;
    }

    public void setmDrawBoxLineSize(int mDrawBoxLineSize) {
        this.mDrawBoxLineSize = mDrawBoxLineSize;
    }

    public CodeInputView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        // 初始化画笔
        mPaint = new Paint();
        mPaint.setAntiAlias(true);

        // 设置“空心”的外框的宽度
        mPaint.setStrokeWidth(mDrawBoxLineSize);
        mPaint.setPathEffect(new CornerPathEffect(1));
    }

    @Override
    public void setOnClickListener(@Nullable OnClickListener l) {
        super.setOnClickListener(l);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        int width = 0;
        int height = 0;

        if (modeWidth == MeasureSpec.EXACTLY) {//如果是精确测量 则直接返回值
            width = sizeWidth;
        } else {//指定宽度的大小
            width = mWidth;
            if (modeWidth == MeasureSpec.AT_MOST) {//如果是最大值模式  取当中的小值  防止超出父类控件的最大值
                width = Math.min(width, sizeWidth);
            }
        }

        if (modeHeight == MeasureSpec.EXACTLY) {//如果是精确测量 则直接返回值
            height = sizeHeight;
        } else {//指定高度的大小
            height = mheight;
            if (modeHeight == MeasureSpec.AT_MOST) {//如果是最大值模式  取当中的小值  防止超出父类控件的最大值
                height = Math.min(height, sizeHeight);
            }
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        drawInputBox(canvas);    //绘制输入框

        drawRemindLine(canvas);  //绘制提醒线

        drawInputTextOrPicture(canvas);         //绘制输入文本或密码图案

    }

    /**
     * //绘制输入文本或密码图案
     *
     * @param canvas
     */
    private void drawInputTextOrPicture(Canvas canvas) {
        if (isDrawText) {            //是否需要进行绘制

            mPaint.setColor(ContextCompat.getColor(mContext, mInputStateTextColor));
            mPaint.setStyle(Paint.Style.FILL);//实心
            switch (mShowPassType) {
                case 0:                                     //绘制圆心
                    canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 8, mPaint);
                    break;
                case 1:                                      //绘制*
                    mPaint.setTextSize(getMeasuredWidth() / 2 + 10);
                    float stringWidth = mPaint.measureText("*");

                    float baseY = (getMeasuredHeight() / 2 - ((mPaint.descent() + mPaint.ascent()) / 2)) + stringWidth / 3;  //实现y轴居中方法
                    float baseX = getMeasuredWidth() / 2 - stringWidth / 2;  //实现X轴居中方法
                    canvas.drawText("*", baseX, baseY, mPaint);
                    break;
                case 2:                                     //绘制输入数据
                    mPaint.setTextSize(mDrawTxtSize);//绘制字体大小
                    float stringWidth2 = mPaint.measureText(mPassText);

                    float baseY2 = (getMeasuredHeight() / 2 - ((mPaint.descent() + mPaint.ascent()) / 2)) + stringWidth2 / 5;  //实现y轴居中方法
                    float baseX2 = getMeasuredWidth() / 2 - stringWidth2 / 2;  //实现X轴居中方法
                    canvas.drawText(mPassText, baseX2, baseY2, mPaint); //文字
                    break;
            }


        }
    }

    /**
     * 绘制提示线
     *
     * @param canvas
     */
    private void drawRemindLine(Canvas canvas) {
        if (mDrawRemindLineState && isShowRemindLine) {  // mDrawRemindLineState 控制闪烁情况  //isShowRemindLine 是否绘制提示线
            int line_height = getMeasuredWidth() / 2 - 10;

            line_height = line_height < 0 ? getMeasuredWidth() / 2 : line_height;

            mPaint.setStyle(Paint.Style.FILL);//实心
            mPaint.setColor(ContextCompat.getColor(mContext, mRemindLineColor));
            canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - line_height / 2, getMeasuredWidth() / 2, getMeasuredHeight() / 2 + line_height / 2, mPaint);
        }
    }

    /**
     * 绘制输入框
     *
     * @param canvas
     */
    private void drawInputBox(Canvas canvas) {
        if (isInputState) { //是否是输入状态  输入状态和未输入状态颜色区分
            mPaint.setColor(ContextCompat.getColor(mContext, mInputStateBoxColor));
        } else {
            mPaint.setColor(ContextCompat.getColor(mContext, mNoInputStateBoxColor));
        }
        mPaint.setStyle(Paint.Style.STROKE);//空心
        switch (mBoxDrawType) {
            case 1:         //绘制圆形
                canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, getMeasuredWidth() / 2 - 5, mPaint);
                break;
            case 2:        //绘制横线
                canvas.drawLine(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight(), mPaint);
                break;
            default:// 绘制矩形
                RectF rect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
                canvas.drawRoundRect(rect, 6, 6, mPaint);
        }

    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
        }
    }


    /**
     * 刷新状态
     *
     * @param isinput 是否已经输入过
     */
    public void updateInputState(boolean isinput) {
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
        }
        if (isinput) {
            isInputState = true;
            isDrawText = true;
        } else {
            isInputState = false;
            isDrawText = false;
        }
        mDrawRemindLineState = false;
        invalidate();
    }

    /**
     * 设置为选中输入状态
     */
    public void startInputState() {
        isInputState = true;
        isDrawText = false;

        if (mHandler == null) {
            mHandler = new Handler();
        }
        mHandler.removeCallbacksAndMessages(null);

        if (!isShowRemindLine) {
            invalidate();
            return;
        }

        mHandler.post(new Runnable() {
            @Override
            public void run() {                 //循环绘制 造成闪动状态
                mDrawRemindLineState = !mDrawRemindLineState;
                invalidate();
                mHandler.postDelayed(this, 800);

            }
        });
    }

自定义线性布局:

public class CodeInputLayout extends LinearLayout {

    private int maxLength = 6; //长度

    private int inputIndex = 0; //设置子LinearLayout的子View状态index
    private int lInputIndex = 0;//设置子LinearLayout状态index

    private List<String> mPassList;//储存输入

    private pwdChangeListener pwdChangeListener;//状态改变监听

    private Context mContext;

    private boolean mIsShowInputLine;
    private int mInputColor;
    private int mNoinputColor;
    private int mLineColor;
    private int mTxtInputColor;
    private int mDrawType;
    private int mInterval;
    private int mItemWidth;
    private int mItemHeight;
    private int mShowPassType;
    private int mTxtSize;
    private int mBoxLineSize;

    public void setPwdChangeListener(pwdChangeListener pwdChangeListener) {
        this.pwdChangeListener = pwdChangeListener;
    }

    public CodeInputLayout(Context context) {
        this(context, null);
    }

    public CodeInputLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CodeInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

    /**
     * 初始化View
     */
    private void initView(Context context, AttributeSet attrs) {

        mContext = context;
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PassWordLayoutStyle);

        mInputColor = ta.getResourceId(R.styleable.PassWordLayoutStyle_box_input_color, R.color.gray_c1c1c1);
        mNoinputColor = ta.getResourceId(R.styleable.PassWordLayoutStyle_box_no_input_color, R.color.gray_c1c1c1);
        mLineColor = ta.getResourceId(R.styleable.PassWordLayoutStyle_input_line_color, R.color.blue_3385ff);
        mTxtInputColor =
                ta.getResourceId(R.styleable.PassWordLayoutStyle_text_input_color, R.color.blue_3385ff);
        mDrawType = ta.getInt(R.styleable.PassWordLayoutStyle_box_draw_type, 0);
        mInterval = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_interval_width, 4);
        maxLength = ta.getInt(R.styleable.PassWordLayoutStyle_pass_leng, 6);
        mItemWidth = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_item_width, 40);
        mItemHeight = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_item_height, 40);
        mShowPassType = ta.getInt(R.styleable.PassWordLayoutStyle_pass_inputed_type, 0);
        mTxtSize = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_draw_txt_size, 32);
        mBoxLineSize = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_draw_box_line_size, 4);
        mIsShowInputLine = ta.getBoolean(R.styleable.PassWordLayoutStyle_is_show_input_line, true);
        ta.recycle();

        mPassList = new ArrayList<>();

        setOrientation(VERTICAL);
        setGravity(Gravity.CENTER);

        //设置点击时弹出输入法
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                setFocusable(true);
                setFocusableInTouchMode(true);
                requestFocus();
                InputMethodManager imm =
                        (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.showSoftInput(CodeInputLayout.this, InputMethodManager.SHOW_IMPLICIT);

            }
        });
        this.setOnKeyListener(new MyKeyListener());//按键监听

        setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View view, boolean b) {
                LinearLayout lLayout = (LinearLayout) getChildAt(lInputIndex);
                if (lLayout != null) {
                    CodeInputView pView = (CodeInputView) lLayout.getChildAt(inputIndex);
                    if (b) {
                        if (pView != null) {
                            pView.setmIsShowRemindLine(mIsShowInputLine);
                            pView.startInputState();
                        }
                    } else {
                        if (pView != null) {
                            pView.setmIsShowRemindLine(false);
                            pView.updateInputState(false);
                        }
                    }
                }
            }
        });
    }

    /**
     * 添加子View
     *
     * @param context
     */
    private void addChildVIews(Context context) {
        for (int j = 0; j < 4; j++) {
            LinearLayout linearLayout = new LinearLayout(context);
            LayoutParams lParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams
                    .WRAP_CONTENT);
            linearLayout.setOrientation(HORIZONTAL);
            if (j > 0) {                                       //第一个和最后一个子View不添加边距
                lParams.topMargin = mInterval;
            }
            for (int i = 0; i < maxLength; i++) {
                CodeInputView codeInputView = new CodeInputView(context);
                if(j==0 && i == 0){
                    codeInputView.setmIsShowRemindLine(true);
                    codeInputView.startInputState();
                }

                LayoutParams params = new LayoutParams(mItemWidth, mItemHeight);
                if (i > 0) {                                       //第一个和最后一个子View不添加边距
                    params.leftMargin = 0;
                }

                codeInputView.setInputStateColor(mInputColor);
                codeInputView.setNoinputColor(mNoinputColor);
                codeInputView.setInputStateTextColor(mTxtInputColor);
                codeInputView.setRemindLineColor(mLineColor);
                codeInputView.setmBoxDrawType(mDrawType);
                codeInputView.setmShowPassType(mShowPassType);
                codeInputView.setmDrawTxtSize(mTxtSize);
                codeInputView.setmDrawBoxLineSize(mBoxLineSize);
                codeInputView.setmIsShowRemindLine(mIsShowInputLine);
                linearLayout.addView(codeInputView, params);
                //                addView(passWordView, params);
            }
            addView(linearLayout, lParams);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (getChildCount() == 0) {     //判断 子View宽+边距是否超过了父布局 超过了则重置宽高
            if ((maxLength * mItemWidth + (maxLength - 1) * mInterval) > getMeasuredWidth()) {
                mItemWidth = (getMeasuredWidth() - (maxLength - 1) * mInterval) / maxLength;
                mItemHeight = mItemWidth;
            }

            addChildVIews(getContext());
        }

    }

    /**
     * 添加
     *
     * @param pwd
     */
    public void addPwd(String pwd) {
        //maxLength*4 是因为一行有maxLen个一共4行
        if (mPassList != null && mPassList.size() < maxLength * 4) {
            mPassList.add(pwd + "");
            setNextInput(pwd);
        }

        if (pwdChangeListener != null) {
            if (mPassList.size() < maxLength) {
                pwdChangeListener.onChange(getPassString());
            } else {
                pwdChangeListener.onFinished(getPassString());
            }
        }
    }

    /**
     * 删除
     */
    public void removePwd() {
        if (mPassList != null && mPassList.size() > 0) {
            mPassList.remove(mPassList.size() - 1);
            setPreviosInput();
        }

        if (pwdChangeListener != null) {
            if (mPassList.size() > 0) {
                pwdChangeListener.onChange(getPassString());
            } else {
                pwdChangeListener.onNull();
            }
        }
    }



    /**
     * 获取
     *
     * @return pwd
     */
    public String getPassString() {

        StringBuffer passString = new StringBuffer();

        for (String i : mPassList) {
            passString.append(i);
        }

        return passString.toString();
    }

    /**
     * 设置下一个View为输入状态
     */
    private void setNextInput(String pwdTxt) {
        if (lInputIndex < 4) {
            setNoInput(inputIndex, true, pwdTxt);
            inputIndex++;
            if (inputIndex >= maxLength && lInputIndex < 3) {
                lInputIndex += 1;
                inputIndex = 0;
            }
            setViewInputState(pwdTxt, false, false);
        }
    }

    private void setViewInputState(String pwdTxt, boolean isUpData, boolean isInput) {
        LinearLayout lLayout = (LinearLayout) getChildAt(lInputIndex);
        if (lLayout != null) {
            CodeInputView pView = (CodeInputView) lLayout.getChildAt(inputIndex);
            if (pView != null) {
                pView.setmPassText(pwdTxt + "");
                if (isUpData) {
                    pView.updateInputState(isInput);
                } else {
                    pView.startInputState();
                }
            }
        }
    }

    /**
     * 设置上一个View为输入状态
     */
    private void setPreviosInput() {
        if (inputIndex > 0) {
            setNoInput(inputIndex, false, "");
            inputIndex--;
            setViewInputState("", false, false);
        } else if (inputIndex == 0) {
            if (lInputIndex == 0 && inputIndex == 0) {
                setViewInputState("", false, false);
            } else {
                setNoInput(inputIndex, false, "");
                inputIndex = 3;
                lInputIndex--;
                setViewInputState("", false, false);
            }
        }
    }

    /**
     * 设置指定View为不输入状态
     *
     * @param index   view下标
     * @param isinput 是否输入过密码
     */
    public void setNoInput(int index, boolean isinput, String txt) {
        if (index < 0) {
            return;
        }
        setViewInputState(txt, true, isinput);
    }

    public interface pwdChangeListener {
        void onChange(String pwd);//密码改变

        void onNull();  //密码删除为空

        void onFinished(String pwd);//密码长度已经达到最大值
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;          //显示数字键盘
        outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI;
        return new ZanyInputConnection(this, false);
    }

    private class ZanyInputConnection extends BaseInputConnection {

        @Override
        public boolean commitText(CharSequence txt, int newCursorPosition) {
            return super.commitText(txt, newCursorPosition);
        }

        public ZanyInputConnection(View targetView, boolean fullEditor) {
            super(targetView, fullEditor);
        }

        @Override
        public boolean sendKeyEvent(KeyEvent event) {
            return super.sendKeyEvent(event);
        }

        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
            if (beforeLength == 1 && afterLength == 0) {
                return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) && sendKeyEvent(
                        new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
            }

            return super.deleteSurroundingText(beforeLength, afterLength);
        }
    }

    /**
     * 按键监听器
     */
    class MyKeyListener implements OnKeyListener {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (event.isShiftPressed()) {//处理*#等键
                    return false;
                }
                if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {//处理数字
                    addPwd(keyCode - 7 + "");              //点击添加密码
                    return true;
                }

                if (keyCode == KeyEvent.KEYCODE_DEL) {       //点击删除
                    removePwd();
                    return true;
                }

                InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
                return true;
            }
            return false;
        }//onKey
    }

    //恢复状态
    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }
        SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        this.mPassList = savedState.saveString;
        inputIndex = mPassList.size();
        if (mPassList.isEmpty()) {
            return;
        }
        for (int i = 0; i < getChildCount(); i++) {
            CodeInputView codeInputView = (CodeInputView) getChildAt(i);
            if (i > mPassList.size() - 1) {
                if (codeInputView != null) {
                    codeInputView.setmIsShowRemindLine(false);
                    codeInputView.updateInputState(false);
                }
                break;
            }

            if (codeInputView != null) {
                codeInputView.setmPassText(mPassList.get(i));
                codeInputView.updateInputState(true);
            }
        }

    }

    //保存状态
    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState savedState = new SavedState(superState);
        savedState.saveString = this.mPassList;
        return savedState;
    }

    public static class SavedState extends BaseSavedState {
        public List<String> saveString;

        public SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);

            dest.writeList(saveString);
        }

        private SavedState(Parcel in) {
            super(in);
            in.readStringList(saveString);
        }

        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel source) {
                return new SavedState(source);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

}

 布局文件里面调用:

<xxxx.goods.CodeInputLayout
    android:id="@+id/passLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@+id/tv_tipsinfo"
    android:layout_marginTop="@dimen/dp54"
    app:box_draw_type="rect"
    app:interval_width="4dp"
    app:item_height="35dp"
    app:item_width="35dp"
    app:pass_inputed_type="text"
    app:pass_leng="four" />

代码里面添加监听:

codeInputLayout.setPwdChangeListener(new CodeInputLayout.pwdChangeListener() {
    @Override
    public void onChange(String pwd) {
        Log.i("info","onChange---in--"+pwd);
       /* if(pwd.length()){

        }*/
    }

    @Override
    public void onNull() {

    }

    @Override
    public void onFinished(String pwd) {
        Log.i("info","onFinished--in---"+pwd);

        codeStrg = pwd;
    }
});

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值