如何在Flutter中实现带阴影与放大效果的滚动视图(音乐APP开发)
实现带缩放+阴影效果的滚动视图方案
针对你需要的音乐APP滚动视图(横向卡片滚动,中间卡片放大、阴影增强),分Android和iOS两个平台给出具体实现方案:
Android 实现方案
核心逻辑
用RecyclerView实现横向滚动,通过滚动监听计算每个item与屏幕中心的距离,动态调整item的缩放比例和阴影高度,距离中心越近的item缩放越大、阴影越明显。
代码实现
- 卡片Item布局(
item_music_card.xml)
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="180dp" android:layout_height="240dp" app:cardCornerRadius="12dp" app:cardElevation="8dp"> <ImageView android:id="@+id/album_cover" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop"/> </androidx.cardview.widget.CardView>
- RecyclerView初始化与滚动监听
// 获取RecyclerView实例 val recyclerView = findViewById<RecyclerView>(R.id.recycler_view) // 设置横向布局管理器 recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) // 设置适配器 recyclerView.adapter = MusicAdapter(albumList) // 添加滚动监听,实现缩放和阴影变化 recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) val screenCenterX = recyclerView.width / 2 // 遍历所有可见item recyclerView.children.forEach { child -> val itemCenterX = (child.left + child.right) / 2 // 计算item与屏幕中心的距离 val distance = Math.abs(screenCenterX - itemCenterX) // 计算缩放比例,范围0.85-1.0,距离越近缩放越大 val scale = 1.0f - (distance / screenCenterX) * 0.15f child.scaleX = scale child.scaleY = scale // 调整阴影高度,中心item阴影最大 val elevation = 8f + (1 - scale) * 12f (child as CardView).cardElevation = elevation } } })
- 适配器代码
class MusicAdapter(private val albumList: List<Album>) : RecyclerView.Adapter<MusicAdapter.ViewHolder>() { inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val albumCover: ImageView = itemView.findViewById(R.id.album_cover) val cardView: CardView = itemView.findViewById(R.id.card_view) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_music_card, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val album = albumList[position] // 加载专辑封面,这里以Glide为例 Glide.with(holder.itemView.context) .load(album.coverUrl) .into(holder.albumCover) } override fun getItemCount(): Int = albumList.size }
iOS 实现方案
核心逻辑
用UICollectionView实现横向滚动,通过UIScrollViewDelegate的滚动回调,计算每个cell与屏幕中心的距离,动态调整cell的缩放变换和layer阴影参数,距离中心越近的cell缩放越大、阴影越浓。
代码实现
- CollectionView初始化
class MusicViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { private var albumList: [Album] = [] private lateinit var collectionView: UICollectionView override func viewDidLoad() { super.viewDidLoad() setupCollectionView() loadAlbumData() } private func setupCollectionView() { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.itemSize = CGSize(width: 180, height: 240) layout.sectionInset = UIEdgeInsets(top: 20, left: 40, bottom: 20, right: 40) layout.minimumLineSpacing = 20 collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.delegate = self collectionView.dataSource = self collectionView.register(MusicCardCell.self, forCellWithReuseIdentifier: "MusicCardCell") collectionView.showsHorizontalScrollIndicator = false view.addSubview(collectionView) } // 数据源方法 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return albumList.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MusicCardCell", for: indexPath) as! MusicCardCell cell.configure(with: albumList[indexPath.item]) return cell } }
- 滚动监听与缩放阴影实现
extension MusicViewController { func scrollViewDidScroll(_ scrollView: UIScrollView) { let screenCenterX = view.bounds.width / 2 // 遍历所有可见cell for cell in collectionView.visibleCells { let cellCenterX = cell.center.x - scrollView.contentOffset.x let distance = abs(screenCenterX - cellCenterX) // 计算缩放比例,范围0.85-1.0 let scale = 1.0 - (distance / screenCenterX) * 0.15 cell.transform = CGAffineTransform(scaleX: scale, y: scale) // 调整阴影参数 cell.layer.shadowOpacity = Float(0.3 + (1 - scale) * 0.7) cell.layer.shadowRadius = CGFloat(8 + (1 - scale) * 12) cell.layer.shadowOffset = CGSize(width: 0, height: CGFloat(4 + (1 - scale) * 8)) } } }
- 自定义Cell代码
class MusicCardCell: UICollectionViewCell { private let albumImageView: UIImageView = { let iv = UIImageView() iv.contentMode = .scaleAspectFill iv.clipsToBounds = true iv.layer.cornerRadius = 12 return iv }() override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { contentView.addSubview(albumImageView) albumImageView.frame = contentView.bounds // 初始化阴影参数 layer.shadowColor = UIColor.black.cgColor layer.shadowOpacity = 0.3 layer.shadowRadius = 8 layer.shadowOffset = CGSize(width: 0, height: 4) // 设置阴影路径优化性能 layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath } func configure(with album: Album) { // 加载专辑封面,这里以Kingfisher为例 albumImageView.kf.setImage(with: URL(string: album.coverUrl)) } }
内容的提问来源于stack exchange,提问作者Kartikey Patel
相关产品推荐
相关产品推荐

