標籤:位置 控制 scrolling 視圖 page ring otto selector bsp
閱讀過程中文章內容可能需要很多屏才能展示完,那麼現在的問題是建立多少個視圖來渲染內容。
經過考慮,決定用兩個視圖,利用手勢來控制不斷來回切換來實現。
首先定義兩個私人的實體的view對象:
@property (nonatomic, strong) DisplyManagerView *buffer1;@property (nonatomic, strong) DisplyManagerView *buffer2;
buffer1與buffer2就是用於渲染內容的兩個視圖。
其次還要定義一個用於區分是當前顯示的視圖還是被當前視圖覆蓋的視圖(用於下一頁或上一頁的渲染):
@property (nonatomic, strong) DisplyManagerView *curDisplayView;@property (nonatomic, strong) DisplyManagerView *nextDisplayView;
curDisplayView與nextDisplayView只是buffer1與buffer2的兩個引用,並不是實際添加到上層視圖上的兩個實體的視圖。它們只是把當前視圖與下次要用以顯示的視圖作區分。可能這樣看著有點繞,下面看代碼就容易理解了。
實現階段父視圖初始化時要建立兩個實體視圖並給兩個索引檢視表賦值並添加相應手勢:
- (id)initWithFrame:(CGRect)frame{ if (self = [super initWithFrame:frame]) { [self createSubViews]; [self addPanGesture]; } return self;}
如果初始化時要走其他的初始化方法,就添加在其他的初始化方法中。
- (void)createSubViews{ _buffer2 = [[DisplyManagerView alloc] initWithFrame:self.bounds]; [_buffer2 createBottomView]; TypographicManager *aManager = [TypographicManager getInstanse]; [_buffer2 setBgColor:aManager.backgroundColor]; [self addSubview:_buffer2]; _nextDisplayView = _buffer2; _buffer1 = [[DisplyManagerView alloc] initWithFrame:self.bounds]; [_buffer1 createBottomView]; [_buffer1 setBgColor:aManager.backgroundColor]; [self addSubview:_buffer1]; _curDisplayView = _buffer1;}
- (void)addPanGesture{ self.userInteractionEnabled = YES; UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; pan.delegate = self; [self addGestureRecognizer:pan]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; tap.delegate = self; [self addGestureRecognizer:tap];}
這裡我們只解釋滑動手勢的相應方法:- (void)pan:(UIPanGestureRecognizer *)panGes{
CGPoint pointer = [panGes locationInView:self]; if (panGes.state == UIGestureRecognizerStateBegan) //滑動開始 { _startX = pointer.x; _scrollingX = pointer.x; _displyViewX = _curDisplayView.frame.origin.x; } else if (panGes.state == UIGestureRecognizerStateChanged) //滑動過程中(滑動過程中一直處於此狀態,即滑動過程中每次調用該方法都會進入該條件) { CGPoint pointer = [panGes locationInView:self]; if (pointer.x > _scrollingX) { _instantDirection = ScrollingInstantDirectionRight; } else if(pointer.x == _scrollingX) { _instantDirection = ScrollingInstantDirectionNone; } else { _instantDirection = ScrollingInstantDirectionLeft; } _scrollingX = pointer.x; //記錄瞬時觸碰點的座標 if (self.delegate != nil && [self.delegate respondsToSelector:@selector(typographicViewBeginScroll)]) { [self.delegate typographicViewBeginScroll]; }
//以下涉及到翻頁方式,利用了裝飾模式 TypographicViewFlipOverManager *flipOverManager = TypographicViewFlipOverManager.instance; flipOverManager.decorateView.typographicView = self; [flipOverManager displyViewMoveToX:pointer.x - _startX]; } else if (panGes.state == UIGestureRecognizerStateEnded) //滑動結束 { if (pointer.x - _startX > 0) { if (_instantDirection == ScrollingInstantDirectionLeft) { [self resetView:pointer.x - _startX isRight:NO]; } else { [self resetView:pointer.x - _startX isRight:YES]; } } else { if (_instantDirection == ScrollingInstantDirectionRight) { [self resetView:pointer.x - _startX isRight:YES]; } else { [self resetView:pointer.x - _startX isRight:NO]; } } }}
該方法主要分三個階段:滑動開始、滑動過程中、滑動結束。
滑動開始階段主要記錄開始觸碰點的座標。
滑動過程中階段主要記錄滑動過程中瞬時的觸碰點的座標,並根據瞬時座標來判斷滑動方向,並根據瞬時觸碰點的座標來即時行動裝置檢視。這個過程中會涉及到不同的翻頁方式,兩個視圖會有不同的移動方式,這裡使用了裝飾模式,相比於使用繼承來實現此功能,利用裝飾模式這種組合的方式會更靈活。
滑動結束階段和主要確定視圖的最終位置:
- (void)resetView:(CGFloat)moveX isRight:(BOOL)isRight{ CGFloat width = self.frame.size.width; TypographicViewFlipOverManager *flipOverManager = TypographicViewFlipOverManager.instance; flipOverManager.decorateView.typographicView = self; if (moveX <= -width / 100) { if (isRight && moveX > -width / 3) { [flipOverManager recoverPage:moveX]; } else { [flipOverManager moveToNextPage]; } } else if (moveX >= width / 100) { if (!isRight && moveX < width / 3) { [flipOverManager recoverPage:moveX]; } else { [flipOverManager moveToPrePage]; } } else { [flipOverManager recoverPage:moveX]; }}
flipOverManager對象中的方法:
func displyViewMoveToX(_ x: CGFloat) { _ = self.decorateView?.displyViewMoveTo(x: x) } func moveToNextPage() { self.decorateView?.moveToNextPage() } func moveToPrePage() { self.decorateView?.moveToPrePage() } func recoverPage(_ moveX: CGFloat) { self.decorateView?.recoverPage(moveX) }
decorateView對象中的方法:
class TypographicDecorateView: TypographicExtensionView { var typographicView: TypographicView? override func displyViewMoveTo(x: CGFloat) ->Bool { let x = x return (self.typographicView?.displyViewMoveTo(x: x))! } override func moveToNextPage() { self.typographicView?.moveToNextPage() } override func moveToPrePage() { self.typographicView?.moveToPrePage() } override func recoverPage(_ moveX: CGFloat) { }}
typographicView即為前面說的buffer1和buffer2的父視圖,而typographicView中的相應代碼為:
- (void)recoverPage:(CGFloat)moveX{}- (void)moveToNextPage{ [self switchBuffer]; [_readManager changeViewIndexToNext]; //處理資料的變化}- (void)moveToPrePage{ [self switchBuffer]; [_readManager changeViewIndexToPre]; //處理資料的變化
}
- (void)switchBuffer //這個方法由於滑動後切換當前和下一個要顯示的視圖
{
if (_isFirstViewDisplay)
{
_curDisplayView = _buffer2;
_nextDisplayView = _buffer1;
_isFirstViewDisplay = NO;
}
else
{
_curDisplayView = _buffer1;
_nextDisplayView = _buffer2;
_isFirstViewDisplay = YES;
}
[self bringSubviewToFront:_curDisplayView];
}
- (BOOL)displyViewMoveToX:(CGFloat)x
{
//一些業務資料上的判斷,來返回YES/NO,不需要關心
}
這一系列流程就體現了裝飾模式的思路,沒有解釋可能有點暈。
先寫到這,這篇未完待續。。。
iOS閱讀器實踐系列(四)閱讀檢視方案