cocos2d-x 源碼 :可以循環的CCScrollView (代碼已經重構過,附使用方法)

cocos2d-x源碼總目錄

http://blog.csdn.net/u011225840/article/details/31743129

1.準備工作

想弄懂可循環的CCscrollView,首先請閱讀cocos2d-x本身的CCscrollView源碼http://blog.csdn.net/u011225840/article/details/30033501(我已經添加註釋,方便閱讀)。

2.源碼展示

因爲源碼我想放到git上,所以註釋都是用的英文,如果這部分源碼有人有問題,請在評論區留言,我會逐一回答。
總體說下,CCCycleScrollview繼承了CCscrollView以及CCscrollViewDelegate,這樣保證了代碼的可擴展性。
同時,CCCycleScrollview參考了tableView,使用了CCCycleCell,將部分界面和數據部分解耦。
/****************************************************************************
 Author : poplaryang 
		  2014- 06
 
 http://blog.csdn.net/u011225840

 
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 ****************************************************************************/

#include "cocos2d.h"
#include "cocos-ext.h"
#include <vector>
using namespace cocos2d;
using namespace cocos2d::extension;

#define SCROLL_DEACCEL_RATE  0.95f
#define SCROLL_DEACCEL_DIST  1.0f

/*
	abstract class 
	the class defines that the what a cycleCell needs to do.
*/
class CCCycleCell :public CCNode
{
public:
	//The cell has been selected ,so you can do something with that, Like  highlight or what ever you want.
	virtual void getSelected() = 0;
	//The cell must show the different view with the different index. Like, the hours within 12.
	virtual void updateWithIndex(unsigned int index) = 0;

	unsigned int getIndex(){return m_index;}



protected:
	unsigned int m_index; 
};

//The moving direction 
enum MovingDirection
{
	Left = 1,
	Right = 2,
	Up,
	Down
};

//The direction that the view allowd to move, the view can't do the both like CCScrollView.
enum Direction
{
	CycleDirectionHorizontal=1,
	CycleDirectionVertical

};

/*
	The view that can show the data with a cycle way.(forgive my awful English ....)
	Eg. The clock the view in iphone, the view will show 0 after 23,otherwise ,you move the opposite direction 23,22,21....until 0.
	
*/
class CCCycleScrollView: public CCScrollView,public CCScrollViewDelegate
{
public:
	CCCycleScrollView();
	virtual ~CCCycleScrollView();

	/*
		The background node is the background that will be show cyclely.
		The nodeCount is the count that every background node can hold the cycleCell.
		The totalCount is the count that how many different data in the cycle cell.Eg...The hour in a day ,can have the 24 totalCount.
	*/
	static CCCycleScrollView* create(std::vector<CCNode*>& background,std::vector<CCCycleCell*>& cycleCell,unsigned int nodeCount =0,unsigned int totalCount = 0,Direction direction = CycleDirectionHorizontal);

	/*
		init methods.
	*/
	bool initWithViewSize(std::vector<CCNode*>& background,std::vector<CCCycleCell*>& cycleCell,unsigned int nodeCount =0,unsigned int totalCount = 0,Direction direction = CycleDirectionHorizontal);
	
	/*
		TouchDelegate.
	*/
	bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
	void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
	void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
	void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);
