实现60fps的网易云音乐首页

本文详细介绍了如何使用Swift实现网易云音乐首页,包括容器设计、Banner、Menu、Cells的实现,并重点探讨了性能优化,如图片解码、XIB、AutoLayout、预计算布局等方面的策略,旨在达到接近60fps的流畅体验。

前言

网易云音乐是一款很优秀的音乐软件,我也是它的忠实用户。最近在研究如何更好的开发TableView,接着我写了一个Model驱动的小框架 - MDTable。为了去验证框架的可用性,我选择了网易云音乐的首页来作为Demo,语言是Swift 3。

本文的内容包括:

  • 实现网易云音乐首页的思路
  • 如何建立一个轻量级的UITableViewController(不到100行)
  • 性能瓶颈原因以分析及如何优化到接近60fps

Note:本文并没有用Reveal去分析网易云音乐iOS客户端的原始UI布局,所以实现方式肯定和原始App有出入。另外,本文仅代表个人观点,与雇主没有任何关系。

最后效果如下


容器

整体上分析来看,网易云音乐的首页是一个异构的滚动视图。由上至下依次是:

  • Banner - 轮播
  • Menu - 三个入口
  • 6个分类,推荐歌单,独家放送,推荐MV,精选专栏,主播电台,最新音乐。每一个分类的UI布局都不一样。

并且这些布局都不是动态的,所谓动态的就是向淘宝京东首页那种,做不同的活动,首页可以按照不同的方式去显示内容,而不需要从App Store下载新的版本。

基于这些,有两种实现方式:

用单纯的UIScrollView作为容器,其他的内容作为SubView添加到ScrollView中,但要手动控制每一个视图进入屏幕和消失的事件,来进行图片的懒加载。采用这种方式可以选择天猫开源的LazyScrollView

用UITableView作为容器,其他的每一行内容都是一个Cell。

本文选择了后者,原因也很简单:我是为了评估MDTable,而MDTable是一个基于TableView的框架。


网易云音乐Banner的最上面是一个轮播图,效果如下

可以看到视图大致分为两部分:

  • ScrollView - 容器
  • ItemView - 轮播的具体内容
    • ImageView - 背景图
    • Label - 标签,就是图中的广告部分。

轮播图有很多种实现方式,这里我选择了之前写的ParallexBanner

这是一个支持视差效果的Banner,所谓视差效果,就是类似这种:

ParallexBanner原理我在这篇博客里有详细介绍,这里就不浪费篇幅了。

另外,那个标签Label也很容易实现,只要用一个左边是圆角的UILabel即可,这里写了个方便的扩展

extension UIView {
    func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
        let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
    }
}

Menu的目标效果如下:

中间的“每日歌曲推荐”这个选项有点意思,因为中间的文字是会随着日期变的。实现起来也很简单,图片留白,中间放一个Lable即可。

这是最后我选择的布局方式:

侧面看起来:

也就是说,SubView是这样子的:

  • UIImageView - 红色背景圆圈
  • UILabel - 标题(每日歌曲推荐)
  • UIImageView - 图标(日历图标)
  • UILabel - 日期时间(25)

Note: 这里先不管按下态,按下态在下文统一讲解。


Cells

我们先从UI效果入手,一共有六种异构的Cell。

第一个冒出来的想法是Cell中放置CollectionView,CollectiionViewLayout也很简单,采用系统提供的FlowLayout即可。

图个省事,每一个CollectionViewCell我都采用Xib的方式,用AutoLayout布局的。

