美文网首页高质量技术专题
LMDropdownView 源码分析

LMDropdownView 源码分析

作者: kakukeme | 来源:发表于2017-04-06 16:23 被阅读31次

    LMDropdownView是一个简单的下拉视图,灵感来自于Tappy。实现了背景模糊+3D效果。使用了Core Animation的关键帧动画,可以很方便地更改菜单内容视图。

    并且在展示和收起菜单视图时还有很不错的弹动效果。

    模糊+3D效果下拉菜单视图--LMDropdownView

    github源码:https://github.com/lminhtm/LMDropdownView

    分析过程源码: https://github.com/kakukeme/iOS-Source-Code-Analyze

    /*!
     *  A simple dropdown view inspired by Tappy.
     *  LMDropdownView是一个简单的下拉视图,灵感来自于Tappy。
     */
    @interface LMDropdownView : NSObject
    
    /*!
     *  The closed scale of container view.
     *  Set it to 1 to disable container scale animation.
     *  设为1,关闭容器view缩放动画
     */
    @property (nonatomic, assign) CGFloat closedScale;
    
    /**
     A boolean indicates whether container view should be blurred. Default is YES
     容器view是否使用模糊效果,默认yes;
     */
    @property (nonatomic, assign) BOOL shouldBlurContainerView;
    
    /*!
     *  The blur radius of container view.
     *  容器view的模糊半径
     */
    @property (nonatomic, assign) CGFloat blurRadius;
    
    /*!
     *  The alpha of black mask button.
     *  背景遮罩透明度
     */
    @property (nonatomic, assign) CGFloat blackMaskAlpha;
    
    /*!
     *  The animation duration.
     *  动画持续时间
     */
    @property (nonatomic, assign) CGFloat animationDuration;
    
    /*!
     *  The animation bounce height of content view.
     *  内容view的拉伸高度;使用关键帧动画实现Spring弹簧动画效果
     */
    @property (nonatomic, assign) CGFloat animationBounceHeight;
    
    /*!
     *  The animation direction.
     *  动画方向,顶部向下,底部向上;
     */
    @property (nonatomic, assign) LMDropdownViewDirection direction;
    
    /*!
     *  The background color of content view.
     *  内容view 背景色
     */
    @property (nonatomic, strong) UIColor *contentBackgroundColor;
    
    /*!
     *  The current dropdown view state.
     *  当前dropdown view 的状态;
     */
    @property (nonatomic, assign, readonly) LMDropdownViewState currentState;
    
    /*!
     *  A boolean indicates whether dropdown is open.
     *  标志dropdown是否打开;
     */
    @property (nonatomic, assign, readonly) BOOL isOpen;
    
    /*!
     *  The dropdown view delegate.
     *  代理
     */
    @property (nonatomic, weak) id<LMDropdownViewDelegate> delegate;
    
    /**
     *  The callback when dropdown view did show in the container view.
     *  dropdown在容器view中已经显示后的回调
     */
    @property (nonatomic, copy) dispatch_block_t didShowHandler;
    
    /**
     *  The callback when dropdown view did hide in the container view.
     *  隐藏的回调
     */
    @property (nonatomic, copy) dispatch_block_t didHideHandler;
    
    /*!
     *  Convenience constructor for LMDropdownView.
     *  类方法,方便的构造方法
     */
    + (instancetype)dropdownView;
    
    /*!
     *  Show dropdown view.
     *  显示方法,指定容器view,内容view;
     *
     *  @param containerView The containerView to contain.
     *  @param contentView   The contentView to show.
     *  @param origin        The origin point in the container coordinator system.
     */
    - (void)showInView:(UIView *)containerView withContentView:(UIView *)contentView atOrigin:(CGPoint)origin;
    
    /*!
     *  Show dropdown view from navigation controller.
     *  从导航控制器navigation下显示;
     *
     *  @param navigationController The navigation controller to show from.
     *  @param contentView          The contentView to show.
     */
    - (void)showFromNavigationController:(UINavigationController *)navigationController withContentView:(UIView *)contentView;
    
    /*!
     *  Hide dropdown view.
     *  隐藏;
     */
    - (void)hide;
    
    /*!
     *  Force hide dropdown view.
     *  强制隐藏,没有hide里的动画了;
     */
    - (void)forceHide;
    
    @end
    
    

    1、init中初始化一些属性变量;同时添加了屏幕旋转的通知UIDeviceOrientationDidChangeNotification;
    屏幕旋转了,就强制hide,处理一些代理、回调;

    2、显示方法-[LMDropdownView showInView:withContentView:atOrigin:]中调用
    -[LMDropdownView setupContentView:inView:atOrigin:]设置 containerView 和 contentView;

    // 视图层次
    
    // 1、mainView是scrollView;
    [containerView addSubview:self.mainView];
    
    // 2、设置了截图背景containerImage
    [self.mainView addSubview:self.containerWrapperView];
    
    // 3、背景遮罩点击事件
    [self.mainView addSubview:self.backgroundButton];
    
    // 4、内容包裹view的frame;
    // content包裹view高度
    CGFloat contentWrapperViewHeight = CGRectGetHeight(contentView.frame) + self.animationBounceHeight;
    switch (self.direction) {
        case LMDropdownViewDirectionTop:
            contentView.frame = CGRectMake(0, self.animationBounceHeight, W(contentView), H(contentView));
            
            // 开始设置view,没出现位置开始,方便动画
            self.contentWrapperView.frame = CGRectMake(origin.x,
                                                       origin.y - contentWrapperViewHeight,
                                                       W(contentView),
                                                       contentWrapperViewHeight);
            break;
        case LMDropdownViewDirectionBottom:
            // 往下点,
            contentView.frame = CGRectMake(0, 0, W(contentView), H(contentView));
            self.contentWrapperView.frame = CGRectMake(origin.x,
                                                       origin.y + contentWrapperViewHeight,
                                                       W(contentView),
                                                       contentWrapperViewHeight);
            break;
        default:
            break;
    }
    [self.contentWrapperView addSubview:contentView];
    [self.mainView addSubview:self.contentWrapperView];
    
    // 5、内容包裹view,位置记录
    // 内容包裹view,动画开始位置中心
    originContentCenter = CGPointMake(midx(self.contentWrapperView), midy(self.contentWrapperView));
    
    // 内容包裹view,动画终点位置中心
    if (self.direction == LMDropdownViewDirectionTop) {
        desContentCenter = CGPointMake(midx(self.contentWrapperView), origin.y + contentWrapperViewHeight/2 - self.animationBounceHeight);
    }
    else {
        desContentCenter = CGPointMake(midx(self.contentWrapperView), origin.y + contentWrapperViewHeight/2);
    }
    
    

    3、关键帧动画;

    - (void)addContentAnimationForState:(LMDropdownViewState)state
    {
        CAKeyframeAnimation *contentBounceAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        contentBounceAnim.duration = self.animationDuration;
        contentBounceAnim.removedOnCompletion = NO;
        contentBounceAnim.fillMode = kCAFillModeForwards;
        contentBounceAnim.values = [self contentPositionValuesForState:state];
        contentBounceAnim.timingFunctions = [self contentTimingFunctionsForState:state];
        contentBounceAnim.keyTimes = [self contentKeyTimesForState:state];
        
        [self.contentWrapperView.layer addAnimation:contentBounceAnim forKey:nil];
        [self.contentWrapperView.layer setValue:[contentBounceAnim.values lastObject] forKeyPath:@"position"];
    }
    
    - (void)addContainerAnimationForState:(LMDropdownViewState)state
    {
        CAKeyframeAnimation *containerScaleAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
        containerScaleAnim.duration = self.animationDuration;
        containerScaleAnim.removedOnCompletion = NO;
        containerScaleAnim.fillMode = kCAFillModeForwards;
        containerScaleAnim.values = [self containerTransformValuesForState:state];
        containerScaleAnim.timingFunctions = [self containerTimingFunctionsForState:state];
        containerScaleAnim.keyTimes = [self containerKeyTimesForState:state];
        
        [self.containerWrapperView.layer addAnimation:containerScaleAnim forKey:nil];
        [self.containerWrapperView.layer setValue:[containerScaleAnim.values lastObject] forKeyPath:@"transform"];
    }
    
    // 开始动画 二维码扫描关键帧;
    CAKeyframeAnimation *animationMove = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
    animationMove.values = @[@(0.0),@(_scanFrameView.tf_height-_scannerView.tf_height),@(0.0)];
    animationMove.duration = 2.5f;
    animationMove.repeatCount = CGFLOAT_MAX;
    animationMove.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    [self.scannerView.layer addAnimation:animationMove forKey:nil];
    
    

    4、使用NSMutableArray,保存关键帧动画执行values

    /** content 内容view, 不同状态的,各种位置保存下 */
    - (NSArray *)contentPositionValuesForState:(LMDropdownViewState)state
    {
        CGPoint currentContentCenter = self.contentWrapperView.layer.position; // position 为中心点
        
        NSMutableArray *values = [NSMutableArray new];
        [values addObject:[NSValue valueWithCGPoint:currentContentCenter]];
        
        if (state == LMDropdownViewStateWillOpen || state == LMDropdownViewStateDidOpen)    // show
        {
            if (self.direction == LMDropdownViewDirectionTop) {
                // 向下拉伸点,类似弹簧
                [values addObject:[NSValue valueWithCGPoint:CGPointMake(currentContentCenter.x, desContentCenter.y + self.animationBounceHeight)]];
            }
            else {
                [values addObject:[NSValue valueWithCGPoint:CGPointMake(currentContentCenter.x, desContentCenter.y - self.animationBounceHeight)]];
            }
            [values addObject:[NSValue valueWithCGPoint:CGPointMake(currentContentCenter.x, desContentCenter.y)]];
        }
        else    // hide
        {
            if (self.direction == LMDropdownViewDirectionTop) {
                [values addObject:[NSValue valueWithCGPoint:CGPointMake(currentContentCenter.x, currentContentCenter.y + self.animationBounceHeight)]]; // 关闭时,往下伸一下,作出弹簧效果;
            }
            else {
                [values addObject:[NSValue valueWithCGPoint:CGPointMake(currentContentCenter.x, currentContentCenter.y - self.animationBounceHeight)]];
            }
            // 最终隐藏点;
            [values addObject:[NSValue valueWithCGPoint:CGPointMake(currentContentCenter.x, originContentCenter.y)]];
        }
        
        return values;
    }
    
    
    - (NSArray *)containerTransformValuesForState:(LMDropdownViewState)state
    {
        CATransform3D transform = self.containerWrapperView.layer.transform;
        
        NSMutableArray *values = [NSMutableArray new];
        [values addObject:[NSValue valueWithCATransform3D:transform]];
        
        if (state == LMDropdownViewStateWillOpen || state == LMDropdownViewStateDidOpen)
        {
            CGFloat scale = self.closedScale - kDefaultAnimationBounceScale;
            [values addObject:[NSValue valueWithCATransform3D:CATransform3DScale(transform, scale, scale, scale)]];
            [values addObject:[NSValue valueWithCATransform3D:CATransform3DScale(transform, self.closedScale, self.closedScale, self.closedScale)]];
        }
        else
        {
            CGFloat scale = 1 - kDefaultAnimationBounceScale;
            [values addObject:[NSValue valueWithCATransform3D:CATransform3DScale(transform, scale, scale, scale)]];
            [values addObject:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
        }
        
        return values;      // CATransform3DScale 3d缩放动画吗
    }
    

    5、支持retina截屏,模糊处理

    #pragma mark - CREATE IMAGE
    
    /** 截图,支持retina */
    + (UIImage *)imageFromView:(UIView *)theView withSize:(CGSize)size
    {
        UIGraphicsBeginImageContext(size);
        CGContextRef context = UIGraphicsGetCurrentContext();
        
        // -renderInContext: renders in the coordinate space of the layer,
        // so we must first apply the layer's geometry to the graphics context
        CGContextSaveGState(context);
        // Center the context around the window's anchor point
        CGContextTranslateCTM(context, size.width/2, size.height/2);
        // Apply the window's transform about the anchor point
        CGContextConcatCTM(context, [theView transform]);
        // Offset by the portion of the bounds left of and above the anchor point
        CGContextTranslateCTM(context,
                              -[theView bounds].size.width * [[theView layer] anchorPoint].x,
                              -[theView bounds].size.height * [[theView layer] anchorPoint].y);
        
        //  [theView.layer renderInContext:context];
        [theView drawViewHierarchyInRect:[theView bounds] afterScreenUpdates:NO];
        
        // Restore the context
        CGContextRestoreGState(context);
        
        UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        return theImage;
    }
    
    
    #pragma mark - CUSTOMIZE IMAGE
    
    /** 模糊效果 */
    - (UIImage *)blurredImageWithRadius:(CGFloat)radius
                             iterations:(NSUInteger)iterations
                              tintColor:(UIColor *)tintColor
    {
        //image must be nonzero size
        if (floorf(self.size.width) * floorf(self.size.height) <= 0.0f) return self;
        
        //boxsize must be an odd integer
        uint32_t boxSize = (uint32_t)(radius * self.scale);
        if (boxSize % 2 == 0) boxSize ++;
        
        //create image buffers
        CGImageRef imageRef = self.CGImage;
        vImage_Buffer buffer1, buffer2;
        buffer1.width = buffer2.width = CGImageGetWidth(imageRef);
        buffer1.height = buffer2.height = CGImageGetHeight(imageRef);
        buffer1.rowBytes = buffer2.rowBytes = CGImageGetBytesPerRow(imageRef);
        size_t bytes = buffer1.rowBytes * buffer1.height;
        buffer1.data = malloc(bytes);
        buffer2.data = malloc(bytes);
        
        //create temp buffer
        void *tempBuffer = malloc((size_t)vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, NULL, 0, 0, boxSize, boxSize,
                                                                     NULL, kvImageEdgeExtend + kvImageGetTempBufferSize));
        
        //copy image data
        CFDataRef dataSource = CGDataProviderCopyData(CGImageGetDataProvider(imageRef));
        memcpy(buffer1.data, CFDataGetBytePtr(dataSource), bytes);
        CFRelease(dataSource);
        
        for (NSUInteger i = 0; i < iterations; i++)
        {
            //perform blur
            vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, tempBuffer, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
            
            //swap buffers
            void *temp = buffer1.data;
            buffer1.data = buffer2.data;
            buffer2.data = temp;
        }
        
        //free buffers
        free(buffer2.data);
        free(tempBuffer);
        
        //create image context from buffer
        CGContextRef ctx = CGBitmapContextCreate(buffer1.data, buffer1.width, buffer1.height,
                                                 8, buffer1.rowBytes, CGImageGetColorSpace(imageRef),
                                                 CGImageGetBitmapInfo(imageRef));
        
        //apply tint
        if (tintColor && CGColorGetAlpha(tintColor.CGColor) > 0.0f)
        {
            CGContextSetFillColorWithColor(ctx, [tintColor colorWithAlphaComponent:0.25].CGColor);
            CGContextSetBlendMode(ctx, kCGBlendModePlusLighter);
            CGContextFillRect(ctx, CGRectMake(0, 0, buffer1.width, buffer1.height));
        }
        
        //create image from context
        imageRef = CGBitmapContextCreateImage(ctx);
        UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
        CGImageRelease(imageRef);
        CGContextRelease(ctx);
        free(buffer1.data);
        return image;
    }
    
    

    相关文章

      网友评论

      • Mr卿:正好 有个需求是这样 本来打算自己造轮子的 有轮子了!!!!QQQ

      本文标题:LMDropdownView 源码分析

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