You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中实现带阴影与放大效果的滚动视图(音乐APP开发)

实现带缩放+阴影效果的滚动视图方案

针对你需要的音乐APP滚动视图(横向卡片滚动,中间卡片放大、阴影增强),分Android和iOS两个平台给出具体实现方案:

Android 实现方案

核心逻辑

用RecyclerView实现横向滚动,通过滚动监听计算每个item与屏幕中心的距离,动态调整item的缩放比例和阴影高度,距离中心越近的item缩放越大、阴影越明显。

代码实现

  1. 卡片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>
  1. 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
        }
    }
})
  1. 适配器代码
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缩放越大、阴影越浓。

代码实现

  1. 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
    }
}
  1. 滚动监听与缩放阴影实现
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))
        }
    }
}
  1. 自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 21:55:03