View绘制Drawable原理分析记录

Android自定义进度条实战:从Canvas绘制到网络感知 进度条是移动端最基础的加载反馈组件,其本质是将后台任务状态转化为用户可感知的视觉信号。理解ProgressBar的Drawable渲染机制、View绘制流程与动画时间轴控制,是实现高定制化体验的前提。本文聚焦Android平台,深入剖析系统ProgressBar在RecyclerView复用、深色模式适配、无障碍支持及网络延迟响应等方面的固有局限,并基于View直接继承方案,结合Canvas硬件加速绘制、Shader光效合成、RTT动态动画调控等关键技术,提供一套稳定、可维护、符合WCAG与Material 阅读详情

View绘制Drawable原理分析记录

Drawable是Android开发中的通用可绘制对象,View类默认针对Drawable进行一些必要的绘制,如背景,此文旨在记录分析View绘制Drawable时的关键细节

1.XML布局文件的Background

通常,我们在XML文件中设置View的background如下:

android:background="@drawable/drawable_bg"/*设置其他Drawable背景*/
android:background="#ff124a54"/*设置颜色背景*/

在展开阐述之前,我们先明确一下,android:background属性值在实际操作中都会实例化为Drawable,参考官方文档,常用的属性值与Drawable对象关系如下:
- “#ffffffff”=>ColorDrawable
- “@drawable/shape_bg”=>GradientDrawable(假设shape_bg.xml文件为顶层为shape标签)
- “@drawable/bmp_bg”=>BitmapDrawable(假设bmp_bg为.png/.jpg/.bmp文件)
- “@drawable/9patch_bg”=>NinePatchDrawable(假设9patch_bg为.9.png文件)

2.Drawable的ConstantState

2.1什么是ConstantState

如果把Drawable比作一个绘制容器,那么ConstantState就是容器中真正的内容,Drawable的源码如下:

/**
     * This abstract class is used by {@link Drawable}s to store shared constant state and data
     * between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
     * share a unique bitmap stored in their ConstantState.
     *
     * <p>
     * {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
     * from this ConstantState.
     * </p>
     *
     * Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
     * {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
     * Drawable.
     */
    public static abstract class ConstantState {
        /**
         * Create a new drawable without supplying resources the caller
         * is running in.  Note that using this means the density-dependent
         * drawables (like bitmaps) will not be able to update their target
         * density correctly. One should use {@link #newDrawable(Resources)}
         * instead to provide a resource.
         */
        public abstract Drawable newDrawable();
        /**
         * Create a new Drawable instance from its constant state.  This
         * must be implemented for drawables that change based on the target
         * density of their caller (that is depending on whether it is
         * in compatibility mode).
         */
        public Drawable newDrawable(Resources res) {
            return newDrawable();
        }
        /**
         * Return a bit mask of configuration changes that will impact
         * this drawable (and thus require completely reloading it).
         */
        public abstract int getChangingConfigurations();

        /**
         * @hide
         */
        public Bitmap getBitmap() {
            return null;
        }
    }

    /**
     * Return a {@link ConstantState} instance that holds the shared state of this Drawable.
     *q
     * @return The ConstantState associated to that Drawable.
     * @see ConstantState
     * @see Drawable#mutate()
     */
    public ConstantState getConstantState() {
        return null;
    }

可以看出,ConstantState其实是Drawable中的静态抽象类,并且getConstantState()方法默认返回null,这我们可以推测ConstantState类和getConstantState()方法会被具体的Drawable实现类继承和重写。这样不难想象,由于不同Drawbale如BitmapDrawable、GradientDrawable这些自身存储的绘制内容数据原则上是不一样的,这就意味着Drawable为了易于扩展,ConstantState对象应该会存储属性可变的数据。

参考BitmapDrawable源码,BitmapDrawable中确实有BitmapState继承于ConstantState:

final static class BitmapState extends ConstantState {
        Bitmap mBitmap;/*可以看出,BitmapState里真正存放了Bitmap对象,而不是在其他地方*/
        int mChangingConfigurations;
        int mGravity = Gravity.FILL;
        Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
        Shader.TileMode mTileModeX = null;
        Shader.TileMode mTileModeY = null;
        int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
        boolean mRebuildShader;
        boolean mAutoMirrored;

        BitmapState(Bitmap bitmap) {
            mBitmap = bitmap;
        }