protected:
	
	//CCScrollViewDelegate
	virtual void scrollViewDidScroll(CCScrollView* view);
	virtual void scrollViewDidZoom(CCScrollView* view);
	virtual void scrollViewDidStop(CCScrollView* view);

	//The delegate must be self, so beyound the class ,anyone can't change the scrollview delegate.
	void setDelegate(){};


	/*
		we should adjust the backgroundNode's position so that the view will cyclely move.
	*/
	void adjustBackgroundNode();

	/*
		The similar functions with the CCScrollView
	*/
	void deaccelerateScrolling(float dt);

	/*
		we should relocate the view to the position where the offset must be integer to make sure that one cycle cell must be selected
	*/
	void relocateContainer();

	/*
		find the final point where the view should be relocated
	*/
	CCPoint findEndPoint();

	/*
		to change the index of the cycle cell
	*/
	void updateCycleCell(bool bothSide = false);

	//The background node that will show cyclely.
	CCNode* m_backgroundNodeLeft;
	CCNode* m_backgroundNodeMiddle;
	CCNode* m_backgroundNodeRight;

	
	CCSize m_backgroundNodeSize;

	//the last touch point 
	CCPoint m_lastPoint;
	//the current touch point , use them to tell the direction that the view is moving.
	CCPoint m_nowPoint;

	//the current postionNum offset of the container,which tells the view when to adjust the backgroudnode.
	float m_nowPositionNum;
	//the last postionNum offset of the container,pay attention to the type of them,one is float and one is integer.
	int m_lastPositionNum;


	//if the backgroundnode has been adjust, the position direction includes right and up.
	bool m_lastPositiveDone;
	//the same as positive,but the direction includes left and down. 
	bool m_lastNegtiveDone;

	//the direction that the view is moving.
	MovingDirection m_moving;

	//limit the direction that the view can move
	Direction m_direction;
	//if the touch can change the direction
	bool m_isTouchDirection;
	
	//The total index of the cycle cell
	unsigned int m_totalCount;
	//the number of the cycle cell that every background should hold
	unsigned int m_nodeCount;

};


#include "CCCycleScrollView.h"

CCCycleScrollView::CCCycleScrollView():
	m_backgroundNodeLeft(NULL),
	m_backgroundNodeMiddle(NULL),
	m_backgroundNodeRight(NULL),
	m_lastPoint(ccp(0.0f,0.0f)),
	m_nowPoint(ccp(0.0f,0.0f)),
	m_lastPositionNum(0),
	m_lastPositiveDone(false),
	m_lastNegtiveDone(false),
	m_isTouchDirection(false)
{

}

CCCycleScrollView::~CCCycleScrollView()
{

}

bool CCCycleScrollView::initWithViewSize(std::vector<CCNode*>& background,std::vector<CCCycleCell*>& cycleCell,unsigned int nodeCount ,unsigned int totalCount ,Direction d )
{
	if (CCLayer::init())
	{
		m_nodeCount = nodeCount;
		m_totalCount = totalCount;

		//initial the background node
		m_backgroundNodeLeft = background[0];
		m_backgroundNodeLeft->ignoreAnchorPointForPosition(false);
		m_backgroundNodeLeft->setAnchorPoint(ccp(0.0f,0.0f));
		

		m_backgroundNodeMiddle = background[1];
		m_backgroundNodeMiddle->ignoreAnchorPointForPosition(false);
		m_backgroundNodeMiddle->setAnchorPoint(ccp(0.0f,0.0f));
		
		
		m_backgroundNodeRight = background[2];
		m_backgroundNodeRight->ignoreAnchorPointForPosition(false);
		m_backgroundNodeRight->setAnchorPoint(ccp(0.0f,0.0f));


		m_backgroundNodeSize = m_backgroundNodeLeft->getContentSize();


		if (d==CycleDirectionHorizontal)
		{
			m_backgroundNodeLeft->setPosition(ccp(-m_backgroundNodeSize.width,0.0f));
			m_backgroundNodeMiddle->setPosition(ccp(0.0f,0.0f));
			m_backgroundNodeRight->setPosition(ccp(m_backgroundNodeSize.width,0.0f));
			m_eDirection  = kCCScrollViewDirectionHorizontal;

		}
		else if(d==CycleDirectionVertical)
		{
			m_backgroundNodeLeft->setPosition(ccp(0.0f,-m_backgroundNodeSize.height));
			m_backgroundNodeMiddle->setPosition(ccp(0.0f,0.0f));
			m_backgroundNodeRight->setPosition(ccp(0.0f,m_backgroundNodeSize.height));
			m_eDirection = kCCScrollViewDirectionVertical;
		}

		//initial the cell in every backNode.
		int index = 0;
		for (index=0;index<nodeCount*3;index++)
		{
			CCCycleCell* tempCell = cycleCell[index];
			tempCell->setTag(index%nodeCount);
			if (m_eDirection==kCCScrollViewDirectionHorizontal)
			{
				tempCell->setAnchorPoint(ccp(0.0f,0.0f));
				CCPoint temp = ccp((m_backgroundNodeSize.width/nodeCount)*(index%nodeCount),0.0f);
				tempCell->setPosition(ccp((m_backgroundNodeSize.width/nodeCount)*(index%nodeCount),0.0f));
			}
			else if (m_eDirection==kCCScrollViewDirectionVertical)
			{
				tempCell->setAnchorPoint(ccp(0.0f,0.0f));
				tempCell->setPosition(ccp(0.0f,(m_backgroundNodeSize.height/nodeCount)*(index%nodeCount)));
			}
			if (index/nodeCount<1)
			{
				m_backgroundNodeLeft->addChild(tempCell);

			}
			else if (index/nodeCount>=1 && index/nodeCount<2)
			{
				m_backgroundNodeMiddle->addChild(tempCell);
				int temp = index-(nodeCount-1);
				tempCell->updateWithIndex(index-(nodeCount-1));
			}
			else
			{
				m_backgroundNodeRight->addChild(tempCell);

			}
			
		}
		updateCycleCell(true);
		
		if (!this->m_pContainer)
		{
			m_pContainer = CCLayer::create();
			this->m_pContainer->ignoreAnchorPointForPosition(false);
			this->m_pContainer->setAnchorPoint(ccp(0.0f, 0.0f));
			this->m_pContainer->addChild(m_backgroundNodeLeft);
			this->m_pContainer->addChild(m_backgroundNodeMiddle);
			this->m_pContainer->addChild(m_backgroundNodeRight);
		}

		this->setViewSize(CCSizeMake(m_backgroundNodeSize.width,m_backgroundNodeSize.height));

		setTouchEnabled(true);
		m_pTouches = new CCArray();
		m_bBounceable = true;
		m_bClippingToBounds = true;

		m_pContainer->setPosition(ccp(0.0f, 0.0f));

		this->addChild(m_pContainer);
		m_direction = d;
		m_pDelegate = this;
		return true;
	}
	return false;
}

