【小松教你手游开发】【系统模块开发】图文混排 (在label中插入表情)
本身ngui是自帶圖文混排的,這個可以在ngui的Example里找到。但是為什么不能用網上已經說得很清楚,比如雨松momo的http://www.xuanyusong.com/archives/2908
最重要的一點就是我們肯定不會選擇一個完整的中文字庫,動態字體無辦法使用ngui的圖文混排
所以還是需要自己寫一個圖文混排。
首先圖文混排的基本邏輯是:
1.定義固定字符串格式作為圖片信息。
2.找到文字中的圖片信息的字符串提取并換成空格
3.根據圖片信息生成uisprite,并放在適當的position
4.輸出文字和圖片
圖文混排有幾個重點是必須解決的:
1.找到圖片應該放的position
2.如果圖片在文字末尾判斷是否放得下是否會被遮擋,是的話要把圖片放到下一行的開頭
3.按照圖片的高度判斷這一行的開頭需要多少個換行符
4.如果一排有多個圖片且尺寸不一,這一排的圖片需要統一高度,不然會出現下面的情況
(如果圖片格式統一的話3,4倒是可以用湊合的辦法省略,但是我們想做一個適用各種大小圖片,每行可能有幾張圖片,適合各種情況的圖文混排)
接下來就是實現。
我的思路是:
有一大段文字且里面有許多圖片信息的前提下
1.首先把所有文字輸入都某個函數,識別出第一個圖片信息的字符串,把這個包含圖片信息的字符串以及前面的文字裁剪下來,和裁剪以后的文字形成兩部分。
2.把裁剪的前面部分(包含圖片信息)分析出圖片信息,各種計算,最后得到圖片的position,生成gameObject并擺放好。保存各種信息。圖片部分用空格留出位置,形成新的字符串,和裁剪的第二部分的文字組合成新文字。
3.輸入到1里的那個函數。遞歸。
4.最終一次過輸出所有文字。
代碼直接寫到UILabel.cs里,也可以寫一個UIEmotionLabel.cs繼承UILabel.cs。
接下來看代碼:(最后會貼出所有代碼)
/// <summary> /// label中有表情在顯示前調用進行轉換 /// </summary> public void ShowEmotionLabel() { m_newEmotionText = ""; string originalText = MyLabel.text; //遞歸找表情并生成文字 CutAndShowEmotionLabel(originalText); //輸出文字 MyLabel.text = m_newEmotionText; MyLabel.UpdateNGUIText(); //每一行的表情重新排序對其 SortAllSprite(); }這個是唯一外部調用接口,當要顯示圖片的時候調用這個函數。
通過注釋就可以看懂里面的邏輯,最后的SortAllSprite()最后會再解釋一下。
所以先看CutAndShowEmotionLabel(string str)這個函數。
void CutAndShowEmotionLabel(string str) { EmotionData emoData = GetEmotionData(str);//解析str中的第一個表情字符串 if (emoData != null) { m_spriteList.Add(emoData); //把str按第一個表情字符串的最后一個字母分成兩部分 string trimString = str.Substring(0, emoData.end_index); string trimLeftString = str.Substring(emoData.end_index); //生成表情和表情前面的文字部分 GenEmotionLabel(emoData, trimString); m_newEmotionText = m_newEmotionText + trimLeftString; //遞歸繼續找表情 CutAndShowEmotionLabel(m_newEmotionText); } else { //找不到表情返回,最后確定文字輸出 m_newEmotionText =str; return; } }第一行就是用自己的方法解析。
上面的邏輯就是按思路寫的
唯一有點不一樣的就是多了一個m_spriteList.Add(emoData);
因為最后需要把所有圖片按每行輸出時可能要對其高度,所以都要先保存下來。
這里面最重要的是GenEmotionLabel(emoData, trimString);這個函數
void GenEmotionLabel(EmotionData emoData, string tramString) { //生成gameobject GameObject go = CreateEmotionSprite(emoData); float spriteWidth = NGUIMath.CalculateRelativeWidgetBounds(gameobject.transform, go.transform, true).size.x / go.transform.localScale.x; float spriteHeight = NGUIMath.CalculateRelativeWidgetBounds(gameobject.transform, go.transform, true).size.y / go.transform.localScale.y; //計算出圖片的位置,判斷文字的轉換和空格 Vector3 position = CalcuEmotionSpritePosition(tramString, emoData.start_index, spriteWidth, spriteHeight); //擺放圖片位置 PlaceEmotionSprite(go, position); m_spriteList[m_spriteList.Count - 1].go = go; }CreateEmotionSprite()就是根據分析出來的圖片信息實例化一個GameObject,但是這時候position位置還是不能確定。
在算出圖片的寬高后。把這些數據都輸入到CalcuEmotionSpritePosition();這個函數里算出最后的position。
獲得position數據在PlaceEmotionSprite()函數正確的擺放
所以這里最關鍵的還是CalcuEmotionSpritePosition()。
這里看GenBlankString()函數。
Vector3 GenBlankString(string str, int startIndex, float spriteWidth, float spriteHeight) { int finalIndex = startIndex; BetterList<Vector3> tempVerts = new BetterList<Vector3>(); BetterList<int> tempIndices = new BetterList<int>(); //1.把圖片信息換成空格 string emontionText = str.Substring(startIndex); int blankNeedCount = CaculateBlankNeed(spriteWidth); str = str.Replace(emontionText, GenBlank(blankNeedCount)); //把換好的文字放回label再計算sprite應該放的坐標, UpdateCharacterPosition(str,out tempVerts,out tempIndices); //2.如果在label末尾且圖片放不下,判斷是否換行 bool needWrap = NeedWrap(tempVerts, tempIndices, startIndex, startIndex + blankNeedCount); if (needWrap) { str = str.Insert(startIndex, "\n"); finalIndex +=1; //重新計算當前所有字符的位置 UpdateCharacterPosition(str, out tempVerts, out tempIndices); } //3.按圖片的高,生成回車(換行) int returnCount = GenCarriageReturn(tempVerts, tempIndices, ref str, finalIndex, spriteHeight, needWrap); finalIndex += returnCount; //4.重新賦值要輸出的str m_newEmotionText = str; //重新計算當前所有字符的位置 UpdateCharacterPosition(str, out tempVerts, out tempIndices); //保存行數,最后重新排放每行的圖片使用 m_spriteList[m_spriteList.Count - 1].line_index = CalcuLineIndex(tempVerts, tempIndices, startIndex) - lastScale; //最終計算圖片該放的位置 Vector3 position = new Vector3(); if (needWrap) { position = new Vector3(tempVerts[0].x, tempVerts[GetIndexFormIndices(finalIndex, tempIndices)].y, tempVerts[0].z); } else { position = tempVerts[GetIndexFormIndices(finalIndex, tempIndices)]; } return position; }先介紹一下NGUI提供的計算每個字符在字符串中位置的函數。
NGUIText.PrintCharacterPositions(str, tempVerts, tempIndices);
輸入str,輸出tempVerts,tempIndices。通過這兩個變量獲取每個字符的position信息
這里我封裝了個函數通過字符在字符串中的index來獲取在tempVerts中index_v,繼而通過tempVerts[index_v]獲取vecter3
int GetIndexFormIndices(int index, BetterList<int> list) { for (int i = 0; i < list.size; i++) if (list[i] == index) return i; return 0; }我把NGUIText.PrintCharacterPositions(str, tempVerts, tempIndices)的用法寫成一個接口。
void UpdateCharacterPosition(string str,out BetterList<Vector3> verts,out BetterList<int> indices) { //把換好的文字放回label再計算sprite應該放的坐標, //計算當前所有字符的位置 MyLabel.text = str; MyLabel.UpdateNGUIText(); BetterList<Vector3> tempVerts = new BetterList<Vector3>(); BetterList<int> tempIndices = new BetterList<int>(); NGUIText.PrintCharacterPositions(str, tempVerts, tempIndices); verts = tempVerts; indices = tempIndices; }這個接口的意思就是把str放到label里,讓NGUI重新擺放一下文字,之后調用PrintCharacterPositions,返回這兩個變量,就更新了位置信息。這時候就可以取得每個字符的位置信息,也就是圖片將要擺放的位置。(在每次改變文字后都要重新調用才能確定位置準確)
回到上面的GenBlankString().
1.首先根據圖片寬度計算需要多少個空格來預留出位置。調用UpdateCharacterPosition()更新,重新獲得位置信息(這部分我暫時是估算哈,比如5像素1空格)
2.判斷是否需要換行。調用UpdateCharacterPosition()更新,重新獲得位置信息(判斷圖片信息字符串(已換成空格)的第一個字符和最后一個字符是否在同一行,如果不同行證明要換行)
3.按圖片的高,生成換行符。調用UpdateCharacterPosition()更新,重新獲得位置信息
4.這時文字已經確定不會再添加任何符號,所以重新復制最終要輸出的文字m_newEmotionText = str;
步驟3需要特別講一下:
int lastScale = 1; int lastIndex = 0; int GenCarriageReturn(BetterList<Vector3> vectList, BetterList<int> indexList, ref string str, int startIndex, float spriteHeight, bool isWrap) { float fontSize = MyLabel.fontSize * gameobject.transform.localScale.x; int scale = Mathf.CeilToInt(spriteHeight / fontSize) - 1; if (CheckIfSameLine(vectList, indexList, startIndex, lastIndex)) { if (lastScale < scale) { scale = scale - lastScale; lastScale = scale + lastScale; } else { scale = 0; } } else { lastScale = scale; } lastIndex = startIndex; string CarriageReturn = ""; for (int i = 0; i < scale; i++) { CarriageReturn = CarriageReturn + '\n'; lastIndex += 1; } //if(CheckIfIsLineFirstCharacter(vectList, indexList, startIndex)) //{ // CarriageReturn = CarriageReturn + '\n'; // scale += 1; //} if (!isWrap && scale > 0) { CarriageReturn = CarriageReturn + '\n'; scale += 1; lastIndex += 1; lastScale += 1; } str = str.Insert(FindLineFirstIndex(vectList, indexList, startIndex) - 1, CarriageReturn); return scale; }可以看到在scale就是我需要多少個換行符。
接著下面的邏輯是如果這次判斷的startIndex(這個圖片的第一個字符)和上次lastIndex(上一個圖片的第一個字符)如果是同一行的話,需要判斷后面的圖片有沒有比前面的更大,如果更大需要判斷大多少,還需要多少個回車。
因為如果同一行內多個圖片的大小不一,只取最大的圖片的大小生成換行符。
再后面是判斷,有種情況是本身文字放到label剛好處于文字末尾(就是本身就需要一個換行符),所以如果是這種情況需要再插入一個換行符。
接著就把換行符插入到這一行的第一個字符前(還是通過位置信息去判斷這行的第一個字符)
這個就是判斷圖片位置的邏輯,然后就一遍遍的遞歸把所有圖片找出來放置好。
最后還需要把每一行的圖片檢索一下,同一行有多個圖片時,所有圖片的y軸都跟最后一個對齊(因為最后一個的y軸肯定是最低的,要跟最低的對齊)
void SortAllSprite() { for (int i = m_spriteList.Count - 1; i > 0; i--) { if (m_spriteList[i].line_index == m_spriteList[i - 1].line_index) { m_spriteList[i - 1].pos.y = m_spriteList[i].pos.y; m_spriteList[i - 1].go.transform.localPosition = m_spriteList[i - 1].pos; } } }這樣就完成了圖文混排。
下面是所有代碼(掛在UILabel.cs上, UILabel的代碼不顯示)
string m_newEmotionText = ""; List<EmotionData> m_spriteList = new List<EmotionData>(); /// <summary> /// label中有表情在顯示前調用進行轉換 /// </summary> public void ShowEmotionLabel() { m_newEmotionText = ""; string originalText = MyLabel.text; //遞歸找表情并生成文字 CutAndShowEmotionLabel(originalText); //輸出文字 MyLabel.text = m_newEmotionText; MyLabel.UpdateNGUIText(); //每一行的表情重新排序對其 SortAllSprite(); } #region 圖文混排輔助函數 void CutAndShowEmotionLabel(string str) { EmotionData emoData = GetEmotionData(str);//解析str中的第一個表情字符串 if (emoData != null) { m_spriteList.Add(emoData); //把str按第一個表情字符串的最后一個字母分成兩部分 string trimString = str.Substring(0, emoData.end_index); string trimLeftString = str.Substring(emoData.end_index); //生成表情和表情前面的文字部分 GenEmotionLabel(emoData, trimString); m_newEmotionText = m_newEmotionText + trimLeftString; //遞歸繼續找表情 CutAndShowEmotionLabel(m_newEmotionText); } else { //找不到表情返回,最后確定文字輸出 m_newEmotionText =str; return; } } void GenEmotionLabel(EmotionData emoData, string tramString) { //生成gameobject GameObject go = CreateEmotionSprite(emoData); float spriteWidth = NGUIMath.CalculateRelativeWidgetBounds(gameobject.transform, go.transform, true).size.x / go.transform.localScale.x; float spriteHeight = NGUIMath.CalculateRelativeWidgetBounds(gameobject.transform, go.transform, true).size.y / go.transform.localScale.y; //計算出圖片的位置,判斷文字的轉換和空格 Vector3 position = CalcuEmotionSpritePosition(tramString, emoData.start_index, spriteWidth, spriteHeight); //擺放圖片位置 PlaceEmotionSprite(go, position); m_spriteList[m_spriteList.Count - 1].go = go; } int lastScale = 1; int lastIndex = 0; int GenCarriageReturn(BetterList<Vector3> vectList, BetterList<int> indexList, ref string str, int startIndex, float spriteHeight, bool isWrap) { float fontSize = MyLabel.fontSize * gameobject.transform.localScale.x; int scale = Mathf.CeilToInt(spriteHeight / fontSize) - 1; if (CheckIfSameLine(vectList, indexList, startIndex, lastIndex)) { if (lastScale < scale) { scale = scale - lastScale; lastScale = scale + lastScale; } else { scale = 0; } } else { lastScale = scale; } lastIndex = startIndex; string CarriageReturn = ""; for (int i = 0; i < scale; i++) { CarriageReturn = CarriageReturn + '\n'; lastIndex += 1; } //if(CheckIfIsLineFirstCharacter(vectList, indexList, startIndex)) //{ // CarriageReturn = CarriageReturn + '\n'; // scale += 1; //} if (!isWrap && scale > 0) { CarriageReturn = CarriageReturn + '\n'; scale += 1; lastIndex += 1; lastScale += 1; } str = str.Insert(FindLineFirstIndex(vectList, indexList, startIndex) - 1, CarriageReturn); return scale; } Vector3 CalcuEmotionSpritePosition(string str, int startIndex, float spriteWidth, float spriteHeight) { Vector3 position = GenBlankString(str, startIndex, spriteWidth, spriteHeight); return position; } Vector3 GenBlankString(string str, int startIndex, float spriteWidth, float spriteHeight) { int finalIndex = startIndex; BetterList<Vector3> tempVerts = new BetterList<Vector3>(); BetterList<int> tempIndices = new BetterList<int>(); //1.把圖片信息換成空格 string emontionText = str.Substring(startIndex); int blankNeedCount = CaculateBlankNeed(spriteWidth); str = str.Replace(emontionText, GenBlank(blankNeedCount)); //把換好的文字放回label再計算sprite應該放的坐標, UpdateCharacterPosition(str,out tempVerts,out tempIndices); //2.如果在label末尾且圖片放不下,判斷是否換行 bool needWrap = NeedWrap(tempVerts, tempIndices, startIndex, startIndex + blankNeedCount); if (needWrap) { str = str.Insert(startIndex, "\n"); finalIndex +=1; //重新計算當前所有字符的位置 UpdateCharacterPosition(str, out tempVerts, out tempIndices); } //3.按圖片的高,生成回車(換行) int returnCount = GenCarriageReturn(tempVerts, tempIndices, ref str, finalIndex, spriteHeight, needWrap); finalIndex += returnCount; //4.重新賦值要輸出的str m_newEmotionText = str; //重新計算當前所有字符的位置 UpdateCharacterPosition(str, out tempVerts, out tempIndices); //保存行數,最后重新排放每行的圖片使用 m_spriteList[m_spriteList.Count - 1].line_index = CalcuLineIndex(tempVerts, tempIndices, startIndex) - lastScale; //最終計算圖片該放的位置 Vector3 position = new Vector3(); if (needWrap) { position = new Vector3(tempVerts[0].x, tempVerts[GetIndexFormIndices(finalIndex, tempIndices)].y, tempVerts[0].z); } else { position = tempVerts[GetIndexFormIndices(finalIndex, tempIndices)]; } return position; } GameObject CreateEmotionSprite(EmotionData data) { GameObject go = new GameObject("(clone)emotion_sprite"); go.transform.parent = gameobject.transform; UISprite sprite = go.AddComponent<UISprite>(); sprite.atlas = CResourceManager.Instance.GetAtlas(data.atlas_name); sprite.spriteName = data.sprite_name; sprite.MakePixelPerfect(); sprite.pivot = UIWidget.Pivot.BottomLeft; float scaleFactor = 1 / gameobject.transform.localScale.x; go.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor);//字體可能縮小了0.5,所以掛在字體下要放大2倍 go.transform.localPosition = new Vector3(5000, 5000, 0);//先把它放到看不見的地方 return go; } void PlaceEmotionSprite(GameObject go, Vector3 position) { float fontSize = MyLabel.fontSize * gameobject.transform.localScale.x; float div = fontSize * go.transform.localScale.x / 2; Vector3 newPosition = new Vector3(position.x, position.y - div, position.z); //Vector3 newPosition = position; go.transform.localPosition = newPosition; m_spriteList[m_spriteList.Count - 1].pos = newPosition; } EmotionData GetEmotionData(string text) { EmotionData tempData = null; int index = text.IndexOf("%p"); if (index != -1) { tempData = new EmotionData(); tempData.start_index = index; int altasEndIndex = text.IndexOf("$", index); tempData.atlas_name = text.Substring(index + 2, altasEndIndex - (index + 2)); int spriteEndIndex = text.IndexOf("$", altasEndIndex + 1); tempData.sprite_name = text.Substring(altasEndIndex + 1, spriteEndIndex - (altasEndIndex + 1)); tempData.end_index = spriteEndIndex + 1; } return tempData; } int GetIndexFormIndices(int index, BetterList<int> list) { for (int i = 0; i < list.size; i++) if (list[i] == index) return i; return 0; } int CaculateBlankNeed(float spriteWidth) { int count = Mathf.CeilToInt(spriteWidth / (float)6); return count; } string GenBlank(int count) { string blank = ""; for (int i = 0; i < count; i++) { blank = blank + " "; } return blank; } bool NeedWrap(BetterList<Vector3> vecList, BetterList<int> indicList, int startIndex, int endIndex) { int startIndic = GetIndexFormIndices(startIndex, indicList); int endIndic = GetIndexFormIndices(endIndex, indicList); if (vecList[startIndic].y == vecList[endIndic].y) return false; else return true; } bool CheckIfSameLine(BetterList<Vector3> vecList, BetterList<int> indicList, int firstIndex, int SecondIndex) { int firstIndic = GetIndexFormIndices(firstIndex, indicList); int secondIndic = GetIndexFormIndices(SecondIndex, indicList); if (vecList[firstIndic].y == vecList[secondIndic].y) return true; else return false; } int FindLineFirstIndex(BetterList<Vector3> vecList, BetterList<int> indicList, int index) { int startIndic = GetIndexFormIndices(index, indicList); if (startIndic > 1) { if (vecList[startIndic].y == vecList[startIndic - 1].y) index = FindLineFirstIndex(vecList, indicList, index - 1); else return index; } else { return 1; } return index; } int CalcuLineIndex(BetterList<Vector3> vecList, BetterList<int> indicList, int index) { int startIndic = GetIndexFormIndices(index, indicList); int count = 0; float lastVecY = 0; for (int i = 0; i < vecList.size; i++) //for (int i =0;i< startIndic; i++) { if (lastVecY != vecList[i].y) { count++; lastVecY = vecList[i].y; } } return count; } bool CheckIfIsLineFirstCharacter(BetterList<Vector3> vecList, BetterList<int> indicList, int index) { int startIndic = GetIndexFormIndices(index, indicList); if (startIndic > 1) { if (vecList[startIndic].y == vecList[startIndic - 1].y) return false; else return true; } else { return false; } } void SortAllSprite() { for (int i = m_spriteList.Count - 1; i > 0; i--) { if (m_spriteList[i].line_index == m_spriteList[i - 1].line_index) { m_spriteList[i - 1].pos.y = m_spriteList[i].pos.y; m_spriteList[i - 1].go.transform.localPosition = m_spriteList[i - 1].pos; } } } void UpdateCharacterPosition(string str,out BetterList<Vector3> verts,out BetterList<int> indices) { //把換好的文字放回label再計算sprite應該放的坐標, //計算當前所有字符的位置 MyLabel.text = str; MyLabel.UpdateNGUIText(); BetterList<Vector3> tempVerts = new BetterList<Vector3>(); BetterList<int> tempIndices = new BetterList<int>(); NGUIText.PrintCharacterPositions(str, tempVerts, tempIndices); verts = tempVerts; indices = tempIndices; } #endregion補上EmotionData類
public class EmotionData { public int start_index; public int end_index; public string atlas_name; public string sprite_name; public float sprite_width; public int line_index; public Vector3 pos; public GameObject go; }轉載于:https://blog.51cto.com/13638120/2084875
總結
以上是生活随笔為你收集整理的【小松教你手游开发】【系统模块开发】图文混排 (在label中插入表情)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JMeter计算QPS
- 下一篇: JavaScript 递归之深度优先和广