cocos2dx之TableView和ScrollView的混合使用

**************************************************************************

時間:2015-01-09

作者:Sharing_Li

轉載出處:http://blog.csdn.net/sharing_li/article/details/42298317

***************************************************************************

 

玩過《開心消消樂》這款遊戲的人,應該知道里面有這樣一處設計,如下圖:

         我們可以左右滑動界面,也可以上下滑動界面,左右滑動的時候不能上下滑動,上下滑動的時候不能左右滑動。這種效果可以用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;
	};

PS:要將m_firstPoint轉換成和m_scrollView同一個父節點下的座標,即調用m_firstPoint = pView->convertToNodeSpace(m_firstPoint)

簡潔明瞭(.......),然後再看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","fonts/Marker Felt.ttf",20);
				pIcon = Sprite::create("combineview/plane.png");
			}
			break;
		case Table_Right:
			{
				pCellBg = Sprite::create("combineview/cell3.png");
				pNum = Label::createWithTTF("3","fonts/Marker Felt.ttf",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,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啦。

代碼及資源下載處:http://download.csdn.net/detail/sharing_li/8345111


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章