CCCycleScrollView* CCCycleScrollView::create(std::vector<CCNode*>& background,std::vector<CCCycleCell*>& cycleCell,unsigned int nodeCount,unsigned int totalCount ,Direction d )
{
	CCCycleScrollView* pRet = new CCCycleScrollView();
	if (pRet && pRet->initWithViewSize(background,cycleCell,nodeCount,totalCount,d))
	{
		pRet->autorelease();
	}
	else
	{
		CC_SAFE_DELETE(pRet);
	}
	return pRet;
}

void CCCycleScrollView::ccTouchEnded( CCTouch *pTouch, CCEvent *pEvent )
{
	
	if (!this->isVisible())
	{
		return;
	}
	
	if (m_pTouches->count()==1)
	{
		this->schedule(schedule_selector(CCCycleScrollView::deaccelerateScrolling));
	}
	
	m_pTouches->removeObject(pTouch);

	//沒有touch時,需要設置狀態
	if (m_pTouches->count() == 0)
	{
		m_bDragging = false;    
		m_bTouchMoved = false;
		m_isTouchDirection = false;
	}
	
}

void CCCycleScrollView::ccTouchMoved( CCTouch *pTouch, CCEvent *pEvent )
{
	m_nowPoint =   convertToWorldSpace(convertTouchToNodeSpace(pTouch));
	CCScrollView::ccTouchMoved(pTouch,pEvent);

}

bool CCCycleScrollView::ccTouchBegan( CCTouch *pTouch, CCEvent *pEvent )
{
	bool result = CCScrollView::ccTouchBegan(pTouch,pEvent);
	m_lastPoint = convertToWorldSpace(convertTouchToNodeSpace(pTouch));
	m_isTouchDirection = true;
	if (m_pTouches->count()>1)
	{
		return false;
	}
	return result;
}

