前言
游戏开发中我们一般都会有聊天系统,我们可以同时或单独发文字,图片,表情,超链接等信息的文本即称为富文本。如下图所示:
我使用的是cocos-3.4引擎版本里的RichText富文本控件实现这些操作的,但cocos自带封装的RichText还有一些问题,1:如上图看到的一样,当中英文混输时右边字体没对齐(看需求,QQ都没处理这问题)。2:聊天信息有时会需求像上面一样还要一个背景底框装着,底框大小随发送内容变化,这时我们就需要知道富文本的宽度和高度而cocos的RichText没有封装这个接口需要我们自己扩展。本章就讲讲怎么来解决这两个问题。
解决步骤
1,在UIRichText.h中新增获取富文本大小接口
public:
Size xxgetRealSize();
Size xxrealSize;
2,默认右没不齐是因为它的换行是根据字的平均长度来计算的,即单个字长度=总长度/总字数,而不同字的宽度是很可能不相同的。现在我的改法是:循环计算出每个字的大小求和换行,当然这样的效率是比默认的低,所以这个对齐还是看需求,有的QQ消息也没处理这个对齐问题。下面是UIRichText.cpp修改后的完整代码,有注释,修改处都用@cxx标记了。
#include "UIRichText.h"
#include "platform/CCFileUtils.h"
#include "2d/CCLabel.h"
#include "2d/CCSprite.h"
#include "base/ccUTF8.h"
#include "ui/UIHelper.h"
#include "platform/CCDevice.h"
#include "base/ccMacros.h"
#include "base/CCDirector.h"
NS_CC_BEGIN
namespace ui {
bool RichElement::init(int tag, const Color3B &color, GLubyte opacity)
{
_tag = tag;
_color = color;
_opacity = opacity;
return true;
}
RichElementText* RichElementText::create(int tag, const Color3B &color, GLubyte opacity, const std::string& text, const std::string& fontName, float fontSize)
{
RichElementText* element = new (std::nothrow) RichElementText();
if (element && element->init(tag, color, opacity, text, fontName, fontSize))
{
element->autorelease();
return element;
}
CC_SAFE_DELETE(element);
return nullptr;
}
bool RichElementText::init(int tag, const Color3B &color, GLubyte opacity, const std::string& text, const std::string& fontName, float fontSize)
{
if (RichElement::init(tag, color, opacity))
{
_text = text;
_fontName = fontName;
_fontSize = fontSize;
return true;
}
return false;
}
RichElementImage* RichElementImage::create(int tag, const Color3B &color, GLubyte opacity, const std::string& filePath)
{
RichElementImage* element = new (std::nothrow) RichElementImage();
if (element && element->init(tag, color, opacity, filePath))
{
element->autorelease();
return element;
}
CC_SAFE_DELETE(element);
return nullptr;
}
bool RichElementImage::init(int tag, const Color3B &color, GLubyte opacity, const std::string& filePath)
{
if (RichElement::init(tag, color, opacity))
{
_filePath = filePath;
return true;
}
return false;
}
RichElementCustomNode* RichElementCustomNode::create(int tag, const Color3B &color, GLubyte opacity, cocos2d::Node *customNode)
{
RichElementCustomNode* element = new (std::nothrow) RichElementCustomNode();
if (element && element->init(tag, color, opacity, customNode))
{
element->autorelease();
return element;
}
CC_SAFE_DELETE(element);
return nullptr;
}
bool RichElementCustomNode::init(int tag, const Color3B &color, GLubyte opacity, cocos2d::Node *customNode)
{
if (RichElement::init(tag, color, opacity))
{
_customNode = customNode;
_customNode->retain();
return true;
}
return false;
}
RichText::RichText():
_formatTextDirty(true),
_leftSpaceWidth(0.0f),
_verticalSpace(0.0f),
_elementRenderersContainer(nullptr)
{
}
RichText::~RichText()
{
_richElements.clear();
}
RichText* RichText::create()
{
RichText* widget = new (std::nothrow) RichText();
if (widget && widget->init())
{
widget->autorelease();
return widget;
}
CC_SAFE_DELETE(widget);
return nullptr;
}
bool RichText::init()
{
if (Widget::init())
{
return true;
}
return false;
}
void RichText::initRenderer()
{
_elementRenderersContainer = Node::create();
_elementRenderersContainer->setAnchorPoint(Vec2(0.5f, 0.5f));
addProtectedChild(_elementRenderersContainer, 0, -1);
}
void RichText::insertElement(RichElement *element, int index)
{
_richElements.insert(index, element);
_formatTextDirty = true;
}
void RichText::pushBackElement(RichElement *element)
{
_richElements.pushBack(element);
_formatTextDirty = true;
}
void RichText::removeElement(int index)
{
_richElements.erase(index);
_formatTextDirty = true;
}
void RichText::removeElement(RichElement *element)
{
_richElements.eraseObject(element);
_formatTextDirty = true;
}
void RichText::formatText()
{
if (_formatTextDirty)
{
_elementRenderersContainer->removeAllChildren();
_elementRenders.clear();
if (_ignoreSize)
{
addNewLine();
for (ssize_t i=0; i<_richElements.size(); i++)
{
RichElement* element = _richElements.at(i);
Node* elementRenderer = nullptr;
switch (element->_type)
{
case RichElement::Type::TEXT:
{
RichElementText* elmtText = static_cast<RichElementText*>(element);
if (FileUtils::getInstance()->isFileExist(elmtText->_fontName))
{
elementRenderer = Label::createWithTTF(elmtText->_text.c_str(), elmtText->_fontName, elmtText->_fontSize);
}
else
{
elementRenderer = Label::createWithSystemFont(elmtText->_text.c_str(), elmtText->_fontName, elmtText->_fontSize);
}
break;
}
case RichElement::Type::IMAGE:
{
RichElementImage* elmtImage = static_cast<RichElementImage*>(element);
elementRenderer = Sprite::create(elmtImage->_filePath.c_str());
break;
}
case RichElement::Type::CUSTOM:
{
RichElementCustomNode* elmtCustom = static_cast<RichElementCustomNode*>(element);
elementRenderer = elmtCustom->_customNode;
break;
}
default:
break;
}
elementRenderer->setColor(element->_color);
elementRenderer->setOpacity(element->_opacity);
pushToContainer(elementRenderer);
}
}
else
{
addNewLine();
for (ssize_t i=0; i<_richElements.size(); i++)
{
RichElement* element = static_cast<RichElement*>(_richElements.at(i));
switch (element->_type)
{
case RichElement::Type::TEXT:
{
RichElementText* elmtText = static_cast<RichElementText*>(element);
handleTextRenderer(elmtText->_text.c_str(), elmtText->_fontName.c_str(), elmtText->_fontSize, elmtText->_color, elmtText->_opacity);
break;
}
case RichElement::Type::IMAGE:
{
RichElementImage* elmtImage = static_cast<RichElementImage*>(element);
handleImageRenderer(elmtImage->_filePath.c_str(), elmtImage->_color, elmtImage->_opacity);
break;
}
case RichElement::Type::CUSTOM:
{
RichElementCustomNode* elmtCustom = static_cast<RichElementCustomNode*>(element);
handleCustomRenderer(elmtCustom->_customNode);
break;
}
default:
break;
}
}
}
formarRenderers();
_formatTextDirty = false;
}
}
bool s_getLineTextNumber(const std::string& text, const std::string& fontName, float fontSize , float rowWidth , int &textNumber)
{
bool ret = true ;
float maxW = rowWidth ;
int count = 0 ;
while(true)
{
std::string tempChar = Helper::getSubStringOfUTF8String(text,count,1);
float tempW,tempH ;
if(tempChar.length() == 0 )
{
break ;
}
bool bSuccess = Device::CalculateTextSize(tempChar.c_str(),fontName.c_str(),fontSize, tempW, tempH);
if( !bSuccess )
{
ret = false ;
break ;
}
maxW -= tempW ;
if(maxW< -tempW / 2)
{
break ;
}
count++ ;
}
textNumber = count ;
return ret ;
}
void RichText::handleTextRenderer(const std::string& text, const std::string& fontName, float fontSize, const Color3B &color, GLubyte opacity)
{
auto fileExist = FileUtils::getInstance()->isFileExist(fontName);
Label* textRenderer = nullptr;
if (fileExist)
{
textRenderer = Label::createWithTTF(text, fontName, fontSize);
}
else
{
textRenderer = Label::createWithSystemFont(text, fontName, fontSize);
}
float textRendererWidth = textRenderer->getContentSize().width;
float saveLeftSpaceWidth = _leftSpaceWidth ;
_leftSpaceWidth -= textRendererWidth;
if (_leftSpaceWidth < 0.0f)
{
float overstepPercent = (-_leftSpaceWidth) / textRendererWidth;
std::string curText = text;
size_t stringLength = StringUtils::getCharacterCountInUTF8String(text);
int leftLength = 0 ;
std::string leftWords = "" ;
std::string cutWords = "" ;
float rowWidth = saveLeftSpaceWidth ;
int textNumber = 0 ;
if( s_getLineTextNumber( curText, fontName, fontSize , rowWidth , textNumber) )
{
leftLength = textNumber ;
leftWords = Helper::getSubStringOfUTF8String(curText,0,leftLength);
cutWords = Helper::getSubStringOfUTF8String(curText, leftLength, stringLength - leftLength);
}
else
{
leftLength = stringLength * (1.0f - overstepPercent);
leftWords = Helper::getSubStringOfUTF8String(curText,0,leftLength);
cutWords = Helper::getSubStringOfUTF8String(curText, leftLength, stringLength - leftLength);
}
if (leftLength > 0)
{
Label* leftRenderer = nullptr;
if (fileExist)
{
leftRenderer = Label::createWithTTF(Helper::getSubStringOfUTF8String(leftWords, 0, leftLength), fontName, fontSize);
}
else
{
leftRenderer = Label::createWithSystemFont(Helper::getSubStringOfUTF8String(leftWords, 0, leftLength), fontName, fontSize);
}
if (leftRenderer)
{
leftRenderer->setColor(color);
leftRenderer->setOpacity(opacity);
pushToContainer(leftRenderer);
}
}
addNewLine();
handleTextRenderer(cutWords.c_str(), fontName, fontSize, color, opacity);
}
else
{
textRenderer->setColor(color);
textRenderer->setOpacity(opacity);
pushToContainer(textRenderer);
}
}
void RichText::handleImageRenderer(const std::string& fileParh, const Color3B &color, GLubyte opacity)
{
Sprite* imageRenderer = Sprite::create(fileParh);
handleCustomRenderer(imageRenderer);
}
void RichText::handleCustomRenderer(cocos2d::Node *renderer)
{
Size imgSize = renderer->getContentSize();
_leftSpaceWidth -= imgSize.width;
if (_leftSpaceWidth < 0.0f)
{
addNewLine();
pushToContainer(renderer);
_leftSpaceWidth -= imgSize.width;
}
else
{
pushToContainer(renderer);
}
}
void RichText::addNewLine()
{
_leftSpaceWidth = _customSize.width;
_elementRenders.push_back(new Vector<Node*>());
}
void RichText::formarRenderers()
{
if (_ignoreSize)
{
float newContentSizeWidth = 0.0f;
float newContentSizeHeight = 0.0f;
Vector<Node*>* row = (_elementRenders[0]);
float nextPosX = 0.0f;
for (ssize_t j=0; j<row->size(); j++)
{
Node* l = row->at(j);
l->setAnchorPoint(Vec2::ZERO);
l->setPosition(nextPosX, 0.0f);
_elementRenderersContainer->addChild(l, 1);
Size iSize = l->getContentSize();
newContentSizeWidth += iSize.width;
newContentSizeHeight = MAX(newContentSizeHeight, iSize.height);
nextPosX += iSize.width;
}
_elementRenderersContainer->setContentSize(Size(newContentSizeWidth, newContentSizeHeight));
}
else
{
float newContentSizeHeight = 0.0f;
float *maxHeights = new float[_elementRenders.size()];
for (size_t i=0; i<_elementRenders.size(); i++)
{
Vector<Node*>* row = (_elementRenders[i]);
float maxHeight = 0.0f;
for (ssize_t j=0; j<row->size(); j++)
{
Node* l = row->at(j);
maxHeight = MAX(l->getContentSize().height, maxHeight);
}
maxHeights[i] = maxHeight;
newContentSizeHeight += maxHeights[i];
}
float nextPosY = _customSize.height;
float realWidth = 0 ;
for (size_t i=0; i<_elementRenders.size(); i++)
{
Vector<Node*>* row = (_elementRenders[i]);
float nextPosX = 0.0f;
nextPosY -= (maxHeights[i] + _verticalSpace);
for (ssize_t j=0; j<row->size(); j++)
{
Node* l = row->at(j);
l->setAnchorPoint(Vec2::ZERO);
l->setPosition(nextPosX, nextPosY);
_elementRenderersContainer->addChild(l, 1);
nextPosX += l->getContentSize().width;
}
if( realWidth < nextPosX )
{
realWidth = nextPosX ;
}
}
float realHeight = _customSize.height - nextPosY;
this->xxrealSize.height = realHeight;
this->xxrealSize.width = realWidth ;
_elementRenderersContainer->setContentSize(_contentSize);
delete [] maxHeights;
}
size_t length = _elementRenders.size();
for (size_t i = 0; i<length; i++)
{
Vector<Node*>* l = _elementRenders[i];
l->clear();
delete l;
}
_elementRenders.clear();
if (_ignoreSize)
{
Size s = getVirtualRendererSize();
this->setContentSize(s);
}
else
{
this->setContentSize(_customSize);
}
updateContentSizeWithTextureSize(_contentSize);
_elementRenderersContainer->setPosition(_contentSize.width / 2.0f, _contentSize.height / 2.0f);
}
Size RichText::xxgetRealSize()
{
this->formatText();
return this->xxrealSize;
}
void RichText::adaptRenderers()
{
this->formatText();
}
void RichText::pushToContainer(cocos2d::Node *renderer)
{
if (_elementRenders.size() <= 0)
{
return;
}
_elementRenders[_elementRenders.size()-1]->pushBack(renderer);
}
void RichText::setVerticalSpace(float space)
{
_verticalSpace = space;
}
void RichText::setAnchorPoint(const Vec2 &pt)
{
Widget::setAnchorPoint(pt);
_elementRenderersContainer->setAnchorPoint(pt);
}
Size RichText::getVirtualRendererSize() const
{
return _elementRenderersContainer->getContentSize();
}
void RichText::ignoreContentAdaptWithSize(bool ignore)
{
if (_ignoreSize != ignore)
{
_formatTextDirty = true;
Widget::ignoreContentAdaptWithSize(ignore);
}
}
std::string RichText::getDescription() const
{
return "RichText";
}
}
NS_CC_END
3,getSubStringOfUTF8String这个截取字符串的接口要修改,它的返回值不对。
std::string Helper::getSubStringOfUTF8String(const std::string& str, std::string::size_type start, std::string::size_type length)
{
if (length==0)
{
return "";
}
std::string::size_type c, i, ix, q, min=std::string::npos, max=std::string::npos;
for (q=0, i=0, ix=str.length(); i < ix; i++, q++)
{
if (q==start)
{
min = i;
}
if (q <= start+length || length==std::string::npos)
{
max = i;
}
c = (unsigned char) str[i];
if (c<=127) i+=0;
else if ((c & 0xE0) == 0xC0) i+=1;
else if ((c & 0xF0) == 0xE0) i+=2;
else if ((c & 0xF8) == 0xF0) i+=3;
else return "";
}
if (q <= start+length || length == std::string::npos)
{
max = i;
}
if (min==std::string::npos || max==std::string::npos)
{
return "";
}
return str.substr(min,max-min);
}
4,在CCDevice.h中新增计算字体大小的接口,具体实现和平台相关,这里我只实现的ios平台。
static bool CalculateTextSize(const char * text, const char * fontName, float size, float &width, float &height ) ; //@cxx
5,ios平台CalculateTextSize的实现
static CGSize _my_calculateStringSize(NSString *str, id font, CGSize *constrainSize)
{
CGSize textRect = CGSizeZero;
textRect.width = constrainSize->width > 0 ? constrainSize->width
: 0x7fffffff;
textRect.height = constrainSize->height > 0 ? constrainSize->height
: 0x7fffffff;
CGSize dim;
if(s_isIOS7OrHigher){
NSDictionary *attibutes = @{NSFontAttributeName:font};
dim = [str boundingRectWithSize:textRect options:(NSStringDrawingOptions)(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:attibutes context:nil].size;
}
else {
dim = [str sizeWithFont:font constrainedToSize:textRect];
}
dim.width = (dim.width);
dim.height = (dim.height);
return dim;
}
bool Device::CalculateTextSize(const char * text, const char * fontName, float size, float &width, float &height )
{
bool ret = false ;
CGSize dim , constrainSize ;
dim.width = 0.0f ;
dim.height = 0.0f;
constrainSize.width = 0.0f ;
constrainSize.height = 0.0f ;
do
{
CC_BREAK_IF(! text );
NSString * str = [NSString stringWithUTF8String:text];
NSString * fntName = [NSString stringWithUTF8String:fontName];
fntName = [[fntName lastPathComponent] stringByDeletingPathExtension];
id font = [UIFont fontWithName:fntName size:size];
if (font)
{
dim = _my_calculateStringSize(str, font, &constrainSize);
}
else
{
if (!font)
{
font = [UIFont systemFontOfSize:size];
}
if (font)
{
dim = _my_calculateStringSize(str, font, &constrainSize);
}
}
if (font)
{
ret = true ;
}
}while(false);
width = dim.width ;
height = dim.height ;
return ret;
}
测试示例
auto _richText = RichText::create();
_richText->ignoreContentAdaptWithSize(false);
_richText->setContentSize(Size(100, 100));
auto str1 = "是减肥了快iii速的减肥了快SD卡路附近ffm国恢复共和国fkdsjfkldsj假了jjiij经济ii的快速减肥了mshfjksdhKSDFHJKDSJFKLlfj。、里的时刻福建路口的.sdfdsjflkjlksdjfl JlkdsjflkdsfjkldsjflkdsjfkljLjkd是福建代理商福建路口的";
RichElementText* re1 = RichElementText::create(1, Color3B::WHITE, 255, str1, "Marker Felt", 20);
_richText->pushBackElement(re1);
_richText->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 100));
this->addChild(_richText);
系统默认的对齐效果图:
修改后的对齐效果图:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)