Android自定义View - 百分比扇形图

Android自定义View - 百分比扇形图

前两天舍友在网上看到了一个扇形图的自定义View,看到后自己也想借此机会试一下自定义View中Canvas的使用,便写了个简单的扇形图,效果图如下,具体细节还有些不是很完善:

GIF动图(以实际运行效果为准):
动图展示

具体的实现方式就是canvas的绘制了,主要参考了 Carson_Ho 的系列:https://www.jianshu.com/p/762b490403c3 ,讲解非常全面。

大致思路:

  1. 自定义 View ,重写两个参数的构造函数 public SectorProcessView(Context context, AttributeSet attrs) ,并在其中中初始化画笔 Paint 等数据。例如:

    public SectorProcessView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(mTextSize);
    }
    
  2. 在自定义 ViewonDraw(Canvas) 中编写具体的逻辑。例如绘制一个扇形 drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) ,参数的具体含义也不难理解,详细的可以参考上面提到的链接或者官方文档 https://developer.android.google.cn/reference/android/graphics/Canvas。

    关于这个方法有几点需要注意

    • onDraw(Canvas) 方法会在创建该 View 时调用,并且是在测量之后调用的,因此可以在该方法中获取到当前 View 的宽和高。
    • onDraw(Canvas) 方法会在 invalidate() 方法调用后执行,因此可以通过调用 invalidate() 方法实现 View 的重绘,进而实现刷新或动画的效果。
    • canvas 绘制时,绘制出来的对象按照次序叠放(即图层),因此无须使用 saveLayer() 方法。

    例:

    protected void onDraw(Canvas canvas) {
        if (mWidth == 0) {
            mWidth = getWidth();
            mHeight = getHeight();
        }
        mPaint.setColor(mSectorColor);
        canvas.drawArc(0, 0, 100, 100, 0, mCurrentRate * 360, true, mPaint);
    }
    
  3. 在子线程中执行操作,需要更新时通过 post(Runnable action) 方法在主线程中更新数据并执行 invalidate() 方法实现刷新。由于线程之间共享数据可能引发问题,因此最好在 action 中而不是子线程中更新数据。
    例:

    public void startAnimate() {
        new Thread(new Runnable() {
            @Override
            public void run() {
           		int tick = 1;
                while (tick <= 1000) {
                    post(new Runnable() {
                        @Override
                        public void run() {
                        	// update data
                            invalidate();
                        }
                    });
                    tick++;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
    
  4. 然后就是具体细节的定义了,例如文字颜色,前景色背景色啊什么的。引用的时候可以直接在布局文件中通过包名+自定义View名使用,很是方便。

大致就是这些了,下面是代码,如果有问题,欢迎评论区讨论 ?。


以下是自定义的扇形图View的代码:


public class SectorProcessView extends View {
    /**
     * 默认动画持续时间为 1s
     */
    public static final int DEFAULT_ANIMATION_DURATION = 1000;

    /**
     * 宽高
     */
    private int mWidth;
    private int mHeight;

    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 是否显示背景圆以及文字
     */
    private boolean mShowBGOval;
    private boolean mShowText;
    /**
     * 扇形矩形与背景色矩形
     */
    private RectF mSectorRect;
    private RectF mBGRect;
    /**
     * 扇形颜色,背景圆形颜色,文字颜色
     * 默认:#77007FFF #007FFF #000000
     */
    private int mSectorColor;
    private int mBGColor;
    private int mTextColor;
    /**
     * 文字大小,默认为 40
     */
    private float mTextSize;
    /**
     * 绘制动画时当前比例变化的监听器
     */
    private OnRateChangeListener mOnRateChangeListener;

    /**
     * 当前比例,绘制动画时使用
     */
    private float mCurrentRate;
    /**
     * 是否正在绘制动画
     */
    private boolean mIsAnimating;

    public SectorProcessView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        mShowBGOval = true;
        mShowText = true;

        mSectorRect = new RectF(0, 0, 0, 0);
        mBGRect = new RectF(0, 0, 0, 0);

        mSectorColor = Color.parseColor("#77007FFF");
        mBGColor = Color.parseColor("#007FFF");
        mTextColor = Color.BLACK;
        mTextSize = 40;

        mWidth = mHeight = 0;

        mCurrentRate = (float) (2 / 3.0);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(mTextSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mWidth == 0) {
            mWidth = getWidth();
            mHeight = getHeight();
        }

        if (mShowBGOval) {
            mBGRect.left = mWidth * (1 - mCurrentRate) / 2;
            mBGRect.right = mWidth * (1 + mCurrentRate) / 2;
            mBGRect.top = mHeight * (1 - mCurrentRate) / 2;
            mBGRect.bottom = mHeight * (1 + mCurrentRate) / 2;
            mPaint.setColor(mBGColor);
            canvas.drawOval(mBGRect, mPaint);
        }

        if (mSectorRect.bottom == 0) {
            mSectorRect.right = mWidth;
            mSectorRect.bottom = mHeight;
        }
        mPaint.setColor(mSectorColor);
        canvas.drawArc(mSectorRect, 0, mCurrentRate * 360, true, mPaint);

        if (mShowText) {
            mPaint.setColor(mTextColor);
            canvas.drawText(String.format(Locale.CHINA, "%.0f%%", mCurrentRate * 100),
                    mWidth / 2, mHeight / 2 + mTextSize / 3, mPaint);
        }

        if (mOnRateChangeListener != null)
            mOnRateChangeListener.onRateChange(this, mCurrentRate);
    }

    /**
     * 开始绘制动画
     *
     * @param fromRate        起始比例,0~1之间
     * @param toRate          结束比例,0~1之间
     * @param animateDuration 时长
     */
    public void startAnimate(final float fromRate, final float toRate, final int animateDuration) {
        if (mIsAnimating)
            return;
        if (fromRate > 1 || toRate > 1)
            return;
        mIsAnimating = true;
        mCurrentRate = fromRate;
        new Thread(new Runnable() {
            int tick = 1;

            @Override
            public void run() {
                while (tick <= animateDuration) {
                    post(new Runnable() {
                        @Override
                        public void run() {
                            mCurrentRate += (toRate - fromRate) / animateDuration;
                            invalidate();
                        }
                    });
                    tick++;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                mIsAnimating = false;
            }
        }).start();
    }

    public void drawRate(float rate) {
        if (mIsAnimating)
            return;
        mCurrentRate = rate;
        invalidate();
    }

    public void setBGOvalColor(int bgColor) {
        this.mBGColor = bgColor;
        invalidate();
    }

    public void setSectorColor(int sectorColor) {
        this.mSectorColor = sectorColor;
        invalidate();
    }

    public void setTextColor(int textColor) {
        this.mTextColor = textColor;
        invalidate();
    }

    public void setShowBGOval(boolean showBGOval) {
        this.mShowBGOval = showBGOval;
    }

    public void setShowText(boolean showText) {
        this.mShowText = showText;
    }

    public void setOnRateChangeListener(OnRateChangeListener onRateChangeListener) {
        this.mOnRateChangeListener = onRateChangeListener;
    }

    public void setTextSize(float textSize) {
        this.mTextSize = textSize;
        mPaint.setTextSize(mTextSize);
        invalidate();
    }

    /**
     * 绘制动画时比例变化监听器
     */
    public interface OnRateChangeListener {
        void onRateChange(View view, float rate);
    }
}

下面是Demo的代码,主要是 seekBar 与自定义View的联动以及一个自制的简易颜色选择器,需要的话可以参考:

SectorUsageActivity

public class SectorUsageActivity extends AppCompatActivity {
    private SectorProcessView spvView;
    private SeekBar seekBar;

    private int mTextColor;
    private int mSectorColor;
    private int mBGOvalColor;

    private int mDuration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sector_usage);

        mTextColor = Color.BLACK;
        mSectorColor = getResources().getColor(R.color.colorAzureHalfTran);
        mBGOvalColor = getResources().getColor(R.color.colorAzure);

        mDuration = DEFAULT_ANIMATION_DURATION;

        //联动
        seekBar = findViewById(R.id.sk_bar_sector_usage);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser)
                    spvView.drawRate((float) (progress / 100.0));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });

        //联动
        spvView = findViewById(R.id.spv_sector_usage);
        spvView.setOnRateChangeListener(new SectorProcessView.OnRateChangeListener() {
            @Override
            public void onRateChange(View view, float rate) {
                seekBar.setProgress((int) (rate * 100));
            }
        });

        //启动按钮
        findViewById(R.id.btn_sector_usage_to_draw).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                spvView.startAnimate(0, (float) Math.random(), mDuration);
            }
        });

        //文字颜色
        final View pickTextColor = findViewById(R.id.view_sector_usage_pick_text_color);
        pickTextColor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startColorPicker(pickTextColor, 0);
            }
        });

        //扇形颜色
        final View pickSectorColor = findViewById(R.id.view_sector_usage_pick_sector_color);
        pickSectorColor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startColorPicker(pickSectorColor, 1);
            }
        });

        //背景圆颜色
        final View pickOvalColor = findViewById(R.id.view_sector_usage_pick_oval_color);
        pickOvalColor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startColorPicker(pickOvalColor, 2);
            }
        });

        //文字大小
        SeekBar seekBarTextSize = findViewById(R.id.sk_bar_sector_text_size);
        seekBarTextSize.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser)
                    spvView.setTextSize(progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });

        //设置文字可见性
        CheckBox checkBoxShowText = findViewById(R.id.checkbox_sector_usage_show_text);
        checkBoxShowText.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                spvView.setShowText(isChecked);
            }
        });

        //设置背景可见性
        CheckBox checkBoxShowOval = findViewById(R.id.checkbox_sector_usage_show_oval);
        checkBoxShowOval.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                spvView.setShowBGOval(isChecked);
            }
        });

        //动画时长
        SeekBar seekBarAnimateDura = findViewById(R.id.sk_bar_sector_duration);
        seekBarAnimateDura.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser)
                    mDuration = progress * 10;
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });
    }

    /**
     * 自制颜色选择器
     *
     * @param showColorView 设置颜色的view
     * @param whichToSet    指定设置的是什么颜色
     *                      0 文字
     *                      1 扇形
     *                      2 背景圆
     */
    private void startColorPicker(final View showColorView, final int whichToSet) {
        LinearLayout linearLayout = new LinearLayout(this);
        View view = LayoutInflater.from(this).inflate(R.layout.ll_color_picker_dialog_view, linearLayout);

        final SeekBar red = view.findViewById(R.id.sk_bar_dialog_view_red);
        final SeekBar green = view.findViewById(R.id.sk_bar_dialog_view_green);
        final SeekBar blue = view.findViewById(R.id.sk_bar_dialog_view_blue);
        final SeekBar alpha = view.findViewById(R.id.sk_bar_dialog_view_alpha);

        final View bgView = view.findViewById(R.id.view_dialog_view_bg);


        switch (whichToSet) {
            case 0:
                alpha.setProgress((mTextColor >> 24) & 0xFF);
                red.setProgress((mTextColor >> 16) & 0xFF);
                green.setProgress((mTextColor >> 8) & 0xFF);
                blue.setProgress(mTextColor & 0xFF);
                bgView.setBackgroundColor(mTextColor);
                break;
            case 1:
                alpha.setProgress((mSectorColor >> 24) & 0xFF);
                red.setProgress((mSectorColor >> 16) & 0xFF);
                green.setProgress((mSectorColor >> 8) & 0xFF);
                blue.setProgress(mSectorColor & 0xFF);
                bgView.setBackgroundColor(mSectorColor);
                break;
            case 2:
                alpha.setProgress((mBGOvalColor >> 24) & 0xFF);
                red.setProgress((mBGOvalColor >> 16) & 0xFF);
                green.setProgress((mBGOvalColor >> 8) & 0xFF);
                blue.setProgress(mBGOvalColor & 0xFF);
                bgView.setBackgroundColor(mBGOvalColor);
                break;
        }
        SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser)
                    bgView.setBackgroundColor(Color.argb(alpha.getProgress(), red.getProgress(), green.getProgress(), blue.getProgress()));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        };
        red.setOnSeekBarChangeListener(seekBarChangeListener);
        green.setOnSeekBarChangeListener(seekBarChangeListener);
        blue.setOnSeekBarChangeListener(seekBarChangeListener);
        alpha.setOnSeekBarChangeListener(seekBarChangeListener);

        new AlertDialog.Builder(this)
                .setView(linearLayout)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        int c = Color.argb(alpha.getProgress(), red.getProgress(), green.getProgress(), blue.getProgress());
                        showColorView.setBackgroundColor(c);
                        switch (whichToSet) {
                            case 0:
                                mTextColor = c;
                                spvView.setTextColor(c);
                                break;
                            case 1:
                                mSectorColor = c;
                                spvView.setSectorColor(c);
                                break;
                            case 2:
                                mBGOvalColor = c;
                                spvView.setBGOvalColor(c);
                                break;
                        }
                    }
                }).show();
    }

