UGUI源代码之Text—实现自定义的字间距属性
以下内容是根据Unity 2020.1.01f版本进行编写的
UGUI源代码之Text—实现自定义的字间距属性
- 1、目的
- 2、参考
- 3、代码阅读
- 4、准备修改UGUI源代码
- 5、实现自定义Text组件,增加字间距属性
- 6、最终效果
1、目的
很多时候,美术在设计的时候是想要使用文本的字间距属性的,但是UGUI的Text组件并不支持字间距属性,因此想要自己实现一个
但是实现Text组件的最核心代码,Unity并没有公开出来,但是通过查看NGUI的Label控件的源代码,可以看出UGUI实现Text的核心方法
2、参考
本文参考Unity官方的UGUI源代码,以及NGUI插件
Github地址:https://github.com/Unity-Technologies/uGUI
3、代码阅读
首先看下UGUI的Text类,继承Graphic类的类一般都是通过通用OnPopulateMesh方法生成对应的Mesh的,所以直接看Text的OnPopulateMesh方法:
protected override void OnPopulateMesh(VertexHelper toFill)
{if (font == null)return;// We don't care if we the font Texture changes while we are doing our Update.// The end result of cachedTextGenerator will be valid for this instance.// Otherwise we can get issues like Case 619238.m_DisableFontTextureRebuiltCallback = true;Vector2 extents = rectTransform.rect.size;var settings = GetGenerationSettings(extents);cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);// Apply the offset to the verticesIList<UIVertex> verts = cachedTextGenerator.verts;float unitsPerPixel = 1 / pixelsPerUnit;int vertCount = verts.Count;// We have no verts to process just return (case 1037923)if (vertCount <= 0){toFill.Clear();return;}Vector2 roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel;roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset;toFill.Clear();if (roundingOffset != Vector2.zero){for (int i = 0; i < vertCount; ++i){int tempVertsIndex = i & 3;m_TempVerts[tempVertsIndex] = verts[i];m_TempVerts[tempVertsIndex].position *= unitsPerPixel;m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;if (tempVertsIndex == 3)toFill.AddUIVertexQuad(m_TempVerts);}}else{for (int i = 0; i < vertCount; ++i){int tempVertsIndex = i & 3;m_TempVerts[tempVertsIndex] = verts[i];m_TempVerts[tempVertsIndex].position *= unitsPerPixel;if (tempVertsIndex == 3)toFill.AddUIVertexQuad(m_TempVerts);}}m_DisableFontTextureRebuiltCallback = false;
}
从上面的代码可以看出,生成文本Mesh的Vertex是通过cachedTextGenerator.PopulateWithErrors的函数生成的,此函数的3个参数分别是文本的内容(string类型),Text组件的设置,Text所在的gameObject。那么这个cachedTextGenerator是啥呢,F12导航到定义此属性:
public TextGenerator cachedTextGenerator
{get { return m_TextCache ?? (m_TextCache = (m_Text.Length != 0 ? new TextGenerator(m_Text.Length) : new TextGenerator())); }
}
cachedTextGenerator这个属性是一个TextGenerator类,再次按F12导航到TextGenerator类(部分):
//
// 摘要:
// Will generate the vertices and other data for the given string with the given
// settings.
//
// 参数:
// str:
// String to generate.
//
// settings:
// Generation settings.
//
// context:
// The object used as context of the error log message, if necessary.
//
// 返回结果:
// True if the generation is a success, false otherwise.
public bool PopulateWithErrors(string str, TextGenerationSettings settings, GameObject context)
{TextGenerationError textGenerationError = PopulateWithError(str, settings);if (textGenerationError == TextGenerationError.None){return true;}if ((textGenerationError & TextGenerationError.CustomSizeOnNonDynamicFont) != 0){Debug.LogErrorFormat(context, "Font '{0}' is not dynamic, which is required to override its size", settings.font);}if ((textGenerationError & TextGenerationError.CustomStyleOnNonDynamicFont) != 0){Debug.LogErrorFormat(context, "Font '{0}' is not dynamic, which is required to override its style", settings.font);}return false;
}
跳转后发现这个类是dll里的类,无法直接查看函数的内容,其中以上代码是Text组件所使用到的PopulateWithErrors函数的定义
关于UGUI的Text组件实现过程在这里就断了,但是NGUI是UGUI的前身,Unity开发团队将NGUI的开发团队收到自己开发团队下,并且由此开发了UGUI。
因此可以想着可以通过查看NGUI对应UGUI的Text类的代码,了解UGUI实现Text的过程,从而让Text实现更多的功能
说干就干,新建项目导入NGUI插件,粗略看了NGUI的控件,估计Label类就是对应UGUI的Text类,生成Text的代码如下:
/// <summary>
/// Process the raw text, called when something changes.
/// </summary>public void ProcessText (bool legacyMode = false, bool full = true)
{if (!isValid) return;mChanged = true;shouldBeProcessed = false;float regionX = mDrawRegion.z - mDrawRegion.x;float regionY = mDrawRegion.w - mDrawRegion.y;NGUIText.rectWidth = legacyMode ? (mMaxLineWidth != 0 ? mMaxLineWidth : 1000000) : width;NGUIText.rectHeight = legacyMode ? (mMaxLineHeight != 0 ? mMaxLineHeight : 1000000) : height;NGUIText.regionWidth = (regionX != 1f) ? Mathf.RoundToInt(NGUIText.rectWidth * regionX) : NGUIText.rectWidth;NGUIText.regionHeight = (regionY != 1f) ? Mathf.RoundToInt(NGUIText.rectHeight * regionY) : NGUIText.rectHeight;mFinalFontSize = Mathf.Abs(legacyMode ? Mathf.RoundToInt(cachedTransform.localScale.x) : defaultFontSize);mScale = 1f;if (NGUIText.regionWidth < 1 || NGUIText.regionHeight < 0){mProcessedText = "";return;}bool isDynamic = (trueTypeFont != null);if (isDynamic && keepCrisp){UIRoot rt = root;if (rt != null) mDensity = (rt != null) ? rt.pixelSizeAdjustment : 1f;}else mDensity = 1f;if (full) UpdateNGUIText();if (mOverflow == Overflow.ResizeFreely){NGUIText.rectWidth = 1000000;NGUIText.regionWidth = 1000000;if (mOverflowWidth > 0){NGUIText.rectWidth = Mathf.Min(NGUIText.rectWidth, mOverflowWidth);NGUIText.regionWidth = Mathf.Min(NGUIText.regionWidth, mOverflowWidth);}}if (mOverflow == Overflow.ResizeFreely || mOverflow == Overflow.ResizeHeight){NGUIText.rectHeight = 1000000;NGUIText.regionHeight = 1000000;}if (mFinalFontSize > 0){bool adjustSize = keepCrisp;for (int ps = mFinalFontSize; ps > 0; --ps){// Adjust either the size, or the scaleif (adjustSize){mFinalFontSize = ps;NGUIText.fontSize = mFinalFontSize;}else{mScale = (float)ps / mFinalFontSize;NGUIText.fontScale = isDynamic ? mScale : ((float)mFontSize / mFont.defaultSize) * mScale;}NGUIText.Update(false);// Wrap the textbool fits = NGUIText.WrapText(printedText, out mProcessedText, false, false, mOverflowEllipsis);if (mOverflow == Overflow.ShrinkContent && !fits){if (--ps > 1) continue;else break;}else if (mOverflow == Overflow.ResizeFreely){mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);int w = Mathf.Max(minWidth, Mathf.RoundToInt(mCalculatedSize.x));if (regionX != 1f) w = Mathf.RoundToInt(w / regionX);int h = Mathf.Max(minHeight, Mathf.RoundToInt(mCalculatedSize.y));if (regionY != 1f) h = Mathf.RoundToInt(h / regionY);if ((w & 1) == 1) ++w;if ((h & 1) == 1) ++h;if (mWidth != w || mHeight != h){mWidth = w;mHeight = h;if (onChange != null) onChange();}}else if (mOverflow == Overflow.ResizeHeight){mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);int h = Mathf.Max(minHeight, Mathf.RoundToInt(mCalculatedSize.y));if (regionY != 1f) h = Mathf.RoundToInt(h / regionY);if ((h & 1) == 1) ++h;if (mHeight != h){mHeight = h;if (onChange != null) onChange();}}else{mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);}// Upgrade to the new systemif (legacyMode){width = Mathf.RoundToInt(mCalculatedSize.x);height = Mathf.RoundToInt(mCalculatedSize.y);cachedTransform.localScale = Vector3.one;}break;}}else{cachedTransform.localScale = Vector3.one;mProcessedText = "";mScale = 1f;}if (full){NGUIText.bitmapFont = null;NGUIText.dynamicFont = null;}
}
从代码可以看出,函数一开始基本都是对一些设置的参数进行读取,计算等,生成文本的函数可能是UpdateNGUIText,NGUIText.Update
先看UpdateNGUIText的代码:
public void UpdateNGUIText ()
{Font ttf = trueTypeFont;bool isDynamic = (ttf != null);NGUIText.fontSize = mFinalFontSize;NGUIText.fontStyle = mFontStyle;NGUIText.rectWidth = mWidth;NGUIText.rectHeight = mHeight;NGUIText.regionWidth = Mathf.RoundToInt(mWidth * (mDrawRegion.z - mDrawRegion.x));NGUIText.regionHeight = Mathf.RoundToInt(mHeight * (mDrawRegion.w - mDrawRegion.y));NGUIText.gradient = mApplyGradient && (mFont == null || !mFont.packedFontShader);NGUIText.gradientTop = mGradientTop;NGUIText.gradientBottom = mGradientBottom;NGUIText.encoding = mEncoding;NGUIText.premultiply = mPremultiply;NGUIText.symbolStyle = mSymbols;NGUIText.maxLines = mMaxLineCount;NGUIText.spacingX = effectiveSpacingX;NGUIText.spacingY = effectiveSpacingY;NGUIText.fontScale = isDynamic ? mScale : ((float)mFontSize / mFont.defaultSize) * mScale;if (mFont != null){NGUIText.bitmapFont = mFont;for (; ; ){UIFont fnt = NGUIText.bitmapFont.replacement;if (fnt == null) break;NGUIText.bitmapFont = fnt;}if (NGUIText.bitmapFont.isDynamic){NGUIText.dynamicFont = NGUIText.bitmapFont.dynamicFont;NGUIText.bitmapFont = null;}else NGUIText.dynamicFont = null;}else{NGUIText.dynamicFont = ttf;NGUIText.bitmapFont = null;}if (isDynamic && keepCrisp){UIRoot rt = root;if (rt != null) NGUIText.pixelDensity = (rt != null) ? rt.pixelSizeAdjustment : 1f;}else NGUIText.pixelDensity = 1f;if (mDensity != NGUIText.pixelDensity){ProcessText(false, false);NGUIText.rectWidth = mWidth;NGUIText.rectHeight = mHeight;NGUIText.regionWidth = Mathf.RoundToInt(mWidth * (mDrawRegion.z - mDrawRegion.x));NGUIText.regionHeight = Mathf.RoundToInt(mHeight * (mDrawRegion.w - mDrawRegion.y));}if (alignment == Alignment.Automatic){Pivot p = pivot;if (p == Pivot.Left || p == Pivot.TopLeft || p == Pivot.BottomLeft){NGUIText.alignment = Alignment.Left;}else if (p == Pivot.Right || p == Pivot.TopRight || p == Pivot.BottomRight){NGUIText.alignment = Alignment.Right;}else NGUIText.alignment = Alignment.Center;}else NGUIText.alignment = alignment;NGUIText.Update();
}
从代码可以看出,此函数主要是读取当前文本控件的设置,并将这些设置赋值到NGUIText静态类中对应的属性上,最后也是会调用NGUIText.Update函数
因此,查看NGUIText.Update函数的代码:
static public void Update () { Update(true); }/// <summary>
/// Recalculate the 'final' values.
/// </summary>static public void Update (bool request)
{finalSize = Mathf.RoundToInt(fontSize / pixelDensity);finalSpacingX = spacingX * fontScale;finalLineHeight = (fontSize + spacingY) * fontScale;useSymbols = (dynamicFont != null || bitmapFont != null) && encoding && symbolStyle != SymbolStyle.None;#if DYNAMIC_FONTFont font = dynamicFont;if (font != null && request){font.RequestCharactersInTexture(")_-", finalSize, fontStyle);#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7if (!font.GetCharacterInfo(')', out mTempChar, finalSize, fontStyle) || mTempChar.vert.height == 0f){font.RequestCharactersInTexture("A", finalSize, fontStyle);{if (!font.GetCharacterInfo('A', out mTempChar, finalSize, fontStyle)){baseline = 0f;return;}}}float y0 = mTempChar.vert.yMax;float y1 = mTempChar.vert.yMin;
#elseif (!font.GetCharacterInfo(')', out mTempChar, finalSize, fontStyle) || mTempChar.maxY == 0f){font.RequestCharactersInTexture("A", finalSize, fontStyle);{if (!font.GetCharacterInfo('A', out mTempChar, finalSize, fontStyle)){baseline = 0f;return;}}}float y0 = mTempChar.maxY;float y1 = mTempChar.minY;
#endifbaseline = Mathf.Round(y0 + (finalSize - y0 + y1) * 0.5f);}
#endif
}
从代码可以看出,有两个函数可能是获取文字信息的:font.RequestCharactersInTexture,font.GetCharacterInfo
但是,鼠标悬停RequestCharactersInTexture函数发现其没有返回值(如上图),根据函数名猜测此函数是请求函数的第一个参数中的字符串,根据字符串在当前Label控件材质球的Texture上生成对应的字符
然后再看GetCharacterInfo,看到其中有out的参数,并且有返回值,根据函数名猜测是根据参数中的字符,字号以及字体样式返回该字符的参数,并将这些参数保存到一个叫做CharacterInfo的类中
跳转到GetCharacterInfo类查看代码:
[MethodImpl(MethodImplOptions.InternalCall)]
[FreeFunction("TextRenderingPrivate::GetCharacterInfo", HasExplicitThis = true)]
public extern bool GetCharacterInfo(char ch, out CharacterInfo info, [DefaultValue("0")] int size, [DefaultValue("FontStyle.Normal")] FontStyle style);
发现这是一个dll文件里的类,无法查看里面的代码
那就看看CharacterInfo类有什么属性是可以使用的
CharacterInfo类代码(为了方便看改成截图形式):
可以看出,CharacterInfo类也是dll文件里的类,但是里面有不少属性是public的,可以使用
逐个属性查看之后,发现有一个叫advance的属性:
//
// 摘要:
// The horizontal distance, rounded to the nearest integer, from the origin of this
// character to the origin of the next character.
public int advance
{get{return (int)Math.Round(width, MidpointRounding.AwayFromZero);}set{width = value;}
}
此属性是当前字符的水平距离,四舍五入到最接近的整数,从该点的原点算起。此字符到下一个字符的原点。大致意思就是字符的宽度。
根据这个属性,就可以实现Text的字间距属性了。
Text显示出文字的根本是通过Vertex顶点生成Mesh,生成Mesh后再通过Canvas Renderer渲染出来。所以我们只需要计算位置,纹理和色值都正确的Vertex顶点,并通过顶点生成Mesh,就可以实现文本了
4、准备修改UGUI源代码
请看这篇:UGUI源代码之修改源代码的前期准备
已经准备过的同学可以跳过
5、实现自定义Text组件,增加字间距属性
新建一个C#脚本,重命名为TextSpacing,并使其继承Text类,为其增加一个调整字间距的属性,然后重写OnPopulateMesh方法就可以了,上代码:
[SerializeField] private float m_characterSpacing = 0f;protected override void OnPopulateMesh(VertexHelper toFill)
{if (font == null)return;if (text.Length <= 0){toFill.Clear();return;}// We don't care if we the font Texture changes while we are doing our Update.// The end result of cachedTextGenerator will be valid for this instance.// Otherwise we can get issues like Case 619238.m_DisableFontTextureRebuiltCallback = true;Vector2 extents = rectTransform.rect.size;var settings = GetGenerationSettings(extents);cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);//-------------------------------------------------------------------------------- toFill.Clear();float currentLineTotalWidth = 0f;float currentTotalHeight = 0f;int lineCount = 1;Vector3 startPos = Vector3.zero;List<float> totalWidthtList = new List<float>();//定义字间距向量Vector3 characterSpacingVector = new Vector3(m_characterSpacing, 0, 0);font.RequestCharactersInTexture(text, fontSize, fontStyle);CharacterInfo ch_firstChar;font.GetCharacterInfo(text[0], out ch_firstChar,fontSize, fontStyle);currentLineTotalWidth = ch_firstChar.advance;currentTotalHeight = fontSize;if(rectTransform.sizeDelta.y < currentTotalHeight){return;}List<CharacterInfo> characterInfoList = new List<CharacterInfo>();characterInfoList.Add(ch_firstChar);for (int i = 0;i < text.Length;i++){if (i + 1 < text.Length){CharacterInfo next_ch;font.GetCharacterInfo(text[i + 1], out next_ch, fontSize, fontStyle);characterInfoList.Add(next_ch);if (text[i] == '\n'){lineCount++;totalWidthtList.Add(currentLineTotalWidth);currentLineTotalWidth = next_ch.advance;currentTotalHeight += lineSpacing + fontSize;if (verticalOverflow == VerticalWrapMode.Truncate && currentTotalHeight > rectTransform.sizeDelta.y){break;}continue;}//自动换行if (horizontalOverflow == HorizontalWrapMode.Wrap && (currentLineTotalWidth + next_ch.advance + m_characterSpacing) > rectTransform.sizeDelta.x){lineCount++;totalWidthtList.Add(currentLineTotalWidth);currentLineTotalWidth = next_ch.advance;currentTotalHeight += lineSpacing + fontSize;if (verticalOverflow == VerticalWrapMode.Truncate && currentTotalHeight > rectTransform.sizeDelta.y){break;}}else{if (!(text[i + 1] == '\n')){currentLineTotalWidth += next_ch.advance + m_characterSpacing;}}}else{if (text[i] == '\n'){lineCount++;totalWidthtList.Add(currentLineTotalWidth);currentLineTotalWidth = 0;continue;}}}//加上最后一行的字符宽度totalWidthtList.Add(currentLineTotalWidth);//重置部分属性lineCount = 1;currentLineTotalWidth = ch_firstChar.advance;currentTotalHeight = fontSize;startPos = GetStartPosition(lineCount, totalWidthtList);for (int i = 0; i < text.Length; i++){CharacterInfo ch = characterInfoList[i];UIVertex[] vertices = new UIVertex[4];vertices[0] = UIVertex.simpleVert;vertices[1] = UIVertex.simpleVert;vertices[2] = UIVertex.simpleVert;vertices[3] = UIVertex.simpleVert;vertices[0].position = startPos + new Vector3(ch.minX, ch.maxY, 0);vertices[1].position = startPos + new Vector3(ch.maxX, ch.maxY, 0);vertices[2].position = startPos + new Vector3(ch.maxX, ch.minY, 0);vertices[3].position = startPos + new Vector3(ch.minX, ch.minY, 0);//Vector2 adjustVector = Vector2.zero;Vector2 adjustVector = new Vector2(0, 0.00f);vertices[0].uv0 = ch.uvTopLeft + adjustVector;vertices[1].uv0 = ch.uvTopRight + adjustVector;vertices[2].uv0 = ch.uvBottomRight + adjustVector;vertices[3].uv0 = ch.uvBottomLeft + adjustVector;vertices[0].color = color;vertices[1].color = color;vertices[2].color = color;vertices[3].color = color;if (text[i] != '\n')toFill.AddUIVertexQuad(vertices);if (i + 1 < text.Length){CharacterInfo next_ch = characterInfoList[i + 1];//适应换行符,如果当前字符是换行符,则直接进行换行操作if (text[i] == '\n'){lineCount++;currentLineTotalWidth = next_ch.advance;startPos = GetStartPosition(lineCount, totalWidthtList);currentTotalHeight += lineSpacing + fontSize;if (verticalOverflow == VerticalWrapMode.Truncate && currentTotalHeight > rectTransform.sizeDelta.y){break;}continue;}//自动换行if (horizontalOverflow == HorizontalWrapMode.Wrap && (currentLineTotalWidth + next_ch.advance + m_characterSpacing) > rectTransform.sizeDelta.x){lineCount++;currentLineTotalWidth = next_ch.advance;startPos = GetStartPosition(lineCount, totalWidthtList);currentTotalHeight += lineSpacing + fontSize;if (verticalOverflow == VerticalWrapMode.Truncate && currentTotalHeight > rectTransform.sizeDelta.y){break;}}else{startPos += new Vector3(ch.advance, 0, 0) + characterSpacingVector;//适应换行符,如果下一个字符是换行符,则不需要增加当前行的字符总宽度if (!(text[i + 1] == '\n')){currentLineTotalWidth += next_ch.advance + m_characterSpacing;}}}else{if (text[i] == '\n'){lineCount++;currentLineTotalWidth = 0;continue;}}}m_DisableFontTextureRebuiltCallback = false;
}private Vector3 GetStartPosition(int lineCount, List<float> totalWidthtList)
{int totalLineCount = totalWidthtList.Count;float leftPosInRect = rectTransform.rect.xMin;float upPosInRect = rectTransform.rect.yMax;float halfLineWidth = totalWidthtList[lineCount - 1] / 2;float rectTransformWidth = rectTransform.sizeDelta.x;float rectTransformHeight = rectTransform.sizeDelta.y;switch (alignment){case TextAnchor.UpperLeft:return new Vector3(leftPosInRect, upPosInRect - fontSize * lineCount - lineSpacing * (lineCount - 1), 0);case TextAnchor.UpperCenter:return new Vector3(-halfLineWidth, upPosInRect - fontSize * lineCount - lineSpacing * (lineCount - 1), 0);case TextAnchor.UpperRight:return new Vector3(leftPosInRect + rectTransformWidth - totalWidthtList[lineCount - 1], upPosInRect - fontSize * lineCount - lineSpacing * (lineCount - 1), 0);case TextAnchor.MiddleLeft:return new Vector3(leftPosInRect, (fontSize * totalLineCount + lineSpacing * (totalLineCount - 1)) / 2 - fontSize * lineCount, 0);case TextAnchor.MiddleCenter:return new Vector3(-halfLineWidth, (fontSize * totalLineCount + lineSpacing * (totalLineCount - 1)) / 2 - fontSize * lineCount, 0);case TextAnchor.MiddleRight:return new Vector3(leftPosInRect + rectTransformWidth - totalWidthtList[lineCount - 1], (fontSize * totalLineCount + lineSpacing * (totalLineCount - 1)) / 2 - fontSize * lineCount, 0);case TextAnchor.LowerLeft:return new Vector3(leftPosInRect, upPosInRect - fontSize * lineCount + (fontSize * totalLineCount - rectTransformHeight) + lineSpacing * (totalLineCount - lineCount + 1), 0);case TextAnchor.LowerCenter:return new Vector3(-halfLineWidth, upPosInRect - fontSize * lineCount + (fontSize * totalLineCount - rectTransformHeight) + lineSpacing * (totalLineCount - lineCount + 1), 0);case TextAnchor.LowerRight:return new Vector3(leftPosInRect + rectTransformWidth - totalWidthtList[lineCount - 1], upPosInRect - fontSize * lineCount + (fontSize * totalLineCount - rectTransformHeight) + lineSpacing * (totalLineCount - lineCount + 1), 0);default:return Vector3.zero;}
}
这里增加了一个调整字间距的属性m_characterSpacing,为了让这个属性显示在Inspector面板,需要为这个脚本增加一个对应的Editor脚本,这里不贴代码了。
实现的逻辑其实很简单,就是对于每个需要显示的字符,根据其所需的宽度,重新算一遍其所在的位置,如果当前是自动换行并且宽度不够,那么就把行数加一,重新在起始的X轴位置生成下一行的字符,适配好换行符,并正确获取到不同对齐方式的起点位置。
看着很复杂,其实就是算对齐比较麻烦。
6、最终效果
大佬们找到问题欢迎拍砖~
相关文章:
UGUI源代码之Text—实现自定义的字间距属性
以下内容是根据Unity 2020.1.01f版本进行编写的 UGUI源代码之Text—实现自定义的字间距属性 1、目的2、参考3、代码阅读4、准备修改UGUI源代码5、实现自定义Text组件,增加字间距属性6、最终效果 1、目的 很多时候,美术在设计的时候是想要使用文本的字间…...
【AI】MCP概念
一文讲透 MCP(附 Apifox MCP Server 内测邀请) 7分钟讲清楚MCP是什么?统一Function calling规范,工作量锐减至1/6,人人手搓Manus!? | 一键链接千台服务器,几行代码接入海量外部工具…...
HarmonyOS:使用geoLocationManager (位置服务)获取位置信息
一、简介 位置服务提供GNSS定位、网络定位(蜂窝基站、WLAN、蓝牙定位技术)、地理编码、逆地理编码、国家码和地理围栏等基本功能。 使用位置服务时请打开设备“位置”开关。如果“位置”开关关闭并且代码未设置捕获异常,可能导致应用异常。 …...
深入解析原生鸿蒙中的 RN 日志系统:从入门到精通!
全文目录: 开篇语📖 目录🎯 前言:鸿蒙日志系统究竟有多重要?🛠️ 鸿蒙 RN 日志系统的基础结构📜 1. 日志的作用⚙️ 2. 日志分类 🔧 如何在鸿蒙 RN 中使用日志系统🖋️ 1…...
【前端】【Nuxt3】Nuxt3中usefetch,useAsyncData,$fetch使用与区别
一、Nuxt3 中不同数据获取方式的请求行为对比 (一)总结:请求行为一览 useFetch 和 useAsyncData 是 Nuxt 推荐的数据获取 API,自动集成 SSR 与客户端导航流程。$fetch 是更底层的请求方法,不具备自动触发、缓存等集成…...
【Linux系统】Linux基础指令
l i n u x linux linux 命令是对 L i n u x Linux Linux 系统进行管理的命令。对于 L i n u x Linux Linux 系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件, L i n u x Linux Linux 系统管理的命令是它正常运…...
Android中Jetpack设计理念、核心组件 和 实际价值
一、Jetpack 的定义与定位(基础必答) Jetpack 是 Google 推出的 Android 开发组件集合,旨在: 加速开发:提供标准化、开箱即用的组件 消除样板代码:解决传统开发中的重复劳动问题 兼容性保障:…...
flutter开发音乐APP(前提准备)
1、项目的一些环境: 2、接口文档: 酷狗音乐 NodeJS 版 API 3、接口数据结构化 Instantly parse JSON in any language | quicktype UI样式借鉴参考: Coffee-Expert/Apple-Music-New-UI: Apple Music Clone on Flutter, with redesigned UI…...
网络协议学习
最近在适配ESP32的网络驱动,借此机会先学习一下网络通信协议。 以太网帧、IP包及TCP与UDP的报文格式 提问腾讯元宝提示词: TCP窗口是干什么的拥塞窗口是什么的...
示波器直流耦合与交流耦合:何时使用哪种?
直流耦合和交流耦合的基本区别应该在于它们如何处理信号的直流分量和交流分量。直流分量是指信号中的固定电压部分,而交流分量则是信号中变化的电压部分。 例如,一个5V的直流电压叠加了一个1V的正弦波交流信号,整个信号会在4V到6V之间波动。如…...
js解除禁止复制、禁止鼠标右键效果
有的网页会禁止复制,甚至禁止鼠标右键,如何解决 按F12进入检查模式,在控制台输入下面的js代码 1.解除禁止复制 document.addEventListener(copy,function(event){event.stopImmediatePropagation();},true); 2.解除禁止鼠标右键 document…...
如何把未量化的 70B 大模型加载到笔记本电脑上运行?
并行运行 70B 大模型 我们已经看到,量化已经成为在低端 GPU(比如 Colab、Kaggle 等)上加载大型语言模型(LLMs)的最常见方法了,但这会降低准确性并增加幻觉现象。 那如果你和你的朋友们把一个大型语言模型分…...
xwiki的权限-页面特殊设置>用户权限>组权限
官方文档https://www.xwiki.org/xwiki/bin/view/Documentation/AdminGuide/Access%20Rights/ 他有组权限、用户权限、页面及子页面特别设置。 页面特殊设置 > 用户权限 > 组权限 XWiki提供了设置wiki范围内权限、细粒度页面级权限的能力,以及在需要更多控制的…...
Go语言比较递归和循环执行效率
一、概念 1.递归 递归是指一个函数在其定义中直接或间接调用自身的编程方法 。简单来说,就是函数自己调用自己。递归主要用于将复杂的问题分解为较小的、相同类型的子问题,通过不断缩小问题的规模,直到遇到一个最简单、最基础的情况&#x…...
Windows 图形显示驱动开发-WDDM 2.0功能_供应和回收更改
供应和回收更改 对于 Windows 显示驱动程序模型 (WDDM) v2,有关 套餐 和 回收 的要求正在放宽。 用户模式驱动程序不再需要在内部分配上使用套餐和回收。 空闲/挂起的应用程序将使用 Microsoft DirectX 11.1 中引入的 TrimAPI 删除驱动程序内部资源。 API 级别将继…...
MongoDB 新手笔记
MongoDB 新手笔记 1. MongoDB 1.1 概述 MongoDB 是一种 文档型数据库(NoSQL),数据以类似 JSON 的 BSON 格式存储,适合处理非结构化或半结构化数据。 对比 MySQL: MySQL 是关系型数据库,数据以表格形式存…...
Pytorch查看神经网络结构和参数量
基本方法 print(model) print(type(model))# 模型参数 numEl_list [p.numel() for p in model.parameters()] total_params_mb sum(numEl_list) / 1e6print(fTotal parameters: {total_params_mb:.2f} MB) # sum(numEl_list), numEl_list print(sum(numEl_list)) print(numE…...
Pytorch Dataset问题解决:数据集读取报错DatasetGenerationError或OSError
问题描述 在huggingface上下载很大的数据集,用多个parquet文件的格式下载到本地。使用load_dataset加载的时候,进度条加载到一半会报错DatasetGenerationError: An error occurred while generating the dataset;如果加载为IterableDataset&…...
学习OpenCV C++版
OpenCV C 1 数据载入、显示与保存1.1 概念1.2 Mat 类构造与赋值1.3 Mat 类的赋值1.4 Mat 类支持的运算1.5 图像的读取与显示1.6 视频加载与摄像头调用1.7 数据保存 参考:《OpenCV4快速入门》作者冯 振 郭延宁 吕跃勇 1 数据载入、显示与保存 1.1 概念 Mat 类 : Ma…...
特权FPGA之PS/2键盘解码
0 故事背景 见过这种接口的朋友们,大概都已经成家立业了吧。不过今天我们不讨论这种接口的历史,只讲讲这种接口的设计。(如果还没有成家的朋友也别生气,做自己想做的事情就对了!) 1 时序分析 数据帧格式如图…...
SpringBoot 接口限流Lua脚本接合Redis 服务熔断 自定义注解 接口保护
介绍 Spring Boot 接口限流是防止接口被频繁请求而导致服务器负载过重或服务崩溃的一种策略。通过限流,我们可以控制单位时间内允许的请求次数,确保系统的稳定性。限流可以帮助防止恶意请求、保护系统资源,并优化 API 的可用性,避…...
FPAG_BUFFER学习
在FPGA设计中,缓冲器(Buffer)是信号传输和管理的核心组件,用于处理输入/输出信号、时钟分配以及信号完整性。以下是FPGA中常见缓冲器的详细介绍,分类说明其功能、应用场景和设计注意事项: --- ### **1. 输…...
《认知觉醒》下篇·第六章第一节“清晰:一个观念,重构你的行动力” 总结
《认知觉醒》下篇第六章第一节“清晰:一个观念,重构你的行动力”的核心内容总结: 1. 清晰的力量:行动力的第一性原理 定义 清晰是对目标、路径和结果的明确认知,是破除拖延与内耗的核心前提。 模糊的代价: …...
idea手动创建resources文件夹
有时maven没有构建成功可能造成,resources文件夹不创建的现象 此时我们可以手动创建 手动创建...
Scala相关知识学习总结6
1、集合计算高级函数说明 - 过滤:遍历集合,提取满足特定条件的元素组成新集合。 - 转化/映射(map):将集合里的每个元素应用到指定函数进行转换。 - 扁平化:文档未详细阐述其具体含义和操作。 - 扁平化映射&…...
IDEA 调用 Generate 生成 Getter/Setter 快捷键
快捷键不会用? 快捷键:AltInsert 全选键:CtrlA IDEA 调用 Generate 生成 Getter/Setter 快捷键 - 爱吃西瓜的番茄酱 - 博客园...
【SpringCloud】从入门到精通(下)
网关与路由 什么是网关?顾明思议,网关就是网络的关口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。 现在前端不能请求各个微服务地址,只能去请求网关 网关可以做安全控…...
深入探索 C++23:特性测试与编译器支持
文章目录 一、C23 新特性概览(一)语言特性(二)标准库特性 二、特性测试程序三、主流编译器支持情况(一)GCC(二)Clang(三)MSVC 四、开发者建议(一&…...
Electron 应用太重?试试 PakePlus 轻装上阵
Electron 作为将 Web 技术带入桌面应用领域的先驱框架,让无数开发者能够使用熟悉的 HTML、CSS 和 JavaScript 构建跨平台应用。然而,随着应用规模的扩大,Electron 应用的性能问题逐渐显现——内存占用高、启动速度慢、安装包体积庞大…...
驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接
驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接 原因描述 项目中有使用到 SQL Server 数据库, 在启动项目时, 出现报错信息: 【驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“The server selected protocol version…...
Java 设计模式:原型模式详解
Java 设计模式:原型模式详解 原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制现有对象来创建新对象,而无需依赖其具体类。这种模式特别适合创建复杂对象或需要频繁创建相似对象的场景。本文将详细介绍原…...
NLP高频面试题(三十七)——大模型训练和推理的显存估计
在训练和推理大型语言模型时,显存(GPU 内存)的需求是一个关键考虑因素。准确估计这些需求有助于选择合适的硬件配置,确保模型高效运行。 推理阶段的显存需求 在推理过程中,显存主要用于存储模型权重和中间激活值。模型权重的显存需求可以通过以下公式估算: 模型权重…...
PHP 阿里云oss 使用指南
1.介绍 把图片放到阿里云上的空间上,可以使用cdn加速。 可以在程序里直接调用 要使用阿里云 oss sdk ,请先到阿里云下载 或用 copmposer 安装 相关链接: 安装OSS PHP SDK_对象存储(OSS)-阿里云帮助中心 composer require aliyuncs/oss…...
leetcode_面试题 02.07. 链表相交_java
面试题 02.07. 链表相交https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/ 1、题目 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 图示两个链表在节点 c…...
LeetCode 3375.使数组的值全部为 K 的最少操作次数:O(1)空间——排序+一次遍历
【LetMeFly】3375.使数组的值全部为 K 的最少操作次数:O(1)空间——排序一次遍历 力扣题目链接:https://leetcode.cn/problems/minimum-operations-to-make-array-values-equal-to-k/ 给你一个整数数组 nums 和一个整数 k 。 如果一个数组中所有 严格…...
紫光展锐5G SoC T8300:影像升级,「定格」美好世界
影像能力已成为当今衡量智能手机性能的重要标尺之一。随着消费者对手机摄影需求日益提升,手机厂商纷纷在影像硬件和算法上展开激烈竞争,力求为用户带来更加出色的拍摄体验。 紫光展锐专为全球主流用户打造的畅享影音和游戏体验的5G SoC——T8300&#x…...
java基础 关键字static
static static使用简介static结合类的生命周期1.加载2.链接(1) 验证(Verification)(2) 准备(Preparation)(3) 解析(Resolution) 3. 初始化4.使用5.卸载总结 staic作用总结静态变量静态代码块静态方法静态内…...
大数据学习(105)-大数据组件分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一…...
Spark运行
一文读懂Spark:从核心概念到实战编程 在大数据处理领域,Spark凭借其高效的计算能力和灵活的架构脱颖而出。今天,就来和大家深入聊聊Spark,帮助初学者快速入门。Spark采用经典的master - slave结构。Driver如同master,…...
在macOS的docker中如何安装及运行ROS2
1、macOS环境及版本 2、docker for macos版本 3、拉取ROS2镜像 docker pull ros:iron 4、查看容器 docker images 5、启动 ROS2 容器 docker run -it --rm ros:iron -it :以交互模式运行容器。 --rm :退出时自动删除容器(测试时推荐&am…...
FFmpeg安装和使用
1. 安装与环境配置 Windows # 方法1:官网下载预编译二进制包 https://ffmpeg.org/download.html#build-windows 解压后添加bin目录到系统PATH# 方法2:通过Chocolatey安装 choco install ffmpegmacOS # 使用Homebrew安装 brew install ffmpegLinux # …...
基于多模态大模型的ATM全周期诊疗技术方案
基于多模态大模型的ATM全周期诊疗技术方案 1. 数据预处理模块 算法1:多模态数据融合伪代码 def multimodal_fusion(data_dict):# 输入:包含MRI、EEG、实验室指标的字典# 输出:对齐后的张量序列# 模态对齐aligned_data = temporal_alignment(data_dict,sampling_rate...
写时复制Copy-on-Write(COW)
简单理解写时复制 读的时候,直接访问原对象。 写的时候,对复制原对象,对副本进行写操作,最后将副本替换原对象。 写时复制多用于读多写少的场景,因为写操作是用悲观锁进行的,如果写的场景多,…...
S7-1200 PLC热电偶和热电阻模拟量模块
热电偶和热电阻模拟量模块 S7-1200 PLC有专用用于对温度进行采集的热电偶模块SM1231 TC和SM 1231RTD。热电偶模块有4AI和8AI两种,下面以SM1231 TC 4AI为例看一下接线图。 该模块一共有4个通道,每个通道有两个接线端子,比如0,0-。…...
ffmpeg函数简介(封装格式相关)
文章目录 🌟 前置说明:FFmpeg 中 AVFormatContext 是什么?🧩 1. avformat_alloc_context功能:场景: 🧩 2. avformat_open_input功能:说明:返回值: ǹ…...
操作数组的工具类
Arrays 它里面的每一个方法基本上都是static静态修饰的,如果想要调用里面的方法,不需要创建对象,直接用类名.就可以了 操作数组的工具类 方法: public static String toString(数组) 把数组拼接成…...
小刚说C语言刷题——第19讲 循环之continue和break
在循环中,当我们得到想要的答案时,这时我们可能要提前结束循环,这个时候我们就会用到break。而我们有时需要结束某一次循环时,我们可以用continue。 1.break语句 (1)在循环中想要提前终止循环,要用break。 (2)语法格…...
FairMOT复现过程中cython_bbox库问题
cython_bbox库就该这么安装_cython-bbox库就应该-CSDN博客...
记录学习的第二十四天
还是每日一题。 题解很巧,我根本想不到。 class Solution { public: int minOperations(vector<int>& nums, int k) { int count; int mnnums[0]; //接下来查找nums数组中最小值 for(int i1;i<nums.size();i) { if(nums[i]<mn) { mnnums[i]; } } …...
Kubernetes 入门篇之网络插件 calico 部署与安装
在运行kubeadm init 和 join 命令部署好master和node节点后,kubectl get nodes 看到节点都是NotReady状态,这是因为没有安装CNI网络插件。 kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Not…...