        BitmapState(BitmapState bitmapState) {
            this(bitmapState.mBitmap);
            mChangingConfigurations = bitmapState.mChangingConfigurations;
            mGravity = bitmapState.mGravity;
            mTileModeX = bitmapState.mTileModeX;
            mTileModeY = bitmapState.mTileModeY;
            mTargetDensity = bitmapState.mTargetDensity;
            mPaint = new Paint(bitmapState.mPaint);
            mRebuildShader = bitmapState.mRebuildShader;
            mAutoMirrored = bitmapState.mAutoMirrored;
        }

        @Override
        public Bitmap getBitmap() {
            return mBitmap;
        }

        @Override
        public Drawable newDrawable() {
            return new BitmapDrawable(this, null);
        }

        @Override
        public Drawable newDrawable(Resources res) {
            return new BitmapDrawable(this, res);
        }

        @Override
        public int getChangingConfigurations() {
            return mChangingConfigurations;
        }
    }

2.2Drawable与ConstantState的关系

引用
http://www.curious-creature.com/2009/05/02/drawable-mutations/(国外)
http://blog.youkuaiyun.com/zhaoyw2008/article/details/45562835(国内)

由同一个Resource实例化生成的多个Drawable公用一个ConstanteState,这是出于对内存节省策略的考虑。那么当我们需要修改多个Drawable中的其中一个的属性时,就会出现修改后的Drawable影响到了其他Drawable的情况,针对这种情况,我们的想法自然是复制一份ConstantState从而避免相互间的干扰。
从根源上复制Drawable的方法可以使用:

/*假设dr为原有Drawable*/
Drawable newDr = dr.getConstantState().newDrawable();
/*或者*/
Drawable newDr = dr.getConstantState().newDrawable().mutate();

3.View绘制Drawable(以绘制StateListDrawable为例)

1. 用户点击View触发onTouch()
2. 触发setPress(true)方法,同时执行以下代码:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    ...

    /**
     * Call this to force a view to update its drawable state. This will cause
     * drawableStateChanged to be called on this view. Views that are interested
     * in the new state should call getDrawableState.
     *
     * @see #drawableStateChanged
     * @see #getDrawableState
     */
    public void refreshDrawableState() {
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        drawableStateChanged();/*改变drawable状态*/

        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);/*如果该View之上仍有父View,则一直递归通知*/
        }
    }

    /**
     * This function is called whenever the state of the view changes in such
     * a way that it impacts the state of drawables being shown.
     *
     * <p>Be sure to call through to the superclass when overriding this
     * function.
     *
     * @see Drawable#setState(int[])
     */
    protected void drawableStateChanged() {
        Drawable d = mBackground;
        if (d != null && d.isStateful()) {
            d.setState(getDrawableState());/*获取当前Drawable状态(pressed),刷新mBackground的状态*/
        }
    }

    ...
}
    可以知道Drawable最终调用setState()方法更改了当前状态

3. Drawable更改状态并触发View绘制自身

public class Drawable{
    ...

    public boolean setState(final int[] stateSet) {
        if (!Arrays.equals(mStateSet, stateSet)) {
            mStateSet = stateSet;
            return onStateChange(stateSet);/*状态发生变化,传入新的stateSet进行通知*/
        }
        return false;
    }

    ...
}
public class StateListDrawable extends DrawableContainer {
    ...

    @Override
    protected boolean onStateChange(int[] stateSet) {
        int idx = mStateListState.indexOfStateSet(stateSet);
        if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states "
                + Arrays.toString(stateSet) + " found " + idx);
        if (idx < 0) {
            /*根据传入的状态,找到StateListDrawable中匹配的第一个状态索引*/
            idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
        }
        if (selectDrawable(idx)) {/*选择匹配状态的Drawable*/
            return true;
        }
        return super.onStateChange(stateSet);
    }


     public boolean selectDrawable(int idx) {
        if (idx == mCurIndex) {
            return false;
        }

        ...

        if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
            final Drawable d = mDrawableContainerState.getChild(idx);
            mCurrDrawable = d;
            mCurIndex = idx;
            ...

        } else {
            mCurrDrawable = null;
            mInsets = Insets.NONE;
            mCurIndex = -1;
        }

        if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
            if (mAnimationRunnable == null) {
                mAnimationRunnable = new Runnable() {
                    @Override public void run() {
                        animate(true);
                        invalidateSelf();// 请求绘制自己
                    }
                };
            } else {
                unscheduleSelf(mAnimationRunnable);
            }
            // Compute first frame and schedule next animation.
            animate(true);
        }

        invalidateSelf();// 请求绘制自己

        return true;
    }
    ...
}

