登录 立即注册
金钱:

Code4App-iOS开发-iOS 开源代码库-iOS代码实例搜索-iOS特效示例-iOS代码例子下载-Code4App.com

BMMusicPlaye,使用ASDK,爱奇艺tabbar动画,网易云音乐播放器动效

[复制链接]
来自: birdmichael126 分类: iOS精品源码 上传时间: 2018-11-27 10:39:04
Tag:

项目介绍:

logo

Github地址 :https://github.com/birdmichael/BMMusicPlayer

BMMusicPlayer

基于FreeStreamer播放器二次封装。仿网易云封面图旋转,转圈音乐动效。全局基于ASDK

安装

为了包的体积,手机运行会报错找不到pod,在BMMusicPlayer文件夹内运行pod install即可。

使用pods目录

  • pod 'Masonry' -> 部分页面布局使用

  • pod 'MJExtension' -> 音频feed页面json转模型

  • pod 'AFNetworking' -> 请求网络资源

  • pod 'BMPrivatePods' -> 私有库,主要动些宏定义(项目快速移植版本,懒的特调)

  • pod 'Texture'-> 部分界面使用到了ASDK。(项目原本使用的ASDK,不影响阅读,换成VIew也可以)

  • pod 'GPUImage' -> GPU模糊

  • pod 'lottie-ios' -> 部分动画

  • pod 'FreeStreamer' -> 播放器

  • pod 'iOSPalette' -> 提取图片主颜色

截图演示(声音播放部分无法演示)

项目特点

  • 仿'爱奇艺'Tabbar点击动画切换效果

  • list页面cell采用ASDK实现layer化

  • 播放器单利实现,除"关闭","进度条滑块"采用ASDK实现layer化。

  • 播放器背景使用封面图背景高斯模糊效果,获取图片主色调作为shandow。音效动画同样获取图片主色调

主代码说明

1.Tabbar部分

BMTabBarController.m

- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item{
    NSArray * sss = self.tabBar.subviews;
    NSMutableArray *tabbatButtonArray = [@[] mutableCopy];
    for (UIView *view in sss) {
        if ([view isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
            [tabbatButtonArray addObject:view];
        }
    }
    
    for (UIView *view in [tabbatButtonArray[item.tag] subviews]) {
        if ([view isKindOfClass:NSClassFromString(@"UITabBarSwappableImageView")]) {
            [self.animation removeFromSuperview];
            NSString * name = item.title;
            LOTAnimationView *animation = [LOTAnimationView animationNamed:name];
            [view addSubview:animation];
            animation.bounds = CGRectMake(0, 0,view.bounds.size.width,view.bounds.size.width);
            animation.center = CGPointMake(view.bounds.size.width/2, view.bounds.size.height/2);
            [animation playWithCompletion:^(BOOL animationFinished) {
                // Do Something
            }];
            self.animation = animation;
        }
    }
}

使用同一个animation来播放动画,通过移除添加控制生命周期。

2.播放器页面高斯模糊,shandow,音效动画

BMMusicDisplayNode.m

  • 高斯模糊

因为使用asdk异步线程,告诉模糊放在了图片的归解档中。正常在主线程拿到下载后的图片中操作就好了。

_imageBackGroudNode.imageModificationBlock = ^UIImage * _Nullable(UIImage * _Nonnull image) {
        // GPUimage
        GPUImageGaussianBlurFilter *filter = [[GPUImageGaussianBlurFilter alloc] init];
        filter.blurRadiusInPixels = 40.0;
        [filter forceProcessingAtSize:image.size];
        GPUImagePicture *pic = [[GPUImagePicture alloc] initWithImage:image];
        [pic addTarget:filter];
        [pic processImage];
        [filter useNextFrameForImageCapture];
        return [filter imageFromCurrentFramebuffer];
    };
  • 获取主色作为shandow

1。因为是圆角添加shandow,涉及masksToBounds所以是在coverPictureNode背后有一个同样大小的coverPictureShadowNode上添加的shandow。

2。使用#import "iOSPalette.h"- (**void**)getPaletteImageColor:(GetColorBlock)block;中获取主颜色,并设置给coverPictureShadowNode。同时这个时候把颜色给音效动画。

_coverPictureNode.imageModificationBlock = ^UIImage * _Nullable(UIImage * _Nonnull image) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [image getPaletteImageColor:^(PaletteColorModel *recommendColor, NSDictionary *allModeColorDic, NSError *error) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    weakSelf.coverPictureShadowNode.layer.shadowColor = [UIColor bm_colorWithHexString:recommendColor.imageColorString].CGColor;
                    weakSelf.coverPictureShadowNode.layer.shadowOffset = CGSizeMake(0,10);
                    weakSelf.coverPictureShadowNode.shadowRadius = 29;
                    weakSelf.coverPictureShadowNode.shadowOpacity = 0.5;
                    
                    for (CALayer *layer in weakSelf.rippleArray) {
                        layer.borderColor = [UIColor bm_colorWithHexString:recommendColor.imageColorString].CGColor;
                    }
                    for (CALayer *layer in weakSelf.rippleCircleArray) {
                        layer.backgroundColor = [UIColor bm_colorWithHexString:recommendColor.imageColorString].CGColor;
                    }
                });
            }];
        });
        CGRect circleRect = (CGRect) {CGPointZero, CGSizeMake(image.size.width, image.size.height)};
        UIGraphicsBeginImageContextWithOptions(circleRect.size, NO, 0);
        UIBezierPath *circle = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:circleRect.size.width/2];
        [circle addClip];
        [[UIColor whiteColor] set];
        [circle fill];
        [image drawInRect:circleRect];
        UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return roundedImage;
    };
  • 音效动画

