xml地图|网站地图|网站标签 [设为首页] [加入收藏]

自己写一个下拉刷新,MJRefresh原理分析

来源:http://www.ccidsi.com 作者:呼叫中心培训课程 人气:70 发布时间:2020-03-22
摘要:多年来沦落了花色中叁个日历月视图与周视图切换效果的贯彻,长日子从没完成想要的成效,衰颓卓殊。烦请有好主见的同室带领一二,在线等@留什么白。 在以往的iOS开采进程中,都

多年来沦落了花色中叁个日历月视图与周视图切换效果的贯彻,长日子从没完成想要的成效,衰颓卓殊。烦请有好主见的同室带领一二,在线等@留什么白。

在以往的iOS开采进程中,都会必要有下拉刷新的效应,那也改为了绝大比超级多 App 必备的交互作用方式。对于新手,往往须要上学应用第三方来成功那几个效果,以往比较流行的开源的下拉刷新控件如 MJRefresh 。其完成原理一共有两种,第一种是使用 UITabelView 的headerView ,另一种是应用 UIScrollView 的类拓宽 ,后一种办法是更被人选用的。

境内不菲开荒者接收MJRefresh来完毕下拉刷新,这段时间自家也在读他的源码,在此本身享受下作者领悟的贯彻的规律

得了例行的啰嗦,步入正题。

首先介绍一下自身那一个DEMO的下拉刷新的应用办法:

下拉刷新的基本原理

  • 貌似的下拉刷新都是用<code>contentInset</code>来贯彻的,若是三个tableView在导航栏的正下方,那么她的<code>contentInset.top</code>就是64,<code>contentOffset.y</code>正是-64。继续下拉<code>contentInset.top</code>不变,<code>contentOffset.y</code>变小,上拉<code>contentOffset.y</code> 变大,直到左上角达到显示屏的左上角变为0。
  • 私下认可处境下,下拉三个tableView,在放手之后,会弹回早先的岗位。而下拉刷新控件,正是将自身身处tableView的下面,最初y设置成负数,所以常常不会显得出来,唯有下拉的时候才会产出,松手又会弹回去。然后在loading的时候,有时把<code>contentInset</code>增大,约等于把tableView往下挤,于是下拉刷新的控件就能够显得出来,然后刷新实现今后,再把<code>contentInset</code>改回原本的值,达成回弹的职能

我们莫不都用过MJRefresh,十一分之有援助,李明杰先生依附runtime的风味,极尽之能,让不可胜数小白开荒者和据守不重复创建轮子原则的开垦者用起码的代码就落实了界面下拉刷新的机能。我们今日不谈runtime的黑法力,而是UITableView最广大的刷新数据方式下拉刷新的落实进程。

图片 1图片 2demo效果图

主要代码深入分析实现

  • 从创设实例的代码起初
    [self.tableView addHeaderWithTarget:self action:@selector(loadNewData)];
 [self.tableView headerEndRefreshing];
  • 那是tagetAction方法,还会有一种block方法,在议程中写着真正的headerView伊始化方法
- (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;
}
  • <code>MJRefreshHeaderView *header = [MJRefreshHeaderView header]; </code>, 这么些法子是第二个扩展点,具体的header有啥属性,哪些顾客设置的体裁都以在这里处安装的,不过它现在还尚未加到任何的superView商,也绝非其余表现将其挂到tableView上,
    接下去的调用<code>self.header = header;</code> ,这里运用的技艺是应用了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);
}
  • 此处运用了关联对象的手艺(AssociatedObject),因为category平日状态下是无法平素抬高实例变量的、通过上边的代码,把header增多到了UIScrollView的subviews里,并保留了多少个援用。然而那几个header的frame尚未规定,也从未其余表现设置header的地点和侦听行为

  • <code>[self addSubview:header];</code> 由于实行了这句代码,接下去就能跻身header的生命周期方法<code>
    willMoveToSuperview</code>,这些主意是在公共的基类<code>MJRefreshBaseView</code>里福寿年高的,因为那是底子的行事,所以写在国有的基类里,全体的子类都能分享:

- (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;
    }
}
  • 接下去会进去生命周期方法layoutSubviews:
- (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;
}
  • 经过上述的代码,鲜明了控件的岗位,以至个中每个subview的职责。
  • 接下去就是侦听<code>UIScrollView</code>的<code>contentOffset</code>和<code>contentSize</code>变化,关键代码如下:
#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;
    }
}
  • 接下去是安装情状的主意,依据state状态变化,驱动不一样的一举一动。setState方法是第3个扩大点,这里的MJRefreshCheckState是个宏,也调用了父类的setState的艺术。下拉的时候最近增大contentInset,令header保留在显示器上,然后调用callback block;甘休现在恢复生机contentInset,如下:
- (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];
}
  • 到此,已经将MJRefresh的规律解析的基本上了,通过解析它的法规,能够看出来下拉刷新的进程大致是:
    伊始化下拉刷新控件 - 设置Frame - 控件增添监听 - 监察和控制contentOffset - 决断contentOffset并做出相应回调
  • 期望能给各位想协调写下拉刷新控件的拉动点援助,有如何写的不法规、有标题标地方,希望各位能商量指正

率先,我们定义八个枚举值

只需求一句代码就能够兑现下拉加载的效果。上边就从头计划职业吧!

typedef NS_ENUM(NSUInteger, RefreshState) { RefreshStateNormal,//正常 RefreshStatePulling,//释放即可刷新 RefreshStateLoading,//加载中};

在友好写八个下拉刷新早前,需求明白多少个scrollView的质量:

各自代表寻常景况、释放就能够刷新状态、加载中状态

contentSize:视图的轮转范围

contentOffset:那性情格在这里间最首要用contentOffset.y来监听视图滚动事件

contentInset:那性格情首借使用来设置刷新视图的frame

为了轻易表达,大家只用贰个UILabel来呈现就能够,把它身处内容上方,也正是常规景况下看不到的地点,独有下拉的时候才干观望。

下拉刷新的作用达成重大正是监听视图滚动的contentOffset.y来更正下拉刷新的事态,并提示顾客接下去的操作。

图片 3UILabel的位置

废话非常少说,步向正题,从前和睦写五个下拉刷新:(上边是下拉刷新的多少个关键步骤)

下一场我们揣测叁个大约的离开,进而明确触发加载状态的临界角。

一。首先,创造一个UIScrollView的类举行,和headerView文件

本文由68399皇家赌场发布于呼叫中心培训课程,转载请注明出处:自己写一个下拉刷新,MJRefresh原理分析

关键词: 68399皇家赌场 iOS 回头看 UITableView 思路

最火资讯