布局文件 activity_sector_usage

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp"
    tools:context=".activities.SectorUsageActivity">
    <com.example.ericjeffrey.helloworld.views.SectorProcessView
        android:id="@+id/spv_sector_usage"
        android:layout_width="150dp"
        android:layout_height="150dp" />
    <Button
        android:id="@+id/btn_sector_usage_to_draw"
        android:layout_marginTop="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="click to animate"
        tools:ignore="HardcodedText" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Progress: "
            android:textColor="@android:color/black"
            tools:ignore="HardcodedText" />
        <SeekBar
            android:id="@+id/sk_bar_sector_usage"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:text="TextColor: "
            tools:ignore="HardcodedText" />
        <View
            android:id="@+id/view_sector_usage_pick_text_color"
            android:background="@android:color/black"
            android:layout_width="40dp"
            android:layout_height="match_parent"/>
        <TextView
            android:layout_marginStart="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:text="SectorColor: "
            tools:ignore="HardcodedText" />
        <View
            android:id="@+id/view_sector_usage_pick_sector_color"
            android:background="@color/colorAzureHalfTran"
            android:layout_width="40dp"
            android:layout_height="match_parent"/>
        <TextView
            android:layout_marginStart="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:text="OvalColor: "
            tools:ignore="HardcodedText" />
        <View
            android:id="@+id/view_sector_usage_pick_oval_color"
            android:background="@color/colorAzure"
            android:layout_width="40dp"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:gravity="center_vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textAllCaps="false"
            android:text="TextSize: "
            tools:ignore="HardcodedText" />
        <SeekBar
            android:id="@+id/sk_bar_sector_text_size"
            android:max="100"
            android:progress="40"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:gravity="center_vertical">
        <CheckBox
            android:id="@+id/checkbox_sector_usage_show_text"
            android:text="show text"
            android:checked="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:ignore="HardcodedText" />
        <CheckBox
            android:id="@+id/checkbox_sector_usage_show_oval"
            android:text="show oval BG"
            android:checked="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:ignore="HardcodedText" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:gravity="center_vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textAllCaps="false"
            android:text="Duration: "
            tools:ignore="HardcodedText" />
        <SeekBar
            android:id="@+id/sk_bar_sector_duration"
            android:max="100"
            android:progress="1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

颜色选择对话框布局文件: ll_color_picker_dialog_view

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:orientation="vertical"
    android:padding="10dp">

    <View
        android:id="@+id/view_dialog_view_bg"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:background="#007FFF"
        android:elevation="5dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Alpha: "
            android:textColor="#0000FF"
            tools:ignore="HardcodedText" />

        <SeekBar
            android:id="@+id/sk_bar_dialog_view_alpha"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="255"
            android:progress="255" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Red:    "
            android:textColor="#FF0000"
            tools:ignore="HardcodedText" />

        <SeekBar
            android:id="@+id/sk_bar_dialog_view_red"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="255"
            android:progress="0" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Green: "
            android:textColor="#00FF00"
            tools:ignore="HardcodedText" />

        <SeekBar
            android:id="@+id/sk_bar_dialog_view_green"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="255"
            android:progress="127" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Blue:    "
            android:textColor="#0000FF"
            tools:ignore="HardcodedText" />

        <SeekBar
            android:id="@+id/sk_bar_dialog_view_blue"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="255"
            android:progress="255" />
    </LinearLayout>
</LinearLayout>
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值