音效动画分为2个小模块:1.旋转。2.波纹

音效部分:

- (void)addAnimation {
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    animation.duration = 15;
    animation.repeatCount = MAXFLOAT;
    animation.removedOnCompletion = NO;
    animation.toValue = @(M_PI*2);
    [self.coverPictureNode.layer addAnimation:animation forKey:@"rotationAnimation"];
    
}

波纹部分:

​ 在波纹地方有其实就是有kCoverPictureRippleCount条圈通过不同的beginTime实现。在asdk中有一个小问题是当使用动画组的时候,仅显示第一个动画,还没找到原因。当使用正常View时,通过设置动画组,并删除动画相同属性就好了,这样代码也精简很多。

- (void)addRippleAnimation {
    self.rippleArray = [@[] mutableCopy];
    self.rippleCircleArray = [@[] mutableCopy];
    CALayer * animationLayer = [CALayer layer];
    CGFloat maxRadius = kBMSCREEN_WIDTH /2;
    for (int i = 0; i<kCoverPictureRippleCount; i++) {
        CALayer * pulsingLayer = [CALayer layer];
        pulsingLayer.frame = CGRectMake(0, 0, maxRadius*2, maxRadius*2);
        pulsingLayer.position = CGPointMake(BM_FitW(kCoverPictureHW)/2, BM_FitW(kCoverPictureHW)/2);
        pulsingLayer.backgroundColor = [UIColor clearColor].CGColor;
        pulsingLayer.cornerRadius = maxRadius;
        pulsingLayer.borderWidth = kCoverPictureRippleMaxBorderWidth;
        
        CALayer *lay = [CALayer layer];
        lay.frame = CGRectMake(0, 0, kCoverPictureRippleCircleSize, kCoverPictureRippleCircleSize);
        lay.cornerRadius = kCoverPictureRippleCircleSize/2;
        lay.masksToBounds = YES;
        lay.position = CGPointMake(maxRadius*2 * sin(45), maxRadius*2 * sin(45));
        [pulsingLayer addSublayer:lay];
        
        CAMediaTimingFunction * defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
        
        CAAnimationGroup * animationGroup = [CAAnimationGroup animation];
        animationGroup.fillMode = kCAFillModeBackwards;
        animationGroup.beginTime = CACurrentMediaTime() + i * kCoverPictureRippleDuration / kCoverPictureRippleCount;
        animationGroup.duration = kCoverPictureRippleDuration;
        animationGroup.repeatCount = HUGE;
        animationGroup.timingFunction = defaultCurve;
        
        CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        scaleAnimation.fromValue = @(BM_FitW(kCoverPictureHW)/2 / maxRadius);
        scaleAnimation.toValue = @1.0;
        scaleAnimation.beginTime = CACurrentMediaTime() + i * kCoverPictureRippleDuration / kCoverPictureRippleCount;
        scaleAnimation.fillMode = kCAFillModeBackwards;
        scaleAnimation.timingFunction = defaultCurve;
        scaleAnimation.duration = kCoverPictureRippleDuration;
        scaleAnimation.repeatCount = HUGE;
        scaleAnimation.removedOnCompletion = NO;
        scaleAnimation.fillMode = kCAFillModeForwards;
        
        
        CABasicAnimation *animation = [CABasicAnimation new];
        animation.keyPath = @"transform.rotation.z";
        animation.beginTime = CACurrentMediaTime() + i * kCoverPictureRippleDuration / kCoverPictureRippleCount;
        animation.fromValue = [NSNumber numberWithFloat:i *(M_PI/2)]; // 起始角度
        animation.toValue = [NSNumber numberWithFloat:i *(M_PI/2) + 2*M_PI]; // 终止角度
        animation.duration = 20;
        animation.repeatCount = HUGE;
        animation.timingFunction = defaultCurve;
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeForwards;
        
        CAKeyframeAnimation * opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
        opacityAnimation.beginTime = CACurrentMediaTime() + i * kCoverPictureRippleDuration / kCoverPictureRippleCount;
        opacityAnimation.values = @[@0.3, @0.5, @0];
        opacityAnimation.keyTimes = @[@0, @0.3, @1];
        opacityAnimation.duration = kCoverPictureRippleDuration;
        opacityAnimation.repeatCount = HUGE;
        opacityAnimation.timingFunction = defaultCurve;
        opacityAnimation.removedOnCompletion = NO;
        opacityAnimation.fillMode = kCAFillModeForwards;
        
        // 有一个位置问题,ASDK使用animationGroup 仅显示一个。
//        animationGroup.animations = @[scaleAnimation, opacityAnimation,animation];
        [pulsingLayer addAnimation:scaleAnimation forKey:@"plulsing"];
        [pulsingLayer addAnimation:animation forKey:@"dsdasdasd"];
        [pulsingLayer addAnimation:opacityAnimation forKey:@"plulsidsadang"];
        [animationLayer addSublayer:pulsingLayer];
        [self.rippleArray addObject:pulsingLayer];
        [self.rippleCircleArray addObject:lay];
    }
    _animationLayer = animationLayer;
    [self.coverPictureShadowNode.layer addSublayer:animationLayer];
}