4.View开始绘制draw()
经过上面一系列的Drawable状态切换和匹配Drawale,最终我们有了需要重新绘制的pressed状态下的Drawable,此时请求绘制自身并最终会调用View的draw()方法

    /**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    public void draw(Canvas canvas) {
        ...

        // draw方法会先绘制背景

        // 不透明才绘制背景
        if (!dirtyOpaque) {  
            final Drawable background = mBackground;
            if (background != null) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;

                if (mBackgroundSizeChanged) {
                    background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                    mBackgroundSizeChanged = false;
                }

                if ((scrollX | scrollY) == 0) {
                    background.draw(canvas);//开始绘制的动作
                } else {
                    canvas.translate(scrollX, scrollY);
                    background.draw(canvas);//开始绘制的动作
                    canvas.translate(-scrollX, -scrollY);
                }
            }
        }

由于Drawable的draw()方法由其实现类完成,绘制动作各有不同,但最终还是调用canvas.draw…的方法去绘制,这里就不展开讨论了。

至此,View绘制Drawable的流程就结束了!

第 25 章:View 系统与输入事件分发 LayoutInflater 运行时将 XML 布局资源转换成 View 对象,连接 res/layout 下声明式 XML 和内存中程序视图树。来源:frameworks/base/core/java/android/view/LayoutInflater.java (第 74 行)...创建自定义 View 需要理解并实现正确的回调方法。默认 onMeasure 实现 wrap_content 会填满父容器;自定义 View 需要正确处理测量模式。@Override// 固有尺寸像素。 阅读详情

相关推荐

Android Glide GifDrawable 模块源码解析

背景项目有个需求:对GIF动图实时添加文字。 结合之前FollowMe景区导游助手项目中做过图片添加水印,MovieMaker项目中多张图片合成一段具有多种切换效果视频的经验,预测需要对GIF文件的数据进行解码,合成处理,编码输出合成文件。 项目基于Glide库实现GIF图片的显示,因此需要搞清楚GIF文件的数据是怎么显示到ImageView模块。本文记录分析GifDrawable模块的原理的过程

hey Man 4375

从源码上剖析Android View绘制Drawable的原理

一、引言对于Drawable,相比每个Android 开发者都无比熟悉,在开发过程中我们经常setBackground设置背景,那么对于Drawable你了解多少呢?对于View是怎样把Drawable绘制出来又了解多少呢?对View根据不同状态绘制不同的背景又了解多少呢?也就是我们经常使用的selector,今天我们从源码上来深度剖析这些原理,从本质上卸下Drawable的神秘面纱。二、背景介绍在

粤语伶仃的专栏 1876

Android图形绘制技术详解与实战

在移动应用开发中,图形绘制是构建用户界面和实现视觉效果的核心技术之一。Android系统提供了丰富的绘图API,使得开发者可以在界面上自由绘制各种形状、图像和动画。本章将从整体上介绍Android图形绘制的技术栈构成,包括CanvasPaintBitmap等核心类的作用及其在整个视图渲染流程中的位置。我们将探讨Android的双缓冲机制、软件绘制与硬件加速的基本原理,并阐明自定义View在图形绘制中的关键地位。

weixin_29050829的博客 1122

android 中 Drawable 和 ConstantState 的一些认识

Drawable  Drawable就是一个可绘制的对象,或者叫做可渲染的对象,里面保存的是可以绘制的数据,其可能是一张位图(BitmapDrawable),也可能是一个图形(ShapeDrawable),还可能只是一个颜色(ColorDrawable)等等,等等。 1.Android 中 Drawable 类对象主要的功能就是可以给 canvas 上面绘制图形,也可以理解为渲

zxd1988的专栏 2563

Android中区别Drawable Bitmap Canvas Paint

本文主要介绍Android中Drawable Bitmap Canvas Paint 之间的区别,这里对这几个概念做出详细介绍,开发Android游戏的朋友可以参考下

基于ROS2的无人机野生动物巡查系统设计

自主巡检系统是嵌入式人工智能与机器人技术融合的关键应用方向,其核心在于多源感知融合、实时路径规划与端侧智能识别的协同实现。原理上依赖SLAM定位构建环境地图,结合栅格化建模与混合路径算法(如蚁群+BFS)达成全覆盖遍历;技术价值体现在资源受限平台下的确定性响应与时空对齐能力,支撑高鲁棒性野外作业。典型应用场景包括生态监测、禁飞区巡检及小尺度智能安防。本文围绕ROS2 Humble中间件架构、Livox激光雷达与MaixCAM视觉协同、YOLO11n INT8模型端侧部署等关键技术展开,突出嵌入式AI与实时控

weixin_30598047的博客 648

《Android开发艺术探索》第6章- Android 的 Drawable 读书笔记

目录1. Drawable 简介1.1 Drawable 和 View 的区别和联系是什么?1.2 如何获取 Drawable 的内部宽/高?2. Drawable 的分类2.1 BitmapDrawable2.2 ShapeDrawable2.3 LayerDrawable2.4 StateListDrawable2.5 LevelListDrawable2.6 TransitionDrawable2.7 InsetDrawable2.8 ScaleDrawable2.9 ClipDrawable3. 自

willwaywang6 3975

避免bank conflict的使用共享内存的矩阵转置。及其循环展开。转置前后都是行主序,中间共享内存中是列主序

为了避免bank conflict,可以使用padding的方式将数组中的每一行各自占据一个bank,这样每个线程读取不同行的数据时就不会发生bank conflict。padding的方式是在每行数据后面添加一定数量的空数据,使得每行的字节数恰好是bank的整数倍,这样就能保证每行数据各自占据一个bank。具体来说,当一个线程访问共享内存中的一个元素时,GPU会根据该元素在共享内存中的地址计算出其所在的bank,然后将该元素从该bank中读取出来。

weixin_55035144的博客 2143

Android Drawable的ConstantState共享问题解决,并从源码层分析其原因

在平时开发中,我们有时候会用到设置图片的透明的功能,不假思索的情况下,我们自然就会通过设置getBackground().setAlpha(XXX)来去达到我们的目的,但是我们后续会发现白色背景变成透明的状况,并且引发很多界面出现颜色错乱的问题,是不是很无奈呢? 这个问题,我们网上一搜也都久知道怎么解决了?通用解决方式: getBackground().setAlpha(XXX)改成getBackground().mutate().setAlpha(XXX); 这样就解决了。 我们在往后面看其他的解释,也

wytwyt123456的专栏 920

debian mirrors 镜像源 riscv 64 赛昉 星光 starfive vf2 InRelease‘ doesn‘t support architecture ‘riscv64‘

debian mirrors 镜像 riscv 64 赛昉 星光2 starfive vf2 InRelease' doesn't support architecture 'riscv64'

qq_29758759的博客 1257

博图Advanced实战:VB脚本实现HMI画面智能弹出(附完整代码解析)

本文详细介绍了在西门子博图Advanced平台中利用VB脚本实现HMI画面智能弹出的实战方法。通过动态计算坐标、分辨率自适应方案和完整代码解析,帮助工程师解决不同显示环境下的定位难题,提升工业自动化界面的交互体验。

weixin_29041767的博客 341

Android中的各种Drawable讲解(慕课笔记)

drawable概述 1、drawable是一个抽象类,是对可绘制物件的抽象。与View不同Drawable没有事件和交互方法。 2、Drawable有好多子类操作具体类型资源,比如BitmapDrawable是用来操作位图;ColorDrawable用来操作颜色;ClipDrawable用来操作剪切板。 二、Drawable原理 1、draw(Canvas canvas)

wode_dream的专栏 641

Photoshop免费获取指南:官方试用与开源替代方案详解

图像处理软件是数字内容创作的核心工具,其工作原理基于像素编辑、图层叠加与算法滤镜。这类软件的技术价值在于为设计师、摄影师和内容创作者提供了强大的视觉表达能力,广泛应用于平面设计、UI/UX、摄影后期及社交媒体内容制作等场景。在众多工具中,Adobe Photoshop以其全面的功能和行业标准地位备受关注。对于希望免费体验或使用的用户,存在合法且安全的途径。其中,Adobe官方提供的7天免费试用版允许用户在安全环境下体验完整功能,而开源替代方案如GIMP则提供了长期免费的强大图像编辑能力。理解这些合法获取方式

cigang4063的博客 522

Android View 绘制流程与自定义 View 全解

Android View 绘制流程与自定义 View 全解

OuO-1 的博客 493

十一、增强现实

那都是n年前的老版本了,新版本完全没有这个问题,我就是纠结这个问题,搭建若干环境,又是vmware虚拟机,又是msys2,又是anaconda,又是找机器,折腾了几天,结果通通不是。以一张实际卡片为锚点,通过笔记本的摄像头,在显示器上将一个计算机内的3d物体显示在卡片上,类似于电影上看到的那种全息投影技术;如果你是想在windows环境下开发linux运行程序,那么CygWin/gcc是你的不二之选。而如果你想开发的是windows运行程序,并且追求速度,那么二者相比而言,MinGW是更好的选择。

3188

linux那些事之numa balance

numa balance numa balance是页迁移技术最重要也是最开始使用应用的技术,主要是为解决由于调度器进程在两个node迁移造成访问内存性能问题。 numba balance通过页迁移技术,将不在运行节点的内存迁移到所运行的节点上。对应numa balance要注意几点: 防止进程在两个节点之前来回切换造成频繁进行页迁移(ping pang现场)从而影响性能。numa balance本身就是为了提高性能,不能因为造成频繁页迁移而对性能进行一个反向优化。 多个线程分布在不同线程 访.

weixin_42730667的博客 3907

Android的Drawable源码分析

Drawable源码分析 本篇文章主要讲述Drawable基类中必须要了解的相关知识,后续文章会根据不同子类进行分析

SEUXXD的博客 534

模型预测控制 (MPC, Model Predictive Control) 算法详解及案例分析

模型预测控制是一种基于模型的控制策略,通过在线求解一个有限时域的优化问题,预测系统的未来行为,并根据预测结果调整控制输入,以实现系统的稳定性和性能优化。

闲人编程的博客 2934

自定义动画之Drawable结合ProgressBar

今天来说一说使用Drawable做动画。我之前还不知道Drawable可以用来干View能干的事,放在以前如果需要做一个自定义的动画我首先想到的就是自定义View去解决,但是最近我发现通过Drawable也可以去实现,而且还不用measure和layout。 说起我发现Drawable这个东西呢也是有点巧合,因为最近在跟着凯哥的公众号学自定义View,然后学到矩阵变化的时候,我就去想弄明白内部的原

qq_34107153的博客 653

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

自定义Drawable:实现文字生成图片

一 、写在正文前 作为一个初来乍到的非资深android从业者,这是本人第一次在简书装逼,哦不,发表文章,因此有点小紧张以及难以掩饰的小激动[手动滑稽],若是有什么写的有问题的,没错,是本人技术问题,还望多多见谅。 二、概述 二话不说反手就是一个超链接:下载地址 一言不合反手又是一个超链接:github地址 本文将介绍的是一款在googleplay上线的APP:TextImageMaker主...

一现灵光 3016

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Android中View的绘制

Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View的,如TextView、Button、ImageView、ListView等。这些控件虽然是Android系统本身就提供好的,我们只需要拿过来使用就可以了,但你知道它们是怎样被绘制到屏幕上的吗?多知道一些总是没有坏处的,那么我们赶快进入到本篇文章的正题内容吧。 要知道,任何一个视图都不可能凭空突然出现在屏幕上,它们都是要

No Excuses! Confidence! Vigorous! 1063

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

View的工作原理之自定义View

        前面的四篇系列文章,从源码开始详细的分析了View的Measure过程。学以致用,这篇文章就记录一下,学完View的Measure过程之后,自己自定义View的一些收获。本文讲解的是普通View的自定义,ViewGroup的自定义将在下篇讲解。        创建一个Android应用工程,新建一个类MyView继承自View。重写它的三个构造方法及onDraw方法: pub...

林序的博客 278

Android从源码解析三:View绘制流程

相信大家都知道我们所看到的控件都是直接或者间接的继承View类,下面我们看一下它是如何绘制在我们视野里的。 要知道,每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),我们逐个对这三个阶段展开进行探讨...

陌路旧梦 1647

天天记录 - Android refreshDrawableState源码及流程简单分析

有多种因素会导致触发refreshDrawableState,当前只考虑其中一种setPressed即设置视图是否处于被按下状态。其他会触发此方法的有focusChanged等。     原理是定义不同状态的图片,系统进行状态监听例如在onTouchEvent中判断当前在什么状态,再根据之前提供的图片进行设置并重绘显示效果。     以下是根据代码一个具体的流程,其中解释的并不是太多,当前也都

Android 1万+

Android视图绘制流程完全解析,带你一步步深入了解View(二)

转载请注明出处:http://blog.youkuaiyun.com/guolin_blog/article/details/16330267 在上一篇文章中,我带着大家一起剖析了一下LayoutInflater的工作原理,可以算是对View进行深入了解的第一步吧。那么本篇文章中,我们将继续对View进行深入探究,看一看它的绘制流程到底是什么样的。如果你还没有看过我的上一篇文章,可以先去阅读 Andro

Liu_LongPo的专栏 2076
上一篇: Android NDK开发环境搭建记录
下一篇: ViewGroup为什么不会调用onDraw
angusing
博客等级 码龄15年 11粉丝 19原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值