美文网首页
Swipe-to-Select照片滑动选择实现

Swipe-to-Select照片滑动选择实现

作者: X先生_未知数的X | 来源:发表于2021-04-19 10:25 被阅读0次

    https://www.jianshu.com/p/c73597d91fab
    我们的产品突然提出一个需求,希望让用户更快地选择照片,通过滑动的方式而不是一张一张点击选择,并且给了我们一个参考对象,iPhone手机相册。

    一开始准备从UITouch和响应链入手,然后根据坐标各种计算。实际操作后发现工程量太大,不好实现。后来打算用UISwipeGesture,但是上下左右控制十分麻烦,不得不放弃。

    最后github上找到一个类似的demo:Swipe to Select GridView,总算为该事件的实现打开思路。不过,原demo和我们的项目实际需求相差甚远,于是自己动手实现了该效果。我们先来看看效果:

    image

    demo


    demo地址:https://pan.baidu.com/s/1nvBcN8l

    核心思想


    UIPanGestureRecognizer

    其实一开始看原项目中是用UIPanGestureRecognizer手势来实现滑动定位的时候还是很吃惊的,一直以为UIPanGestureRecognizer是用来做缩放之类的手势,没想到滑动手势也能胜任。更神奇的是,如果添加到view上,而view存在UICollection,纵向滑动优先触发scrollView的上下滑动,横向滑动就触发PanGesture事件后又能纵向滑动了,不需要自己写代码控制,简直和iPhone相册一模一样。(后来根据响应链的思路想想也应该是这样。。collectionView在View的前面嘛。。)

    gestureRecognizer 只要设置了最大和最小触点都是1就能识别单点滑动事件。只要响应了该手势,就能拿到UIPanGestureRecognizer对象,通过 [gestureRecognizer locationInView:collectionView]方法就能获得当前触点在collectionView中的位置,然后进一步比较,判断选择不选择。

    • UIGestureRecognizerStateBegan
    • UIGestureRecognizerStateEnded

    UIPanGestureRecognizer有一个state属性,当手指触发事件的时候,state == UIGestureRecognizerStateBegan,这时就能进行一些手势开始的操作,比如标记进入滑动状态等。当手指离开屏幕的时候,state == UIGestureRecognizerStateEnded,这时进行手势结束操作等。其他时刻可以根据点的位置进行判断cell选中不选中。

    选中 & 不选中

    仔细分析iPhone相册cell选中不选中的实现可以发现规律:

    • 找到第一个进入区域的cell和最后一个进入区域的cell,然后将2个cell位置之间的cell改变状态

    如图,我只选中红色区域,蓝色区域也跟着选中。

    image
    • 改变的值是第一个cell变化的值

    如果第一个cell变成选中,那么后面变化的cell全都是选中。如果第一个cell变成不选中,那么后面变化的cell全都不选中。

    • cell先进入选中区域,然后离开选中区域,那么选中与否与cell进入选中区域之前保持一致

    这一点就比较复杂了,也就是说手指滑动进入状态后,需要产生一个临时值来保存当前选中状态(tmpIsSelected)而不是最终选中状态(isSelected)。这时候就需要结合UIGestureRecognizerStateBeganUIGestureRecognizerStateEnded进行判断。

    大致思路如下(参考demo):

    • 1、进入滑动状态,将所有model的isSelected的值赋值给tmpIsSelected,界面打钩不打钩的依据完全按照tmpIsSelected属性的值来显示。

    • 2、在滑动状态中(手指滑动),cell状态改变都修改model的tmpIsSelected值。

    • 3、结束滑动状态(手指离开),将model的tmpIsSelected的值赋值给isSelected,是否打钩都依据isSelected值显示。

    区域判断

    image

    知道cell选中与不选中的规则之后,我们的任务就是找到首尾两个cell的位置,然后将之间的cell的状态改变就好。首先就是要找到第一个cell。

    1、查找第一个cell

    第一个cell的判断比较简单,就是看触点(x,y)坐标是否落入cell的区域内。这里需要遍历collectionView.visibleCells,因为手势滑到的地方肯定在可视范围内,因此要找的cell肯定也在visibleCells里面。只要遍历一遍,找到点的区域在cell的frame里面的cell即可。记录下cell要改变的状态firstSelectedCellChoose、cell的坐标firstChooseCellRect还有cell的位置firstChooseCellIndexPath

    2、查找第二个cell

    第二个cell就要根据第一个cell的位置划分成5个区域:上侧、下侧、同行左侧、同行右侧、cell中,如上图所示。前4个区域都要根据坐标判断,然后遍历collectionView.visibleCells,找到最后一个满足区域的cell就是第二个cell,如果不满足,就把model的tmpIsSelected值改回isSelected的值,达到划出区域选择恢复的效果。(这里需要注意collectionView.visibleCells并不是按上到下左到右返回的,因此还需要排个序)而在cell中这个区域不可能存在第二个cell(与第一个cell重复),因此只要把collectionView.visibleCells中所有的cell的选中状态恢复即可。具体算法可以参照demo。

    自动滚动的实现

    image

    在滑动触发事件中,我们还得认为的添加2个区域以实现自动上滑和自动下滑功能。

    想要做到控制同步自动上滑和自动下滑功能,我们可以设置一个参数,scrollSpeed,当scrollSpeed > 0 代表下滑,当scrollSpeed < 0代表下滑,scrollSpeed == 0代表不滑动。这样,滑动的动画就可以用公式表示出来。

    • collectionView.ContentOffset.y = collectionView.ContentOffset.y + scrollSpeed;

    这样做的好处是可以用一个变量就控制上下滑动,还能适当改变scrollSpeed的值加快或者减慢滑动速度。

    • 注意 滑动的时候不会触发滑动手势方法,必须自己调用处理方法。

    相关代码:

    - (void)startScroll{
        if (!startScroll ) {
            return;
        }
        if (scrollOperationQueue.operationCount > 1) {
            return ;
        }
        __weak typeof(self) wSelf = self;
        NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            if (wSelf.mainCollectionView.contentOffset.y + wSelf.mainCollectionView.frame.size.height + scrollSpeed >= wSelf.mainCollectionView.contentSize.height && scrollSpeed > 0) {
    
                [UIView animateWithDuration:0.1 animations:^{
                wSelf.mainCollectionView.contentOffset =  CGPointMake(wSelf.mainCollectionView.contentOffset.x, wSelf.mainCollectionView.contentSize.height -wSelf.mainCollectionView.frame.size.height);
                }];
    
                [wSelf stopScroll];
                   return;
              }
            if (wSelf.mainCollectionView.contentOffset.y + scrollSpeed  <= 0 && scrollSpeed < 0) {
    
            [UIView animateWithDuration:0.1 animations:^{
              wSelf.mainCollectionView.contentOffset =  CGPointMake(wSelf.mainCollectionView.contentOffset.x, 0);
            }];
                [wSelf stopScroll];
                return;
    
            }
    
            [UIView animateWithDuration:0.1 animations:^{
            wSelf.mainCollectionView.contentOffset =  CGPointMake(wSelf.mainCollectionView.contentOffset.x, wSelf.mainCollectionView.contentOffset.y +scrollSpeed);
        }];
    
            [wSelf dealWithPointX:scrollPoint.x pointY:scrollPoint.y];
            scrollPoint = CGPointMake(scrollPoint.x, scrollPoint.y +scrollSpeed);
            [wSelf performSelector:@selector(startScroll) withObject:nil afterDelay:0.1];
    }];
        [scrollOperationQueue addOperation:operation];   
    }
    
    

    ps:关于滑动这块我后来又改进了下,使用UIView的动画更加流畅

    总结


    说了那么多,其实有很多东西只有自己去尝试后才知道是什么意思,用文字很难表达出来。

    由于这个demo也是我第一次尝试,如果有什么更好方式或者效率更高的改进,欢迎在评论区提出来~

    我是翻滚的牛宝宝,欢迎大家评论交流~

    作者:o翻滚的牛宝宝o
    链接:https://www.jianshu.com/p/c73597d91fab
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:Swipe-to-Select照片滑动选择实现

          本文链接:https://www.haomeiwen.com/subject/pnqilltx.html