停止动画:

​ 单纯移除动画会导致旋转归零,产生视觉不适。

- (void)updateCoverPictureRotating {
    if (!_palybtnNode.selected) {
        // 停止动画
        CFTimeInterval pausedTime = [self.coverPictureNode.layer convertTime:CACurrentMediaTime() fromLayer:nil];
        self.coverPictureNode.layer.speed = 0.0;
        self.coverPictureNode.layer.timeOffset = pausedTime;
        _animationLayer.hidden = YES;
    }else {
        CFTimeInterval pausedTime = [self.coverPictureNode.layer timeOffset];
        self.coverPictureNode.layer.speed = 1.0;
        self.coverPictureNode.layer.timeOffset = 0.0;
        self.coverPictureNode.layer.beginTime = 0.0;
        CFTimeInterval timeSincePause = [self.coverPictureNode.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
        self.coverPictureNode.layer.beginTime = timeSincePause;
        _animationLayer.hidden = NO;
    }
}

联系

邮箱:birdmichael126@gmail.com

微信:birdmichael

License

The Texture project is available for free use, as described by the LICENSE (Apache 2.0).

相关源码推荐:

我来说两句
*滑动验证:
所有评论(43)
这是个逗比 2018-11-28 10:12:08
感谢大神分享,必须顶!
回复
码农创新者 2018-11-28 10:13:27
我是来学习的
回复
乱世佳人520 2018-11-28 10:14:12
帮帮顶顶!!
回复
Rf2698 2018-11-28 10:14:21
感谢分享,楼主V5~
回复
pp美景 2018-11-28 10:15:13
支持,感谢,祝code4app越来越好~
回复
kengsir 2018-11-28 10:15:20
写的真的很不错
回复
birdmichael126 2018-11-28 10:15:50
Rf2698 发表于 2018-11-28 10:14
感谢分享,楼主V5~

淡定,淡定,淡定……
回复
Kuroco 2018-11-28 16:04:40
感谢分享,Code4App有你更精彩:)
回复
code4app热心网友 2018-11-29 08:18:41
楼主用心了,内容非常精彩。
回复
12345下一页
提取码:  下载次数:28 状态:已购或VIP 售价:0(原价:15)金钱 下载权限:初级码农 
1473 0 28
联系我们
首页/微信公众账号投稿

帖子代码编辑/版权问题

QQ:435399051,742864542

如何获得代码达人称号?

代码贡献英雄榜
用户名 下载数
通过邮件订阅最新 Code4App 信息
上一条 /4 下一条
联系我们
关闭
合作电话:
13802416937
Email:
435399051@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| 广东互联网违法和不良信息举报中心|中国互联网举报中心|Github|申请友链|手机版|Code4App ( 粤ICP备15117877号-1 )

快速回复 返回顶部 返回列表