void CCCycleScrollView::adjustBackgroundNode()
{

	//正在向右移動
	CCLog("The moving direction is %d",m_moving);
	CCLog("The now Num is %f",m_nowPositionNum);
	CCLog("The last Num is%d",m_lastPositionNum);
	if (m_direction==CycleDirectionHorizontal)
	{
		if (m_moving==Right)
		{
			if (m_nowPositionNum-m_lastPositionNum>0.5)
			{
				m_lastPositiveDone=true;
				m_lastPositionNum++;
			}
			//
			if (m_lastPositiveDone)
			{
				m_backgroundNodeRight->setPosition(ccp(m_backgroundNodeRight->getPositionX()-m_backgroundNodeSize.width*3,0));
				
				CCNode* temp = m_backgroundNodeRight;
				m_backgroundNodeRight = m_backgroundNodeMiddle;
				m_backgroundNodeMiddle = m_backgroundNodeLeft;
				m_backgroundNodeLeft = temp;
				updateCycleCell();
				m_lastPositiveDone = false;
				
			}
		
		}
		else if (m_moving==Left)
		{
			if (m_lastPositionNum-m_nowPositionNum>=0.5)
			{
				m_lastNegtiveDone=true;
				m_lastPositionNum--;
			}
	
			if (m_lastNegtiveDone)
			{
	
				m_backgroundNodeLeft->setPosition(ccp(m_backgroundNodeLeft->getPositionX()+m_backgroundNodeSize.width*3,0));
				
				CCNode* temp = m_backgroundNodeLeft;
				m_backgroundNodeLeft = m_backgroundNodeMiddle;
				m_backgroundNodeMiddle = m_backgroundNodeRight;
				m_backgroundNodeRight = temp;
				updateCycleCell();
				m_lastNegtiveDone=false;
			}
		}
	}else if (m_direction==CycleDirectionVertical)
	{
		if (m_moving==Up)
		{
			if (m_nowPositionNum-m_lastPositionNum>0.5)
			{
				m_lastPositiveDone=true;
				m_lastPositionNum++;
			}
			
			if (m_lastPositiveDone)
			{
				m_backgroundNodeRight->setPosition(ccp(0.0f,m_backgroundNodeRight->getPositionY()-m_backgroundNodeSize.height*3));
				CCNode* temp = m_backgroundNodeRight;
				m_backgroundNodeRight = m_backgroundNodeMiddle;
				m_backgroundNodeMiddle = m_backgroundNodeLeft;
				m_backgroundNodeLeft = temp;
				updateCycleCell();
				m_lastPositiveDone = false;
				
			}
		
		}
		else if (m_moving==Down)
		{
			if (m_lastPositionNum-m_nowPositionNum>=0.5)
			{
				m_lastNegtiveDone=true;
				m_lastPositionNum--;
			}
	
			if (m_lastNegtiveDone)
			{
	
				m_backgroundNodeLeft->setPosition(ccp(0.0f,m_backgroundNodeLeft->getPositionY()+m_backgroundNodeSize.height*3));
				CCNode* temp = m_backgroundNodeLeft;
				m_backgroundNodeLeft = m_backgroundNodeMiddle;
				m_backgroundNodeMiddle = m_backgroundNodeRight;
				m_backgroundNodeRight = temp;
				updateCycleCell();
	
				m_lastNegtiveDone=false;
			}
		}
	}
	
}

void CCCycleScrollView::updateCycleCell(bool bothSide)
{


	if (m_moving==Right ||bothSide ||m_moving==Up)
	{
		CCCycleCell* cycleCell = static_cast<CCCycleCell*>(m_backgroundNodeMiddle->getChildByTag(0));
		unsigned int index =cycleCell->getIndex();
		
		index--;
		for (int i=m_nodeCount-1;i>=0;i--)
		{
			if (index==0)
			{
				index=m_totalCount;
			}
			((CCCycleCell*)m_backgroundNodeLeft->getChildByTag(i))->updateWithIndex(index);
			index--;
		}
		
	}
	if (m_moving==Left ||bothSide ||m_moving==Down)
	{
		CCCycleCell* cycleCell = static_cast<CCCycleCell*>(m_backgroundNodeMiddle->getChildByTag(m_nodeCount-1));
		unsigned int index = cycleCell->getIndex();
		CCLog("The left index is %d",index);
		CCLog("The total count is %d",m_totalCount);
		index++;
		for (int i=0;i<m_nodeCount;i++)
		{
			if (index>m_totalCount)
			{
				index=1;
				
				CCLog("Beyond the index");
			}
			((CCCycleCell*)m_backgroundNodeRight->getChildByTag(i))->updateWithIndex(index);
			index++;
		}
	}
}

