浅析Android View(二)

AD5700 HART芯片实战笔记:从时钟检测到数据收发,一个STM32工程师的踩坑实录 本文详细记录了STM32工程师在AD5700 HART芯片开发中的实战经验,涵盖时钟信号检测、RTS切换时序优化及HART数据解析等关键问题。通过硬件设计检查、定时器配置优化和大小端转换方案对比,解决了工业级芯片开发中的典型挑战,为嵌入式开发者提供宝贵参考。 阅读详情

深入理解Android View(一) View的位置参数信息
二、View的绘制过程
View的绘制过程一共分为三个部分:
- measure(测量View的大小)
- layout(确定View的位置)
- draw(画出View)
通常我们的View都是以这样的树结构呈现的,如下图
这里写图片描述
当然我们这里ViewGroup其实上面其实是放在DecorView中的,我们可以通过findViewbById(andorid.id.content)获取到顶级ViewGroup,这里的DecorView开发中一般涉及不到,这里我们不过多分析,我们只需要知道我们的绘制过程是从DecorView分发下来的即可。我们先来看ViewGroup中的一段源码

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }


protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

这里我们可以看到在ViewGroup中的measureChildren的方法,首先获取到了所有的子View,然后调用View的measure方法进行子View的测量,这样就完成了一次测量过程,然后子View会重复父View的操作,如此返回就完成了整个View树的测量过程。通过源码我们发现一个View的大小,主要由父View的MeasureSpec和自身的LayoutParams来共同决定,LayoutParams我们应都比较熟悉了,接下来我们就来深入分析MeasureSpec,这是在View类中的一个静态内部类,看名字就能猜到它代表它的作用是一种测量标准或者说测量规格,我们先来看下它的源码

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY = 1 << MODE_SHIFT;
    public static final int AT_MOST = 2 << MODE_SHIFT;

    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            // 老版api
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}

