本文主要是介绍Cocos2d-x 3.9教程:9. Cocos2d-x中基于布局的容器控件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Cocos2d-x 3.9教程
9. Cocos2d-x中基于布局的容器控件
1.1. ScrollView滚动视图
滚动视图是一种常见的容器型控件,它里面可以放置其他组件。Cocos2d-x中的ScrollView本身也是一种Layout,所以也可以在其中使用线形或者相对布局。ScrollView的显示效果,主要是取决于:
setContentSize(),设置显示出来的区域大小“视口”。
setInnerContainerSize(),设置内部容器的大小,决定了拖动的区域大小;它必须大于或等于setContentSize()。
以及添加进内部的组件和布局位置。
ScrollView,内部的容器为innerLayout,我们的控件放在这个layout上。任一时刻拖动ScrollView,会在“视口”显示innerLayout的一部分:效果示意图如下:
拖动视口,改变的是内部容器innerLayout的位置偏移。则通过视口看到的内容得以改变:
注意,因此内部容器innerLayout上我们加入的控件,需要我们自己来摆放到正确的位置上。
#include "ui/UIScrollView.h"
ui::ScrollView *scrollView = ui::ScrollView::create();
scrollView->setBackGroundColorType(ui::Layout::BackGroundColorType::SOLID);
scrollView->setBackGroundColor(Color3B::RED);
scrollView->setPosition(Vec2(50, 100));
scrollView->setContentSize(Size(400, 300));//外部显示的大小
scrollView->setBounceEnabled(true);//是否开启拖动到头后的反弹效果
scrollView->setInnerContainerSize(Size(500, 500));//内部的尺寸,决定了拖动的区域大小;它必须大于或等于setContentSize
//scrollView->setTouchEnabled(true);//是否允许拖动空白处滚动视图,默认为是;否的话,只能拖动里面的组件来滚动视图
scrollView->setDirection(ui::ScrollView::Direction::VERTICAL);//允许拖动的方向
scrollView->setLayoutType(Layout::Type::VERTICAL);//垂直布局;ScrollView本身就是一种Layout
//绑定响应事件
scrollView->addEventListener(CC_CALLBACK_2(HelloWorld::onScrollViewEvent, this));
Button * button1 = Button::create("button.png");
scrollView->addChild(button1);
Button * button2 = Button::create("potentiometerButton.png");
scrollView->addChild(button2);
Button * button3 = Button::create("potentiometerProgress.png");
scrollView->addChild(button3);
Button * button4 = Button::create("potentiometerTrack.png");
scrollView->addChild(button4);
Button * button5 = Button::create("button_normal.png");
scrollView->addChild(button5);
Button * button6 = Button::create("CloseNormal.png");
scrollView->addChild(button6);
this->addChild(scrollView);
void HelloWorld::onScrollViewEvent(Ref* pSender, cocos2d::ui::ScrollView::EventType event)
{
ui::ScrollView * scrollView = (ui::ScrollView *)pSender;
switch (event)
{
case cocos2d::ui::ScrollView::EventType::SCROLL_TO_TOP:
CCLOG("scrollView is scroll to top");
break;
case cocos2d::ui::ScrollView::EventType::SCROLLING:
//CCLOG("scrollView is scrolling");
break;
case cocos2d::ui::ScrollView::EventType::SCROLL_TO_BOTTOM:
CCLOG("scrollView is scroll to bottom");
break;
case cocos2d::ui::ScrollView::EventType::BOUNCE_TOP:
CCLOG("scrollView is bounce to top");
break;
case cocos2d::ui::ScrollView::EventType::BOUNCE_BOTTOM:
CCLOG("scrollView is bounce to bottom");
break;
}
}
注意,由于cocos2d::extension库中也有ScrollView,为了避免编译时的冲突。建议类的名字写全路径,如:cocos2d::ui::ScrollView在使用Event时,同样也需要使用全路径:cocos2d::ui::ScrollView::EventType。
最终效果如下:
初始位置:
拖动到最下面的位置:
由于我们设定了允许回弹,所以拖动到某一端时,还可以继续拖动一段距离,松开即可回弹:
1.2. TableView桌面视图
注意,目前至最新版cocos2d-x3.9为止,TableView是属于extension库的,它继承自extension库的ScrollView,而不是UI库中的ScroolView。
TableView是ScrollView的子类,是一种特殊的ScrollView。
它跟ScrollView类似,可以横向、纵向添加子节点。但TableView是为了解决容器内部出现大量类型相同、但内容数值不同的子节点时,一次性创建过多的子节点而占用大量资源的问题。TableView只会在需要时创建内部单元格(子节点TableViewCell),隐藏在可见区域之外的cell会被重复利用。
比如我们在传统的ScrollView中创建4个子节点,大体效果如下:
其中节点1完全不可见,被隐藏了起来。
我们在TableView中,可以重复利用这些“被隐藏起来的、类型相同的单元格”。在节点1被完全隐藏、不可见时(位于视图可见区域以外),而同时右边的节点4需要被显示出来。我们可以在这一霎那动态的创建节点4,但这样做我们并没有最佳的利用资源,因为随着后续的节点越来越多,TableView内部的单元格cell会越来越多。
但经过观察,节点1是已经完全显示不出来的,因此我们可以拿节点1这个对象出来,放到节点4应当出现的位置,然后把其上的信息修改为节点4应当显示的信息。如图所示:
这样一来,上述示例情况下,我们永远只需要创建3个单元格即可达到创建N个节点的效果。这3个单元格一直被重复利用。但须注意的是,比如在TableView视图上滑动时,任一单元格节点上的值,应当予以更新。
使用起来需要以下几个步骤:
1.在场景类的头文件中:
#include "extensions/GUI/CCScrollView/CCTableView.h"
USING_NS_CC_EXT;
//实现TableViewDelegate接口,以响应触摸事件
//实现TableViewDataSource接口,以在创建内部Cell时提供必要数据
class HelloWorld : public cocos2d::Layer, public TableViewDelegate, public TableViewDataSource
{
......
//for TableViewDelegate
virtual void tableCellTouched(TableView* table, TableViewCell* cell);
//for TableViewDataSource
virtual TableViewCell* tableCellAtIndex(TableView *table, ssize_t idx);
virtual ssize_t numberOfCellsInTableView(TableView *table);
virtual Size tableCellSizeForIndex(TableView *table, ssize_t idx);
}
TableViewDelegate提供了触摸事件等接口。在事件发生时回调这些方法。
TableViewDataSource提供了TableView的数据源,在创建TableView及内部单元时,回调相应的方法,我们在这些方法中定制视图和具体的内容。
2.在相应的cpp文件中创建TableView并实现这些接口
//init()方法中
//当前类作为数据源,视图大小为500,400
TableView * tbView = TableView::create(this, Size(500, 400));
tbView->setDelegate(this);//当前类作为事件代理
tbView->setDirection(ScrollView::Direction::HORIZONTAL);//TableView中单元格的方向
tbView->setPosition(Vec2(100, 50));
tbView->setVerticalFillOrder(TableView::VerticalFillOrder::TOP_DOWN);//设定垂直填充方向
this->addChild(tbView);
tbView->reloadData();//从dataSource数据源加载数据,视图将被刷新
//实现各种必要接口
//界面上点击任何一个cell时,会调用此函数
void HelloWorld::tableCellTouched(TableView* table, TableViewCell* cell)
{
CCLOG("table cell touched at index:%d", cell->getIdx());
}
TableViewCell* HelloWorld::tableCellAtIndex(TableView *table, ssize_t idx)
{
//出队一个空闲的(没有被显示出来时,认为是空闲的)cell,重复利用它
TableViewCell * tbCell = table->dequeueCell();
string strIndex = StringUtils::format("cell%d", idx);
//如果没有空闲的cell,则创建一个
if (tbCell == NULL)
{
tbCell = TableViewCell::create();
Sprite * sp = Sprite::create("potentiometerButton.png");
sp->setAnchorPoint(Vec2::ZERO);
sp->setPosition(Vec2::ZERO);
tbCell->addChild(sp);
Label *label = Label::createWithTTF(strIndex.c_str(), "fonts/arial.ttf", 24, Size::ZERO, TextHAlignment::CENTER);
label->setPosition(Vec2(40, 40));
label->setTag(1);
tbCell->addChild(label);
}
else//原来那个cell对象拿来复用,但其上的信息应当按照实际index进行更新
{
Label * label = (Label*)tbCell->getChildByTag(1);
label->setString(strIndex.c_str());
}
return tbCell;
}
//创建每个cell时,会自动调用此函数,获得某个index下该单元的尺寸
Size HelloWorld::tableCellSizeForIndex(TableView *table, ssize_t idx)
{
if (idx == 2)
return Size(120, 120);
return Size(80, 80);
}
//创建TableView时,会自动调用此函数,获得应当需要创建多少个cell单元
ssize_t HelloWorld::numberOfCellsInTableView(TableView *table)
{
return 10;//它确定了TableView一共有多少个cell单元
}
最终效果如下:
由于我们在tableCellSizeForIndex中进行了判断,若要创建的cell单元格的index为2,则返回Size(120, 120)。因此下标为2的单元格占的大小比其他单元格都要大。
若我们把init()方法中创建TableView时的排列方向进行修改,如:
tbView->setDirection(ScrollView::Direction::VERTICAL);//TableView中单元格的方向
则最终效果如下:
1.3. PageView页面视图
容器型控件,一次显示一页,每个页面之间左右拖拽实现切换。
静止效果,第1页和第3页不可见:
拖拽中的效果,中间蓝框部分可见,其余不可见:
注意,上图中蓝色框为可见部分大小(也就是控件的content size)与页面的大小没有关系。而每一页的大小应当保持一致。
创建PageView控件,然后再创建每一页,每一页必须是一个“Layout”类型(或子类型)的,然后PageView再把每一页添加进去。PageView的可显示部分大小,通过setContentSize()来设定。每个页面Layout的大小,也通过其setContentSize()方法来设定。PageView和页面Layout之间的大小关系,应当提前予以设计。
#include "ui/UIPageView.h"
#include "ui/UILayout.h"
PageView * pageView = PageView::create();
pageView->setContentSize(Size(300, 400));
pageView->setPosition(Vec2(50, 100));
for (int i = 0; i < 3; i++)
{
Layout *layout = Layout::create();
layout->setLayoutType(Layout::Type::ABSOLUTE);
layout->setContentSize(Size(300, 400));
layout->setBackGroundColorType(Layout::BackGroundColorType::SOLID);
switch (i)
{
case 0:
layout->setBackGroundColor(Color3B::BLUE);
break;
case 1:
layout->setBackGroundColor(Color3B::YELLOW);
break;
case 2:
layout->setBackGroundColor(Color3B::GREEN);
break;
}
string str = StringUtils::format("第%2d页", i + 1);
Label *label = Label::createWithTTF(G2U(str.c_str()), "fonts/abc.ttf", 24);
label->setPosition(Vec2(150, 380));
layout->addChild(label);
Button * button1 = Button::create("button.png");
button1->setPosition(Vec2(150, 300));
layout->addChild(button1);
Button * button2 = Button::create("potentiometerButton.png");
button2->setPosition(Vec2(150, 240));
layout->addChild(button2);
Button * button3 = Button::create("potentiometerProgress.png");
button3->setPosition(Vec2(150, 160));
layout->addChild(button3);
pageView->addPage(layout);
}
//绑定page view的事件响应
pageView->addEventListener(CC_CALLBACK_2(HelloWorld::onPageViewEvent, this));
this->addChild(pageView);
void HelloWorld::onPageViewEvent(Ref* pSender, cocos2d::ui::PageView::EventType event)
{
PageView * pageView = (PageView*)pSender;
//根据手册,page view目前只有一种事件!
switch (event)
{
case PageView::EventType::TURNING:
CCLOG("pageView is turrning");
break;
}
}
最终效果如下:
静态状态:
切换中的状态:
1.4. ListView列表视图
ListView列表视图控件,是一种特殊的ScrollView(ListView类继承自ScrollView)。它提供了纵向或者横向的添加条目,按照列表的方式来管理内部的item。可以方便的获取当前选中的条目,并动态的添加、删除指定下标的条目。
#include "ui/UIListView.h"
#include "ui/UILayout.h"
//创建ListView控件
ListView * listView = ListView::create();
listView->setDirection(ListView::Direction::VERTICAL);//垂直排列item(纵向)
listView->setBackGroundColorType(Layout::BackGroundColorType::SOLID);
listView->setBackGroundColor(Color3B::RED);
listView->setPosition(Vec2(50, 100));
listView->setTouchEnabled(true);
//创建一个“模型”,ListView可以用它克隆出来多个item!
Layout * default_item = Layout::create();
Button * button = Button::create("button.png");
default_item->setContentSize(button->getContentSize());
button->setPosition(Vec2(default_item->getContentSize().width / 2, default_item->getContentSize().height / 2));
button->setTitleText("model button");
default_item->addChild(button);
listView->setItemModel(default_item);//ListView绑定该模型
//我们listView的大小是要根据内容来设定的。所以先设定内容大小,再设定listView大小
listView->setContentSize(Size(default_item->getContentSize().width, 500));
//根据设定的“模型”,连续克隆出来3个并插入末尾
listView->pushBackDefaultItem();
listView->pushBackDefaultItem();
listView->pushBackDefaultItem();
//插入自定义(非模型)的item
//插入一个按钮
Button * customButton = Button::create("button.png");
customButton->setTitleText("button1");
listView->pushBackCustomItem(customButton);
//把一个layout当做一个item插入
Layout *customItem2 = Layout::create();
customItem2->setLayoutType(Layout::Type::ABSOLUTE);
customItem2->setContentSize(Size(300, 400));
customItem2->setBackGroundColorType(Layout::BackGroundColorType::SOLID);
customItem2->setBackGroundColor(Color3B::YELLOW);
string str = StringUtils::format("测试条目");
Label *label = Label::createWithTTF(G2U(str.c_str()), "fonts/abc.ttf", 24);
label->setPosition(Vec2(150, 380));
customItem2->addChild(label);
Button * button1 = Button::create("button.png");
button1->setPosition(Vec2(150, 300));
customItem2->addChild(button1);
Button * button2 = Button::create("potentiometerButton.png");
button2->setPosition(Vec2(150, 240));
customItem2->addChild(button2);
Button * button3 = Button::create("potentiometerProgress.png");
button3->setPosition(Vec2(150, 160));
customItem2->addChild(button3);
listView->pushBackCustomItem(customItem2);
listView->addEventListener((ListView::ccListViewCallback)CC_CALLBACK_2(HelloWorld::onPageViewEvent, this));
this->addChild(listView);
void HelloWorld::onListViewEvent(Ref* pSender, cocos2d::ui::ListView::EventType event)
{
switch (event)
{
case ListView::EventType::ON_SELECTED_ITEM_START:
CCLOG("on selected item start");
break;
case ListView::EventType::ON_SELECTED_ITEM_END:
CCLOG("on selected item end");
break;
}
}
最终效果如下:
1.5. RichText多格式文本
1.5.1. 基本用法
RichText多格式文本控件,也称富文本。它提供了一般文本框所不能提供的一些丰富功能,比如同一个文本框内不同问的颜色不同、字体不同、字号不同,图、文混编等等。
#include "ui/UIRichText.h"
RichText * richText = RichText::create();
richText->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2));
richText->setContentSize(Size(300, 400));
//ture,忽略自身的size,使用内部纹理的size;若一个字符串很长,richText长度会跟着变长。
//false的话反之。若字符串过长,会让其“自动换行”
richText->ignoreContentAdaptWithSize(false);
//richText->setVerticalSpace(50);//设置行间距
//RichText中的字符串不能强制换行,\n没有效果
RichElementText * re1 = RichElementText::create(1, Color3B::WHITE, 255, G2U("测试字符串1\n"), "fonts/abc.ttf", 24);
RichElementText * re2 = RichElementText::create(2, Color3B::RED, 255, G2U("测试字符串2\n"), "fonts/abc.ttf", 36);
RichElementText * re3 = RichElementText::create(3, Color3B::BLUE, 255/2, G2U("测试字符串3\n"), "fonts/abc.ttf", 24);
RichElementText * re4 = RichElementText::create(4, Color3B::GREEN, 255, G2U("测试字符串4\n"), "fonts/abc.ttf", 24);
RichElementText * re5 = RichElementText::create(5, Color3B::YELLOW, 255, G2U("测试字符串5\n"), "fonts/abc.ttf", 24);
richText->pushBackElement(re1);
richText->pushBackElement(re2);
richText->pushBackElement(re3);
richText->pushBackElement(re4);
richText->pushBackElement(re5);
RichElementText * re6 = RichElementText::create(5, Color3B::YELLOW, 255, G2U("今天天气不错"), "fonts/abc.ttf", 24);
RichElementImage * re7 = RichElementImage::create(7, Color3B::RED, 255, "smile.png");
richText->pushBackElement(re6);
richText->pushBackElement(re7);
//目前只能通过骨骼动画,实现RichText中的某个element的动画效果! //cocostudio::ArmatureDataManager::getInstance()->addArmatureFileInfo("cocosui/100/100.ExportJson");
//cocostudio::Armature *pAr = cocostudio::Armature::create("100");
//pAr->getAnimation()->play("Animation1");
//RichElementCustomNode* recustom = RichElementCustomNode::create(1, Color3B::WHITE, 255, pAr);
//richText->pushBackElement(recustom);
this->addChild(richText);
最终效果如下:
其中蓝色字符串透明度为一半。
1.5.2. 手工换行
暂未找到RichText支持换行的方法,在上述效果中,由于我们设置了richText->ignoreContentAdaptWithSize(false);,字符串会根据RichText的contentSize来自动换行(根据宽度),但“\n”这种换行符是无效的。因此,目前想到的思路是,在ListView中添加RichText作为其item。
#include "ui/UIRichText.h"
#include "ui/UIListView.h"
ListView * listView = ListView::create();
listView->setBackGroundColorType(Layout::BackGroundColorType::GRADIENT);//渐变色
listView->setBackGroundColor(Color3B::RED, Color3B::BLUE);
listView->setContentSize(Size(400, 500));
listView->setPosition(Vec2(50, 100));
for (int i = 0; i < 3; i++)
{
RichText * richText = RichText::create();
richText->setContentSize(Size(400, 100));
richText->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2));
//ture,忽略自身的size,使用内部纹理的size;若一个字符串很长,richText长度会跟着变长。
//false的话反之。若字符串过长,会让其“自动换行”
richText->ignoreContentAdaptWithSize(false);
string player = StringUtils::format("玩家%d:", i + 1);
RichElementText * rePlayer = RichElementText::create(0, Color3B::YELLOW, 255, G2U(player.c_str()),
"fonts/abc.ttf", 24);
RichElementText * re1 = RichElementText::create(1, Color3B::WHITE, 255, G2U("今天天气不错,"),
"fonts/abc.ttf", 24);
RichElementImage * re2 = RichElementImage::create(2, Color3B::RED, 255, "smile.png");
RichElementText * re3 = RichElementText::create(3, Color3B::BLUE, 255, G2U("心情也好起来了!"),
"fonts/abc.ttf", 24);
Sprite * sp = Sprite::create("pentacle.png");
//动画不起作用,因为RichElementCustomNode本身不是Node子类,无法作为sprite的parent
//sp->runAction(RepeatForever::create(RotateBy::create(1, 360)));
//只通过骨骼动画,实现RichText中的某个element的动画效果!
//cocostudio::ArmatureDataManager::getInstance()->addArmatureFileInfo("cocosui/100/100.ExportJson");
//cocostudio::Armature *pAr = cocostudio::Armature::create("100");
//pAr->getAnimation()->play("Animation1");
//RichElementCustomNode* recustom = RichElementCustomNode::create(1, Color3B::WHITE, 255, pAr);
//richText->pushBackElement(recustom);
RichElementCustomNode * re4 = RichElementCustomNode::create(4, Color3B::BLACK, 255, sp);
richText->pushBackElement(rePlayer);
richText->pushBackElement(re1);
richText->pushBackElement(re2);
richText->pushBackElement(re3);
richText->pushBackElement(re4);
listView->pushBackCustomItem(richText);
}
this->addChild(listView);
最终效果如下:
这篇关于Cocos2d-x 3.9教程:9. Cocos2d-x中基于布局的容器控件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!