void CCCycleScrollView::deaccelerateScrolling(float dt)
{

	//如果剛好在幀開始前 又有一個觸摸點發生了began,造成了滾動狀態,則取消並返回
    if (m_bDragging)
    {
        this->unschedule(schedule_selector(CCCycleScrollView::deaccelerateScrolling));
        return;
    }
    
	//好玩的東西來咯
	
    float newX, newY;
    CCPoint maxInset, minInset;
    m_pContainer->setPosition(ccpAdd(m_pContainer->getPosition(), m_tScrollDistance));
    
	
  
    newX = m_pContainer->getPosition().x;
    newY = m_pContainer->getPosition().y;
    
    m_tScrollDistance     = ccpSub(m_tScrollDistance, ccp(newX - m_pContainer->getPosition().x, newY - m_pContainer->getPosition().y));
    m_tScrollDistance     = ccpMult(m_tScrollDistance, SCROLL_DEACCEL_RATE);
	//m_nowPoint = ccp(newX,newY);
    this->setContentOffset(ccp(newX,newY));

	//m_isTouchDirection = false;


    if ((fabsf(m_tScrollDistance.x) <= SCROLL_DEACCEL_DIST &&
         fabsf(m_tScrollDistance.y) <= SCROLL_DEACCEL_DIST))
    {
        this->unschedule(schedule_selector(CCCycleScrollView::deaccelerateScrolling));
		//CCLog("stop!!!!");
		this->relocateContainer();
		//m_isTouchDirection = false;
    }
}

void CCCycleScrollView::relocateContainer()
{
	
	
	CCPoint newPoint = findEndPoint();
	
	if (m_direction==CycleDirectionHorizontal)
	{	
		m_nowPositionNum = newPoint.x / m_backgroundNodeSize.width;
		

	}else if (m_direction==CycleDirectionVertical)
	{
		m_nowPositionNum = newPoint.y / m_backgroundNodeSize.height;
	}
	this->setContentOffset(newPoint);
	//this->adjustBackgroundNode();
	
}

cocos2d::CCPoint CCCycleScrollView::findEndPoint()
{
	CCPoint nowPoint ;
	
	nowPoint.x = m_pContainer->getPositionX();
	nowPoint.y = m_pContainer->getPositionY();
	float adjustWidth = m_backgroundNodeSize.width/m_nodeCount;
	float adjustHeight = m_backgroundNodeSize.height/m_nodeCount;
	CCPoint newPoint =ccp(0.0f,0.0f);
	float interval;
	int inter;
	if (m_direction==CycleDirectionHorizontal)
	{
		 interval = (nowPoint.x)/adjustWidth;
		 inter = (int)interval;
		if (fabsf(interval-inter)>=0.5)
		{
			if (inter<0)
			{
				newPoint.x = adjustWidth*(inter-1);
			}
			else
			{
				newPoint.x = adjustWidth*(inter+1);
			}

		}
		else
		{
			newPoint.x = adjustWidth*inter;
		}

		if (newPoint.x>nowPoint.x)
		{
			m_moving = Right;
		}
		else
		{
			m_moving = Left;
		}
	}else if (m_direction==CycleDirectionVertical)
	{
		 interval = (nowPoint.y)/adjustHeight;
		 inter = (int)interval;
		
		if (fabsf(interval-inter)>=0.5)
		{
			if (inter<0)
			{
				newPoint.y = adjustHeight*(inter-1);
			}
			else
			{
				newPoint.y = adjustHeight*(inter+1);
			}

		}
		else
		{
			newPoint.y = adjustHeight*inter;
		}

		if (newPoint.y>nowPoint.y)
		{
			m_moving = Up;
		}
		else
		{
			m_moving = Down;
		}
	}
	/*
	CCLog("The final offset is %f",nowPoint.y);  
	CCLog("The float is %f , the int is %d",interval,inter);  
	CCLog("The endpoint is %f",newPoint.y);  
	//CCLog("The adjustwidth is %f",adjustWidth);
	*/
	//CCLog("The newPoint y is %f",newPoint.y);
	m_lastPoint = nowPoint;
	m_nowPoint = newPoint;
	return newPoint;
}