MeasureSpec代表一个32位的int值,高两位代表SpecMode,低30位代表SpecSize,Mode指测量模式,Size则指测量的大小,通过看MeasureSpec的源码也能看到,其实这里谷歌工程师非常巧妙的把Mode和Size封装到了一个int值中,非常巧妙的办法(这种设计方式可以借鉴),如果你对Java运算符不太熟的话,你可以这样理解,谷歌工程师把一个32位的int分成了两节,一节用来表示Mode,一节用来表示Size。可以看到Mode分为三种模式如下所示:

  • UNSPECIFIED
    父容器不对子View做任何限制,要多大给多大,一般用于系统内部,这里不用过多考虑
  • EXACTLY
    精准模式,一般View指定了具体的大小(dp/px)或者设置为match_parent则就是这个模式
  • AT_MOST
    父容器制定了一个可用的大小,子View不能大于这个值,对于wrap_content
    这里写图片描述
    这里借用任主席博客中的一张图,UNSPECIFIED 我们不予考虑。
    总结:一个View的绘制主要由父View的MeasureSpec和自身的LayoutParams决定,其关系如上表。说了这么多其实大多数时候当我自定义View时,只需要处理AT_MOST即可,通用代码如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    //分别获取宽高的Mode 和size 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);  
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);  
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);  
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);  
    // 如果是精准模式则直接使用获取到的宽高,如果是AT_MOST,则使用我 们自己测量的宽高
    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {  
        setMeasuredDimension(mWidth, mHeight);  
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {  
        setMeasuredDimension(mWidth, heightSpecSize);  
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {  
        setMeasuredDimension(widthSpecSize, mHeight);  
    }  
}

如果当前是View则直接完成测量,如果当前是ViewGruop除了完成自身绘制外,还需要遍历调用子View的measure方法。
View的layout方法和Draw方法就比较简单了,layout用于确定View的位置,原理和Measure过程类似,当一个ViewGroup的位置被确定之后,会遍历其所有子View并调用其layout方法。Draw的过程也类似,通过dispatchDraw(Canvas canvas)方法,遍历子View,通过canvas进行Draw过程,接下来我通过一个小Demo流式布局,来运用一下三个过程




import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 *
 */
public class FlowLayout extends ViewGroup {

    /**
     * 存储所有的子View
     */
    private List<List<View>> mAllChildViews = new ArrayList<List<View>>();

    /**
     * 存储每一行的高度
     */
    private List<Integer> mLineHeight = new ArrayList<Integer>();

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

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

    public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mAllChildViews.clear();
        mLineHeight.clear();
        // 获取当前ViewGroup的宽度
        int width = getWidth();

        int lineWidth = 0;
        int lineHeight = 0;
        // 记录当前行的view
        List<View> lineViews = new ArrayList<View>();
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            // 如果需要换行
            if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width) {
                // 记录LineHeight
                mLineHeight.add(lineHeight);
                // 记录当前行的Views
                mAllChildViews.add(lineViews);
                // 重置行的宽高
                lineWidth = 0;
                lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
                // 重置view的集合
                lineViews = new ArrayList();
            }
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
                    + lp.bottomMargin);
            lineViews.add(child);
        }
        // 处理最后一行
        mLineHeight.add(lineHeight);
        mAllChildViews.add(lineViews);

        MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams();

        // 设置子View的位置
        int left = 0;
        // 添加marginTop
        int top = 0 + params.topMargin;
        // 获取行数
        int lineCount = mAllChildViews.size();
        for (int i = 0; i < lineCount; i++) {
            // 当前行的views和高度
            lineViews = mAllChildViews.get(i);
            lineHeight = mLineHeight.get(i);
            for (int j = 0; j < lineViews.size(); j++) {
                // 为每一列设置marginLeft
                if (j == 0) {
                    left = 0 + params.leftMargin;
                }
                View child = lineViews.get(j);
                // 判断是否显示
                if (child.getVisibility() == View.GONE) {
                    continue;
                }
                MarginLayoutParams lp = (MarginLayoutParams) child
                        .getLayoutParams();
                int cLeft = left + lp.leftMargin;
                int cTop = top + lp.topMargin;
                int cRight = cLeft + child.getMeasuredWidth();
                int cBottom = cTop + child.getMeasuredHeight();
                // 进行子View进行布局
                child.layout(cLeft, cTop, cRight, cBottom);
                left += child.getMeasuredWidth() + lp.leftMargin
                        + lp.rightMargin;
            }
            left = 0;
            top += lineHeight;
        }
    }

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

        // 父控件传进来的宽度和高度以及对应的测量模式
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        // 如果当前ViewGroup的宽高为wrap_content的情况
        int width = 0;// 自己测量的 宽度
        int height = 0;// 自己测量的高度
        // 记录每一行的宽度和高度
        int lineWidth = 0;
        int lineHeight = 0;

        // 获取子view的个数
        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            // 测量子View的宽和高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 得到LayoutParams

            MarginLayoutParams params = (MarginLayoutParams) child
                    .getLayoutParams();
            // 子View占据的宽度
            int childWidth = child.getMeasuredWidth() + params.leftMargin
                    + params.rightMargin;
            // 子View占据的高度
            int childHeight = child.getMeasuredHeight() + params.bottomMargin
                    + params.topMargin;
            // 换行时候
            if (lineWidth + childWidth > sizeWidth) {
                // 对比得到最大的宽度
                width = Math.max(width, lineWidth);
                // 重置lineWidth
                lineWidth = childWidth;
                // 记录行高
                height += lineHeight;
                lineHeight = childHeight;
            } else {
                // 不换行情况
                // 叠加行宽
                lineWidth += childWidth;
                // 得到最大行高
                lineHeight = Math.max(lineHeight, childHeight);
            }
            // 处理最后一个子View的情况
            if (i == childCount - 1) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
        }
        // 这里就是关键代码
        setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth
                : width, modeHeight == MeasureSpec.EXACTLY ? sizeHeight
                : height);

    }

    /**
     * 与当前ViewGroup对应的LayoutParams
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }
}
AI Agent驱动的多模态情感分析方法:从BERT到Vision Transformer的探索 情感分析(Sentiment Analysis)是自然语言处理(NLP)与计算机视觉(CV)中一项重要任务。传统的情感分析主要依赖于文本数据,但在现实应用中,情感往往通过 语言、语音、表情、姿态 等多模态信号共同传达。因此,结合 多模态深度学习 的 AI Agent 在情感理解中具有广阔的前景。 阅读详情

相关推荐

事件相机(Event Camera)及相关研究简介——新一代相机?新的计算机视觉领域?

事件相机(Event Camera)及相关研究简介 事件相机是一个新型的类脑相机,是一个待开发的大领域。希望通过我的分享,能够有更多人来研究并将它更好地运用在我们的生活中~

Cyril的博客 5万+

Android View原理浅析——View的工作原理

Android View原理浅析——View的工作原理 下图是Android的UI管理系统的层级关系。 PhoneWindow是Android系统中最基本的窗口系统,继承自Windows类,负责管理界面显示以及事件响应。它是Activity与View系统交互的接口 DecorView是PhoneWindow中的起始节点View,继承于View类,作为整个视图容器来使用。用于设置窗口属性。它本质...

N0tExpecterr0r的博客 1万+

Python与numpy版本兼容性排查指南

本文详细解析了Python与numpy版本兼容性问题,提供了快速诊断和解决版本冲突的实用方法。通过查看版本信息、使用官方兼容性对照表以及降级或升级环境等方案,帮助开发者有效避免常见的报错问题,如'ImportError'和'AttributeError'。文章还介绍了使用虚拟环境和requirements.txt等工程实践,确保项目依赖的稳定性。

weixin_29089057的博客 321

浅析Android中View的硬件绘制流程

硬件绘制在主线程主要负责记录绘制命令并同步绘制命令,而软件绘制则是在主线程完成绘制命令的处理,生成最终的数据;硬件绘制通过RenderNode记录当前View是否需要重新构建来减少不必要的绘制处理,而软件绘制则会对所有View重新进行绘制;硬件绘制引入渲染线程降低主线程的压力,软件绘制则是在主线程完成所有的绘制命令处理;下面给出了梳理后的硬件绘制的主要结构图,用于帮助整体上理解和掌握硬件绘制流程。

boker_han的博客 1589

LK32T102 单片机 操作GPIO实现流水灯(1)

前言: LK32T10232(32位)单片机是一款专为电机控制器和数字电源开发的高性能 MCU。采Cortex-M0 内核,最高工作频率可达 72Mhz,ADC 采样速率 达 1Msps。同时具备协处理器单元可满足各种乘法、除法、移位以及各种 复杂函数运算。 LK32T102(32位)支持单电源供电,且内嵌高精度高速及低速振荡器,以及具备 多种低功耗工作模式。同时,集成了多路增强型 PWM,多通道模拟比较 器和高速运算放大器,可满足多种电机及功率控制应用,并简化系统成本。...

qq_58676187的博客 3190

【5G NB-IoT NTN】3GPP R17 NB-IoT NTN介绍_nbiot ntn

你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!” />你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!更多资料点击此处获qu!!

2401_83703866的博客 3170

浅析Android View的测量布局流程

从测量流程的分析中,总结出自定义View需要注意的事项:自定义View的从上面的源码分析中可以得知,onMeasure的默认实现是将等同于进行处理,因此自定义View时需要注意对这种情况的处理。解决办法是通过对onMeasure方法进行重写,实现对模式的特殊处理,即判断布局参数是否为,是的话则调用方法并传入具体数值。自定义View的padding参数通过上面源码的分析可以知道,容器类型的View比如都会通过ViewGroup的方法将自身的padding和子View的margin。

boker_han的博客 1355

【Flutter 面试题】 怎么减少Widget的重新构建?

关于 const 关键字的使用:在 Flutter 开发中,任何时候当你确定一个 Widget 在其整个生命周期内都不会发生变化时,使用 const 关键字声明这个 Widget。这样的声明告诉 Flutter 框架这个 Widget 可以在编译时生成,后续的构建周期中可以重用已经创建的 Widget,从而避免了重复的构建开销。这适用于静态文本、图标或整个不变的 Widget 树。

Code Metaverse 1577

【Android】浅析View.post()

View.post()能够获取视图宽高的核心原理在于任务执行的时机控制。当View尚未附着到窗口时(mAttachInfo为null),任务会被暂存在HandlerActionQueue中;一旦View完成附着(dispatchAttachedToWindow被调用),这些任务会通过主线程Handler执行,此时视图已完成测量布局流程,因此能够获取准确宽高。

2301_79344902的博客 1486

数据仓库从明细到汇总:DWD与DWS层建模实战指南

DWS层的“主题”是指业务分析的核心场景,通常分为“用户主题、商品主题、订单主题、营销主题”等。主题的定义需紧密结合业务需求,避免“为了汇总而汇总”。例如,电商平台的核心业务需求包括“用户活跃度分析”“商品销售情况分析”“订单转化分析”,对应的DWS层主题即可定义为“用户主题汇总表”“商品主题汇总表”“订单主题汇总表”。主题定义时需注意“粒度适中”——汇总粒度既不能过细(否则与DWD层重复,失去汇总意义),也不能过粗(否则无法满足细分分析需求)。

2401_83088008的博客 646

【推荐100个unity插件之34】在unity中实现和Live2D虚拟人物的交互——Cubism SDK for Unity

Live2D是一款2D建模的软件,常用于游戏,动态壁纸,虚拟主播,游戏公司里面是有专门的ive2D岗位的。游戏里面还有一个经常使用的软件——spine,他做的也是2d的模型,但是他更适合做一些动作,Live2D更适合人物交互方面。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 5000

浅析Android View的软件绘制流程

前言 在《浅析Android中View的测量布局流程》中分析到,VSYNC信号到达App进程之后开启了View布局过程,经过对整个View树遍历完成测量和布局,最终确定所有View的大小以及在屏幕中所处的位置。但是View的内容想要在屏幕上可见,还需要经过绘制渲染来生成图形数据并交由硬件来刷新屏幕才可以。 在《浅析Android View绘制过程中的Surface》中分析到,View绘制渲染生成的图形数据需要在进程间传输,最终才能完成合成上屏。基于跨进程的数据传输,生产者(通常是App进程)生产图形数据并交

boker_han的博客 1525

结构光之相移法+多频外差的数学原理推导

‍论文:基于数字光栅投影的结构光三维测量技术与系统研究 [博]作者:李中伟 华中科技大学 2007功能:相移法:多频外差(三频N步相移法)01 相移法图1 待写入投影仪的理想N步相移条纹通常对于 步相移来说,生成第 幅条纹图像(如图1所示)的公式如下:402 Payment Required其中::背景光强:调制强度:像素点横向坐标:相移值图2 相机拍摄到的实际 N ......

3D视觉工坊 1190

Android View原理浅析——View的事件体系

Android View原理浅析——View的事件体系 View是什么 首先我们需要理解什么是View。View是安卓中所有控件的基类,无论是简单的TextView、Button,还是复杂的LinearLayout、ListView,它们的共同基类都是View,ViewGroup其实也是继承了View。Button是一个View,而LinearLayout是View的同时也是ViewGroup,V...

N0tExpecterr0r的博客 413

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

浅析Android View(一)

Android中的View是Android开发中一个非常重要的知识点,系统提供的基础控件TextView,Button,ListView,ViewGroup等都是基于View。但是很多时候的UI效果,利用系统的提供的基本组件无法满足我们的需求,这时候就需要我们自己自定义组件,然而想要随心所遇的写出满足我们需求的组件,却不是那么容易,但也没大家想象的那么难,只要我们掌握的View的基本原理和相关知识,

源码有毒的专栏 7189

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

浅析Android View绘制过程中的Surface

首先,当App进程的View的测量数据发生变化时,会导致窗体大小发生变化,此时会调用请求进程的更新窗体的大小,进程通过创建Java层的对象,对应App进程中Java层的对象;进程创建的Java层的对象是一个壳,其内部会通过JNI调用到native层,创建native层的对象,并将native层的对象的句柄值拷贝到App进程的对象;在native层的对象创建的过程中会通过请求到进程,调用创建Layer对象,并将Layer对象的关键信息返回给进程,并用于构造native层的对象;接着,当App。

boker_han的博客 1577

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

【Android】浅析View的事件分发机制

《Android 进阶之光》学习笔记

LiuYaoheng的博客 1189

View框架浅析

参考文章:http://www.jianshu.com/p/a3014f8442b0 整体View框架 控件主要分为两类,一类是View,一类是ViewGroup 如下是View的一些原理 所有的View都是矩形的 View是不能添加子View的,ViewGroup可以 Activity之所以能加载并且控制View,是因为它包含了一个Window,所有的图形化界面都是由

蜕变的快乐 5689

Android View 的工作原理浅析

  在 Android 的知识体系中,View 占据非常重要的作用,简单来理解,View 是 Android 在视觉上的呈现。为了更好的自定义控件,也就是自定义 View ,我们还需要掌握 View 的底层原理。在 Android 系统中 UI 显示的 3 要是是尺寸大小、位置和内容,分别对应于 View 的 measure、layout和 draw过程。 1. 基础概念 View 和 Vie...

Tomdogs的专栏 535

浅析Android View内部工作原理及其实战

转载请声明出处:http://blog.csdn.net/andrexpert/article/details/77511996      在Android开发中,当我们需要显示用户交互界面时,通常的做法是创建一个继承Activity的类并重写它的onCreate()方法,再在该方法中调用setContentView()方法将布局界面显示出来。那么问题来了,setContentView方法具体做...

老蒋也疯狂 4263

浅析Android View的Measure过程

我们知道,View从创建到展示给用户包含了三大过程:measure过程、layout过程、draw过程,即测量、布局、绘制。而measure过程则是其中之首,它测量并确定了view的宽高。现总结下view的measure过程。 View的measure过程包含了两个核心方法: onMeasure() 方法: protected void onMeasure(i

泥土的博客 471

Android浅析:关于View的绘制流程

今天咱们来聊聊Android的绘制流程,从大方向讲,View是在何处开始被绘制的?从具体步骤看,View的具体绘制流程是咋样的?

m0_64420071的博客 525

UETools源码浅析

期间无意看到了饿了吗大神写的UETools工具,感觉很神奇,于是就翻了下代码看看如何做的,下面是源码浅析,自己也试着写了下相关代码封装了一个适合自己用的库,如有不对请在评论区留言指出,谢谢! 这里我只是浅析了下UETools代码是如何实现的,如果想看饿了吗大神UETools的源码,请移步https://github.com/eleme/UETool 饿了吗这个库主要是实现当前界面view基本信息的...

Boys博客 980

Android View原理浅析——View的事件分发机制

Android View原理浅析——View的事件分发机制 事件分发机制是View的一个核心知识点。它也是解决滑动冲突的一个理论基础。因此掌握好View的事件分发机制是十分重要的。 点击事件的传递规则 首先要明白,这里要分析的对象就是MotionEvent,即点击事件。点击事件的分发,实际就是MotionEvent的分发过程。MotionEvent产生后,系统需要将这个事件传递给一个具体的View...

N0tExpecterr0r的博客 364

android View——事件分发

介绍 示例 源码浅析 对于Android中的事件,最常见到的就是点击事件。那么点击在屏幕上,最终怎么响应在设置的控件上的呢? 三个重要的方法 了解这些需要先知道三个方法 dispatchTouchEvent 此方法View和ViewGroup中都有。 public boolean dispatchTouchEvent (MotionEvent event) 将触摸屏运动事件传递给目标视图...

android_hl的博客 279

Android 源码 listview 重用机制 浅析

listview 复用机制机制 大体上也就说的差不多了; 今后 写类似view回收池时可以参考Listview$RecycleBin的写法

昊邡的博客 1336
上一篇: 深入理解Android View(二)
下一篇: Andorid5.0原生下拉刷新简单使用
coley-wu
博客等级 码龄12年 859粉丝 112原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值