MJRefresh原理分析
8378 ワード
国内の多くの開発者はMJRefreshを選んでプルダウンのリフレッシュを実現して、最近私も彼のソースコードを読んで、ここで私が理解した実現の原理を分かち合います
ドロップダウン・リフレッシュの基本原理一般的なドロップダウン・リフレッシュは デフォルトでは、tableViewがドロップダウンされ、手を離すと元の位置に戻ります.ドロップダウンリフレッシュコントロールは、tableViewの上に自分を置き、初期yを負数に設定するので、普段は表示されず、ドロップダウンの時だけ現れ、放して弾き返します.そしてloadingでは一時的に を実現します.
キーコード解析実装インスタンスを作成するコードから これはtagetActionメソッドで、もう一つのblockメソッドがあり、メソッドには本物のheaderView初期化メソッド と書かれています.ここでは関連オブジェクトのテクニック(AssociatedObject)を使用します.categoryは通常、インスタンス変数を直接追加することはできません.上のコードで、UIscrollViewのsubviewsにheaderを追加し、参照を保持します.しかしこのヘッダのフレームはまだ確定しておらず、ヘッダの位置とリスニング行為 を設定する行為もない.次にライフサイクルメソッドに入りますlayoutSubviews: 上記のコードにより、コントロールの位置、および各subviewの位置が決定される. 次はリスニング 次にステータスを設定する方法で、stateステータスの変化に応じて異なる動作を駆動します.setStateメソッドは2番目の拡張ポイントで、ここでMJRefreshCheckStateはマクロであり、親クラスのsetStateメソッドも呼び出されます.ドロップダウン時にcontentInsetを一時的に大きくし、headerを画面に残してcallback blockを呼び出します.終了後にcontentInsetを復元し、 ここまで、MJRefreshの原理分析の差は多くないが、その原理を分析することで、ドロップダウンリフレッシュのプロセスは、ドロップダウンリフレッシュコントロールを初期化する-Frameを設定する-コントロールに傍受を追加する-contentOffsetを監視する-contentOffsetを判断し、対応するコールバックを行う ドロップダウン・リフレッシュ・コントロールを自分で書こうとする皆さんのお役に立てばと思います.
ドロップダウン・リフレッシュの基本原理
contentInset
で実現され、tableViewがナビゲーション・バーの真下にある場合、彼のcontentInset.top
は64であり、contentOffset.y
は-64である.引き続きドロップダウンcontentInset.top
は変わらず、contentOffset.y
は小さくなり、アップcontentOffset.y
は大きくなり、左上隅がスクリーンの左上隅に達するまで0になる.contentInset
を大きくし、tableViewを押し下げることに相当します.すると、ドロップダウン・リフレッシュのコントロールが表示され、リフレッシュが完了した後、contentInset
を元の値に戻し、リバウンドの効果キーコード解析実装
[self.tableView addHeaderWithTarget:self action:@selector(loadNewData)];
[self.tableView headerEndRefreshing];
- (void)addHeaderWithTarget:(id)target action:(SEL)action
{
[self addHeaderWithTarget:target action:action dateKey:nil];
}
- (void)addHeaderWithTarget:(id)target action:(SEL)action dateKey:(NSString*)dateKey
{
// 1. header
if (!self.header) {
MJRefreshHeaderView *header = [MJRefreshHeaderView header];
[self addSubview:header];
self.header = header;
}
// 2.
self.header.beginRefreshingTaget = target;
self.header.beginRefreshingAction = action;
// 3. key
self.header.dateKey = dateKey;
}
MJRefreshHeaderView *header = [MJRefreshHeaderView header];
,この方法は最初の拡張点であり、具体的なヘッダにどのような属性があり、どのユーザーが設定したスタイルがここで設定されているかは、まだsuperView業者に追加されておらず、tableViewに掛ける行為もなく、次の呼び出しself.header = header;
,ここで用いたテクニックは、UIscrollView+MJRefreshのcategoryを利用して、UIscrollViewに属性headerとfooterを追加し、set.getメソッドでは、- (void)setHeader:(MJRefreshHeaderView *)header {
[self willChangeValueForKey:@"MJRefreshHeaderViewKey"];
objc_setAssociatedObject(self, &MJRefreshHeaderViewKey,
header,
OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"MJRefreshHeaderViewKey"];
}
- (MJRefreshHeaderView *)header {
return objc_getAssociatedObject(self, &MJRefreshHeaderViewKey);
}
[self addSubview:header];
このコードを実行すると、次はヘッダのライフサイクルメソッド
willMoveToSuperview
に入ります.このメソッドは共通のベースクラスMJRefreshBaseView
で実現されます.これは基礎的な行為なので、共通のベースクラスに書くと、すべてのサブクラスが共有できます.- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
//
[self.superview removeObserver:self forKeyPath:MJRefreshContentOffset context:nil];
if (newSuperview) { //
[newSuperview addObserver:self forKeyPath:MJRefreshContentOffset options:NSKeyValueObservingOptionNew context:nil];
//
self.mj_width = newSuperview.mj_width;
//
self.mj_x = 0;
// UIScrollView
_scrollView = (UIScrollView *)newSuperview;
// UIScrollView contentInset
_scrollViewOriginalInset = _scrollView.contentInset;
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
// 1.
CGFloat arrowX = self.mj_width * 0.5 - 100;
self.arrowImage.center = CGPointMake(arrowX, self.mj_height * 0.5);
// 2.
self.activityView.center = self.arrowImage.center;
}
UIScrollView
のcontentOffset
とcontentSize
の変化であり、キーコードは以下の通りである:#pragma mark - UIScrollView contentOffset
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//
if (!self.userInteractionEnabled || self.alpha <= 0.01 || self.hidden) return;
// ,
if (self.state == MJRefreshStateRefreshing) return;
if ([MJRefreshContentOffset isEqualToString:keyPath]) {
[self adjustStateWithContentOffset];
}
}
/**
*
*/
- (void)adjustStateWithContentOffset
{
// contentOffset
CGFloat currentOffsetY = self.scrollView.mj_contentOffsetY;
// offsetY
CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
// ,
if (currentOffsetY >= happenOffsetY) return;
if (self.scrollView.isDragging) {
//
CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_height;
if (self.state == MJRefreshStateNormal && currentOffsetY < normal2pullingOffsetY) {
//
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && currentOffsetY >= normal2pullingOffsetY) {
//
self.state = MJRefreshStateNormal;
}
} else if (self.state == MJRefreshStatePulling) {// &&
//
self.state = MJRefreshStateRefreshing;
}
}
- (void)setState:(MJRefreshState)state
{
// 0. contentInset
if (self.state != MJRefreshStateRefreshing) {
_scrollViewOriginalInset = self.scrollView.contentInset;
}
// 1. ( )
if (self.state == state) return;
// 2.
MJRefreshState oldState = self.state;
// 3.
_state = state;
// 4.
switch (state) {
case MJRefreshStateNormal: //
{
if (oldState == MJRefreshStateRefreshing) {
[UIView animateWithDuration:MJRefreshSlowAnimationDuration * 0.6 animations:^{
self.activityView.alpha = 0.0;
} completion:^(BOOL finished) {
//
[self.activityView stopAnimating];
// alpha
self.activityView.alpha = 1.0;
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(MJRefreshSlowAnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //
// normal
// _state = MJRefreshStatePulling;
// self.state = MJRefreshStateNormal;
//
self.arrowImage.hidden = NO;
//
[self.activityView stopAnimating];
//
[self settingLabelText];
});
//
return;
} else {
//
self.arrowImage.hidden = NO;
//
[self.activityView stopAnimating];
}
break;
}
case MJRefreshStatePulling:
break;
case MJRefreshStateRefreshing:
{
//
[self.activityView startAnimating];
//
self.arrowImage.hidden = YES;
//
if ([self.beginRefreshingTaget respondsToSelector:self.beginRefreshingAction]) {
msgSend(msgTarget(self.beginRefreshingTaget), self.beginRefreshingAction, self);
}
if (self.beginRefreshingCallback) {
self.beginRefreshingCallback();
}
break;
}
default:
break;
}
// 5.
[self settingLabelText];
}