然后,每一个TableViewCell的子类如下:


                
要在 Android 端实现类似网易云音乐的播放界面布局使用kotlin语言,需从UI 结构、动画效果、功能模块三方面入手。以下是基于 Android 原生开(Kotlin + Jetpack Compose)的实现思路与核心代码示例: 一、界面结构分层设计 网易云播放界面可拆解为 “背景层 + 黑胶唱片层 + 控制层” 三部分,通过 Compose 的 Box 组件实现分层叠加: @Composable fun NetEaseMusicPlayerScreen() { Box(modifier = Modifier.fillMaxSize()) { // 1. 背景层:高斯模糊 + 封面图 BackgroundLayer(coverRes = R.drawable.your_cover) // 2. 黑胶唱片层:唱片旋转 + 唱针动画 VinylLayer(isPlaying = true, coverRes = R.drawable.your_cover) // 3. 控制层:歌曲信息 + 进度条 + 操作按钮 ControlLayer( songName = “今夕何夕”, artist = “Adam Gubman”, onPlayPause = { /* 播放/暂停逻辑 / }, onPrev = { / 上一首逻辑 / }, onNext = { / 下一首逻辑 */ } ) } } 二、核心动画实现 黑胶唱片旋转动画 通过 LaunchedEffect 循环更新旋转角度,模拟唱片匀速转动: @Composable fun VinylLayer(isPlaying: Boolean, coverRes: Int) { var rotation by remember // 播放时持续旋转,暂停时停止 LaunchedEffect(isPlaying) { while (isPlaying) { rotation += 0.04f * 16 // 控制旋转速度 if (rotation > 360f) rotation -= 360f delay(16) // 模拟 60fps } } Box( modifier = Modifier .size(200.dp) .align(Alignment.Center) .graphicsLayer(rotationZ = rotation) // 应用旋转 ) { // 唱片底盘 Image( painter = painterResource(R.drawable.disc_background), contentDescription = “Disc Background”, modifier = Modifier.matchParentSize() ) // 唱片封面 Image( painter = painterResource(coverRes), contentDescription = “Disc Cover”, modifier = Modifier .size(160.dp) .align(Alignment.Center) .clip(CircleShape) ) } } 唱针摆动动画 通过 animateFloatAsState 实现播放/暂停时的平滑摆动: @Composable fun NeedleAnimation(isPlaying: Boolean) { val needleAngle by animateFloatAsState( targetValue = if (isPlaying) 0f else -25f, animationSpec = tween(durationMillis = 500), finishedListener = { /* 动画结束回调 */ } ) Image( painter = painterResource(R.drawable.needle), contentDescription = “Needle”, modifier = Modifier .align(Alignment.TopCenter) .graphicsLayer(rotationZ = needleAngle) // 绕Z轴旋转 ) } 三、背景层与控制层实现 高斯模糊背景 使用 Compose 的 blur 修饰符实现封面图模糊效果: @Composable fun BackgroundLayer(coverRes: Int) { Image( painter = painterResource(coverRes), contentDescription = “Background”, modifier = Modifier .fillMaxSize() .blur(radius = 10.dp) // 高斯模糊半径 .drawWithContent { // 叠加半透明遮罩,提升文字可读性 drawContent() drawRect(color = Color.Black.copy(alpha = 0.5f)) } ) } 控制层布局 包含歌曲信息、进度条、播放按钮等,通过 Column 垂直排列: @Composable fun ControlLayer( songName: String, artist: String, onPlayPause: () -> Unit, onPrev: () -> Unit, onNext: () -> Unit ) { Column( modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter) .padding(16.dp) ) { // 歌曲信息 Text(songName, fontSize = 20.sp, fontWeight = FontWeight.Bold, color = Color.White) Text(artist, fontSize = 16.sp, color = Color.White.copy(alpha = 0.8f)) // 进度条 Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Text(“00:53”, fontSize = 12.sp, color = Color.White) Slider( value = 0.3f, // 播放进度(0-1) onValueChange = { /* 拖动进度逻辑 */ }, modifier = Modifier.weight(1f), colors = SliderDefaults.colors( thumbColor = Color.Red, activeTrackColor = Color.Red, inactiveTrackColor = Color.White.copy(alpha = 0.3f) ) ) Text(“03:16”, fontSize = 12.sp, color = Color.White) } // 操作按钮 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { IconButton(onClick = onPrev) { Icon(Icons.Default.SkipPrevious, contentDescription = “Previous”, tint = Color.White) } IconButton(onClick = onPlayPause) { Icon(Icons.Default.Pau。分成组件来写,方便我查看代码和修改,需要添加什么信赖,以及我需要做出的效果是分为三层,点进去的初始界面是歌曲列表,随便点进去一首歌进行播放,进入黑胶唱片界面,播放音乐,有暂停,下一首,上一首,然后右下角还有个列表栏的功能,然后弹出音乐列表栏,要切换丝滑,还能够支持我替换背景样式为图片,我还能改变黑胶唱片的封面
最新发布
11-27
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值