void CCCycleScrollView::scrollViewDidScroll( CCScrollView* view )
{
	//CCLog("I am  scrolling");
	CCLog("The last point is %f",m_lastPoint.x);
	CCLog("The now point is %f",m_nowPoint.x);
	if (m_direction==CycleDirectionHorizontal)
	{	
		if (m_isTouchDirection)
		{
			if (m_nowPoint.x>m_lastPoint.x)
			{
				m_moving = Right;
				m_lastPoint = m_nowPoint;
			}
			else
			{
				m_moving= Left;
				m_lastPoint = m_nowPoint;
			}
		}
		m_nowPositionNum = m_pContainer->getPositionX() / m_backgroundNodeSize.width;
	}else if (m_direction==CycleDirectionVertical)
	{

		
		if (m_isTouchDirection)
		{
			if (m_nowPoint.y>m_lastPoint.y)
			{
				m_moving = Up;
				m_lastPoint = m_nowPoint;
			}
			else
			{
				m_moving= Down;
				m_lastPoint = m_nowPoint;
			}

		}
			
		m_nowPositionNum = m_pContainer->getPositionY() / m_backgroundNodeSize.height;
	}

	adjustBackgroundNode();
}

void CCCycleScrollView::scrollViewDidZoom( CCScrollView* view )
{

}

void CCCycleScrollView::scrollViewDidStop( CCScrollView* view )
{

}

void CCCycleScrollView::ccTouchCancelled( CCTouch *pTouch, CCEvent *pEvent )
{
	CCScrollView::ccTouchCancelled(pTouch,pEvent);
	m_isTouchDirection = false;
}




3.使用方法

首先需要實現一個CCcycleCell,需要注意的是CycleCell的大小必須是被CCcycleScrollView 展示界面大小整除。

class TempCell : public CCCycleCell
{
public:

	static TempCell* create()
	{
		TempCell* pRet = new TempCell();
		if (pRet && pRet->initwith())
		{
			pRet->autorelease();
		}
		else
		{
			CC_SAFE_DELETE(pRet);
		}
		return pRet;
	}
	bool initwith()
	{
		if (CCCycleCell::init())
		{
			label = CCLabelTTF::create();
			label->setFontSize(80);
			label->setAnchorPoint(ccp(0.5,0.5));
			label->setPosition(ccp(80,160));

			this->setContentSize(CCSizeMake(160,320));
			this->addChild(label);

			return true;
		}
		return false;

	}
	TempCell(){

	}
	~TempCell(){

	}

	void getSelected(){

	}
	void updateWithIndex(unsigned int index)
	{
		m_index = index;
		label->setString(CCString::createWithFormat("%d",index)->getCString());
	}
private:
	CCLabelTTF* label;
};

第二步,需要將三個一樣或者不一樣的,相同大小的背景Node傳給CCCycleScrollview。
第三步,需要將九個生成好的CCCycleCell傳給CCCycleScrollview,請注意,背景Node的size必須是cycleCell的倍數。
第四步,需要將每個背景node含有cell的個數與cell不同的種類數(根據不同index顯示不同數據的個數)傳給CCcycleScrollview。

CCSprite* sprite1 = CCSprite::create("HelloWorld.png");
	CCSprite* sprite2 = CCSprite::create("HelloWorld.png");
	CCSprite* sprite3 = CCSprite::create("HelloWorld.png");
	vector<CCNode*> tempV ;
	tempV.push_back(sprite1);
	tempV.push_back(sprite2);
	tempV.push_back(sprite3);

	vector<CCCycleCell*> tempC;
	for (int i=0;i<9;i++)
	{
		TempCell* tempCell = TempCell::create();
		tempC.push_back(tempCell);
	}
	CCCycleScrollView* cycleView = CCCycleScrollView::create(tempV,tempC,3,12,CycleDirectionHorizontal);

4.小結

CCcycleScrollview繼承了CCScrollView和CCSrollViewDelegate。
CCCycleScrollview使用了CCycleCell來解耦。
CCCycleScrollview的限制是背景Node的大小必須是cell的整數倍。

如果有任何問題,請聯繫我~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章