**************************************************************************
时间:2015-01-09
作者:Sharing_Li
转载出处:http://www.jb51.cc/article/p-cjqisawf-rx.html
***************************************************************************
玩过《开心消消乐》这款游戏的人,应该知道里面有这样一处设计,如下图:
我们可以左右滑动界面,也可以上下滑动界面,左右滑动的时候不能上下滑动,上下滑动的时候不能左右滑动。这种效果可以用TableView和ScrollView来组合实现,即先弄一个ScrollView,然后把2个TableView当作内容放入这个ScrollView中就可以了,这种UI设计也应用在《开心消消乐》其好友信件中,只不过多了一个TableView。
接下来将进行代码讲解,cocos2dx的版本是3.2,先展示一下实现之后的效果图:
看完效果图,再看正文,定义一个类:CombineView
头文件:CombineView.h
#ifndef __COMBINE_VIEW_H__ #define __COMBINE_VIEW_H__ #include "cocos2d.h" #include "extensions/cocos-ext.h" USING_NS_CC; USING_NS_CC_EXT; enum Table { Table_Left = 0,Table_Center,Table_Right }; class CombineView : public Layer,TableViewDataSource,TableViewDelegate { public: CombineView(); ~CombineView(); virtual bool init(); static cocos2d::Scene * create(); virtual Size tableCellSizeForIndex(TableView *table,ssize_t idx); virtual TableViewCell* tableCellAtIndex(TableView *table,ssize_t idx); virtual ssize_t numberOfCellsInTableView(TableView *table); virtual void tableCellTouched(TableView* table,TableViewCell* cell); virtual void scrollViewDidScroll(ScrollView* view); virtual void scrollViewDidZoom(ScrollView* view); public: void SetTouch(bool isTouched); //对scrollview的调整 void adjustScrollView(float offset); private: ScrollView * m_scrollView; TableView * m_leftTable; TableView * m_centerTable; TableView * m_rightTable; //scrollview当前显示的页数 int m_curPage; //第一个触摸点 Vec2 m_firstPoint; //scrollview的偏移 Vec2 m_offset; //判断第一次滑动方向 bool m_horizontal; bool m_vertical; //View的大小 Size m_viewSize; }; #endif // !__COMBINE_VIEW_H__
再看看cpp文件的实现,这里对主要的代码进行讲解,想要完整代码和资源,请到文章末尾点击下载(0下载积分)。
我们写代码,要养成初始化成员变量的习惯,这样可以避免一些意想不到的错误。同时记住不用的资源要记得释放。
CombineView::CombineView() { m_scrollView = NULL; m_leftTable = NULL; m_centerTable = NULL; m_rightTable = NULL; m_curPage = 0; m_firstPoint = Vec2(0,0); m_offset = Vec2(0,0); m_vertical = false; m_horizontal = false; m_viewSize = Size(0,0); }
如效果图所示,我们要搞一个scrollview,这家伙呢,怀了5个月的三胞胎,分别是三个tableview。为了区别这三个儿子(喂,你怎么知道都是男的而不是女的),我们要给他们取名字,因为他们仨要共用一个函数即tableCellAtIndex,如果不取名,怎么知道谁是老二老三呢, 如头文件中定义的枚举类。
m_scrollView = ScrollView::create(); m_scrollView->setViewSize(m_viewSize); m_scrollView->setContentOffset(Point::ZERO); m_scrollView->setDelegate(this); m_scrollView->setDirection(ScrollView::Direction::HORIZONTAL); m_scrollView->setAnchorPoint(Point::ZERO); m_scrollView->setPosition(Vec2::ZERO); m_scrollView->setTouchEnabled(false);//因为我们不需要scrollview的触摸,因为太糟糕~ pView->addChild(m_scrollView); //添加内容 auto pContainer = Layer::create(); pContainer->setContentSize(Size(m_viewSize.width * 3,m_viewSize.height)); pContainer->setAnchorPoint(Point::ZERO); pContainer->setPosition(Vec2::ZERO); m_scrollView->setContainer(pContainer); //添加tabelview auto containerSize = pContainer->getContentSize(); m_leftTable = TableView::create(this,ViewSize); m_leftTable->setTag(Table_Left); m_leftTable->ignoreAnchorPointForPosition(false); m_leftTable->setAnchorPoint(Vec2(0.5,0.5)); m_leftTable->setPosition(Vec2(containerSize.width / 6,containerSize.height / 2)); m_leftTable->setDirection(ScrollView::Direction::VERTICAL); m_leftTable->setDelegate(this); m_leftTable->setVerticalFillOrder(TableView::VerticalFillOrder::TOP_DOWN); m_leftTable->reloadData(); pContainer->addChild(m_leftTable); m_centerTable = TableView::create(this,ViewSize); m_centerTable->setTag(Table_Center); m_centerTable->ignoreAnchorPointForPosition(false); m_centerTable->setAnchorPoint(Vec2(0.5,0.5)); m_centerTable->setPosition(Vec2(containerSize.width / 2,containerSize.height / 2)); m_centerTable->setDirection(ScrollView::Direction::VERTICAL); m_centerTable->setDelegate(this); m_centerTable->setVerticalFillOrder(TableView::VerticalFillOrder::TOP_DOWN); m_centerTable->reloadData(); pContainer->addChild(m_centerTable); m_rightTable = TableView::create(this,ViewSize); m_rightTable->setTag(Table_Right); m_rightTable->ignoreAnchorPointForPosition(false); m_rightTable->setAnchorPoint(Vec2(0.5,0.5)); m_rightTable->setPosition(Vec2(containerSize.width / 6 * 5,containerSize.height / 2)); m_rightTable->setDirection(ScrollView::Direction::VERTICAL); m_rightTable->setDelegate(this); m_rightTable->setVerticalFillOrder(TableView::VerticalFillOrder::TOP_DOWN); m_rightTable->reloadData(); pContainer->addChild(m_rightTable);
然后我们再来看看触摸函数的实现,首先是touchbegan:
auto listenerT = EventListenerTouchOneByOne::create(); listenerT->onTouchBegan = [=](Touch * touch,Event * pEvent){ m_firstPoint = touch->getLocation(); m_offset = m_scrollView->getContentOffset(); if (!m_scrollView->getBoundingBox().containsPoint(m_firstPoint)) { return false; } return true; };
简洁明了(.......),然后再看touchmoved:
listenerT->onTouchMoved = [=](Touch * touch,Event * pEvent){ auto movePoint = touch->getLocation(); auto distance = movePoint.x - m_firstPoint.x; if ((distance > 0 && this->m_curPage == 0) || (distance < 0 && this->m_curPage == 2)) { return; } //限制滑动方向,避免scorll和table同时滑动 if (fabs(movePoint.y - m_firstPoint.y) / fabs(distance) > 0.7 || m_vertical) { if (!m_horizontal) { m_vertical = true; } return; } else //水平 { if (!m_vertical) { m_horizontal = true; } } if (m_horizontal) { this->SetTouch(false); } m_scrollView->setContentOffset(Vec2(distance + m_offset.x,0)); };
这一段代码的意思是:如果你先垂直滑动,那么就将m_vertical设置为true,这样你就不能水平滑动了;如果你先水平滑动,就将m_horizontal设置为true,因而调用函数SetTouch,对着三个孩子tableview唱摇篮曲,要他们乖乖睡觉不要乱动。然后再来看看touchended:
listenerT->onTouchEnded = [=](Touch * touch,Event * pEvent){ auto endPoint = touch->getLocation(); auto distance = endPoint.x - m_firstPoint.x; //优化滑动效果 bool flag = false; if (fabsf(distance) < 60) { flag = true; if (distance < 0) { m_curPage--; } else if (distance > 0) { m_curPage++; } } //限制滑动方向,避免scroll和table同时滑动 if (m_vertical) { m_vertical = false; if (flag) { if (distance > 0) { m_curPage--; } else if (distance < 0) { m_curPage++; } } return ; } else { this->SetTouch(true); } this->adjustScrollView(distance); m_horizontal = false; };
这一段代码的意思是:if (fabsf(distance) < 60)这个if语句是对滑动效果的优化,如果滑动很小距离,那么就忽视这次滑动,视图还是老样子,效果图如下:
这下应该一目了然了吧,接下来的代码是判断是先垂直滑动还是水平滑动,如果是先垂直,则直接return,return之前呢要还原m_curPage的值。如果是先水平,则要把三个熟睡的孩子搞醒。然后是对scrollview最终显示界面的调整:
void CombineView::adjustScrollView(float offset) { if (offset < 0) { m_curPage++; } else if (offset > 0) { m_curPage--; } if (m_curPage < 0) { m_curPage = 0; } else if (m_curPage > 2) { m_curPage = 2; } auto adjustPoint = Vec2(-m_viewSize.width * m_curPage,0); m_scrollView->setContentOffsetInDuration(adjustPoint,0.1f); }
未列出的部分代码如下:
TableViewCell* CombineView::tableCellAtIndex(TableView *table,ssize_t idx) { auto cell = table->dequeueCell(); auto cellSize = this->tableCellSizeForIndex(table,idx); auto tag = table->getTag(); if (!cell) { cell = new TableViewCell(); cell->autorelease(); Sprite * pCellBg = NULL; Label * pNum = NULL; Sprite * pIcon = NULL; switch (tag) { case Table_Left: { pCellBg = Sprite::create("combineview/cell.png"); pNum = Label::createWithTTF("1","fonts/Marker Felt.ttf",20); pIcon = Sprite::create("combineview/book.png"); } break; case Table_Center: { pCellBg = Sprite::create("combineview/cell2.png"); pNum = Label::createWithTTF("2",20); pIcon = Sprite::create("combineview/plane.png"); } break; case Table_Right: { pCellBg = Sprite::create("combineview/cell3.png"); pNum = Label::createWithTTF("3",20); pIcon = Sprite::create("combineview/setting.png"); } default: break; } pCellBg->setPosition(Vec2(cellSize.width / 2,cellSize.height / 2)); cell->addChild(pCellBg); pNum->setColor(Color3B(255,0)); pNum->setPosition(Vec2(cellSize.width * 0.1,cellSize.height / 2)); cell->addChild(pNum); pIcon->setPosition(Vec2(cellSize.width * 0.85,cellSize.height / 2)); pIcon->setScale(0.2); cell->addChild(pIcon); } return cell; }
void CombineView::SetTouch(bool isTouched) { m_leftTable->setTouchEnabled(isTouched); m_centerTable->setTouchEnabled(isTouched); m_rightTable->setTouchEnabled(isTouched); }
最后,完了。。。。。。。。。。才怪!
代码其实有问题,我故意留了一个bug,不知道大家发现没,这个bug不解决的话,程序跑起来会崩溃的。如果按照我之前的代码来运行的话,会在tableCellAtIndex函数中崩溃,这是为什么呢?因为我们在创建tableview的时候,给每个tableview设置tag并没有成功,那为什么没成功呢?因为我们还没设置好tag的时候,tableCellAtIndex这斯就跑起来了,我们通过table->getTag(),其实是取不到tag的,既然取不到,那么之后就不能创建图片文字,会调用空指针,所以程序就BOOM了。那么罪魁祸首就是TableView::create(this,ViewSize);这个家伙了,我们调试跟踪进源码,如下:
TableView* TableView::create(TableViewDataSource* dataSource,Size size,Node *container) { TableView *table = new TableView(); table->initWithViewSize(size,container); table->autorelease(); table->setDataSource(dataSource); table->_updateCellPositions(); table->_updateContentSize(); return table; }
倒数第二句table->_updateContentSize();里面会调用tableCellAtIndex这个函数。那么找到问题了该怎么解决呢,难懂要改源码?不用,我们可以这样创建tableview,如下:
//m_rightTable = TableView::create(this,ViewSize); m_rightTable = new TableView(); m_rightTable->initWithViewSize(m_viewSize,NULL); m_rightTable->autorelease(); m_rightTable->setDataSource(this);
那么为什么不把table->_updateCellPositions();也搞进来,因为这是保护成员函数,所以不能访问,而且也用不上,以后遇到类似的问题也可以这样解决。然后把三个tableview改过来就ok啦。