Android 3D Cover Flow 效果

发布时间:2026/7/11 10:40:57
Android 3D Cover Flow 效果 实现步骤1. 添加依赖首先在app/build.gradle文件中添加必要的依赖特别是用于实现高斯模糊效果的库。dependencies{implementationandroidx.recyclerview:recyclerview:1.3.2implementationjp.wasabeef:recyclerview-animators:4.0.2// 用于实现模糊效果implementationcom.github.bumptech.glide:glide:4.16.0kaptcom.github.bumptech.glide:glide:4.16.0// 如果使用 Kotlin}2. 创建数据模型和适配器创建一个简单的数据类来存储专辑信息并编写一个标准的RecyclerView.Adapter。// Album.ktdataclassAlbum(valtitle:String,valimageUrl:String,valcolor:Int)// CoverFlowAdapter.ktclassCoverFlowAdapter(privatevalalbums:ListAlbum,privatevalonItemClick:(Int)-Unit):RecyclerView.AdapterCoverFlowAdapter.CoverFlowViewHolder(){innerclassCoverFlowViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){valimageView:ImageViewitemView.findViewById(R.id.imageView)valtitleView:TextViewitemView.findViewById(R.id.titleView)}overridefunonCreateViewHolder(parent:ViewGroup,viewType:Int):CoverFlowViewHolder{valviewLayoutInflater.from(parent.context).inflate(R.layout.item_cover_flow,parent,false)returnCoverFlowViewHolder(view)}overridefunonBindViewHolder(holder:CoverFlowViewHolder,position:Int){valalbumalbums[position]holder.titleView.textalbum.title holder.itemView.setBackgroundColor(album.color)// 使用 Glide 加载图片并添加模糊效果Glide.with(holder.itemView).load(album.imageUrl).transform(BlurTransformation(25,3))// 需要导入 glide-transformations 库或自定义.into(holder.imageView)holder.itemView.setOnClickListener{onItemClick(getBindingAdapterPosition())}}overridefungetItemCount()albums.size}res/layout/item_cover_flow.xml:?xml version1.0 encodingutf-8?FrameLayoutxmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_width200dpandroid:layout_height300dpandroid:layout_margin8dpImageViewandroid:idid/imageViewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:scaleTypecenterCropandroid:background#333/TextViewandroid:idid/titleViewandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:layout_gravitybottomandroid:padding8dpandroid:gravitycenterandroid:textColor#FFFandroid:background#80000000android:textSize14sp//FrameLayout3. 实现 LoopingLayoutManager这是实现无限循环的关键。它通过重写LinearLayoutManager来欺骗RecyclerView使其认为列表是无限的。// LoopingLayoutManager.ktclassLoopingLayoutManager(context:Context,orientation:IntHORIZONTAL,reverseLayout:Booleanfalse):LinearLayoutManager(context,orientation,reverseLayout){overridefuncanScrollHorizontally():BooleantrueoverridefuncanScrollVertically():Booleanfalse// 核心通过返回一个极大的数来模拟无限列表overridefungetItemCount():Int{returnif(super.getItemCount()0)0elseInt.MAX_VALUE}// 将无限的位置映射到实际的数据位置overridefungetPosition(view:View):Int{valpositionsuper.getPosition(view)returnposition%super.getItemCount()}// 滚动到中间位置为无限循环做准备overridefunonLayoutChildren(recycler:Recycler?,state:State?){if(itemCount0){// 滚动到 无限 列表的中间这样两边都有足够的空间scrollToPosition(Int.MAX_VALUE/2)}super.onLayoutChildren(recycler,state)}}4. 创建 CoverFlowView 自定义组件这是整个效果的核心它封装了RecyclerView并处理所有的 3D 变换逻辑。// CoverFlowView.ktclassCoverFlowViewJvmOverloadsconstructor(context:Context,attrs:AttributeSet?null,defStyleAttr:Int0):FrameLayout(context,attrs,defStyleAttr){privatevalrecyclerView:RecyclerViewprivatevallayoutManager:LoopingLayoutManagerprivatevaladapterWrapperAdapterWrapper()privatevaronCenterItemChangedListener:((Int)-Unit)?null// 配置参数对应原JS中的 CONFIGprivatevalconfigConfig()init{recyclerViewRecyclerView(context).apply{overScrollModeOVER_SCROLL_NEVERsetHasFixedSize(true)layoutManagerthisCoverFlowView.layoutManager adapteradapterWrapper// 添加滚动监听来处理 3D 变换addOnScrollListener(object:RecyclerView.OnScrollListener(){overridefunonScrolled(recyclerView:RecyclerView,dx:Int,dy:Int){super.onScrolled(recyclerView,dx,dy)applyTransformations()}overridefunonScrollStateChanged(recyclerView:RecyclerView,newState:Int){super.onScrollStateChanged(recyclerView,newState)if(newStateRecyclerView.SCROLL_STATE_IDLE){// 滚动停止时确保中间项居中snapToCenter()}}})}addView(recyclerView)}// 应用 3D 变换的核心方法privatefunapplyTransformations(){valcenterrecyclerView.width/2for(iin0until recyclerView.childCount){valchildrecyclerView.getChildAt(i)valchildCenter(child.leftchild.right)/2valoffsetcenter-childCentervaloffsetRatiooffset.toFloat()/config.xDist// 只处理可见范围内的卡片if(abs(offsetRatio)config.maxVisible/2){applyTransformationToChild(child,offsetRatio)}else{child.alpha0f}}}privatefunapplyTransformationToChild(child:View,offsetRatio:Float){valabsOffsetabs(offsetRatio)// 计算变换参数valtranslateXoffsetRatio*config.xDistvaltranslateZ-absOffset*config.zDepthif(offsetRatio0f)config.centerPushelse0fvalrotateY-offsetRatio*config.rotateAngle// 应用变换child.apply{// 使用 Camera 进行 3D 变换valcameraCamera()valmatrixMatrix()camera.save()camera.rotateY(rotateY)camera.getMatrix(matrix)camera.restore()// 设置矩阵的中心点matrix.preTranslate(-width/2f,-height/2f)matrix.postTranslate(width/2f,height/2f)// 应用平移和旋转translationXtranslateX translationZtranslateZ cameraMatrixmatrix// 其他属性alphamaxOf(0.3f,1-absOffset*0.2f)scaleXmaxOf(0.8f,1-absOffset*0.1f)scaleYmaxOf(0.8f,1-absOffset*0.1f)// 模糊效果 (需要 Glide 或其他库支持这里简化处理)// 实际项目中可能需要使用 RenderScript 或 GPUImage 实现实时模糊}}privatefunsnapToCenter(){// 找到最接近中心的子项并平滑滚动到该位置valcenterrecyclerView.width/2varclosestChild:View?nullvarminDistanceInt.MAX_VALUEfor(iin0until recyclerView.childCount){valchildrecyclerView.getChildAt(i)valchildCenter(child.leftchild.right)/2valdistanceabs(center-childCenter)if(distanceminDistance){minDistancedistance closestChildchild}}closestChild?.let{valpositionlayoutManager.getPosition(it)recyclerView.smoothScrollToPosition(position)onCenterItemChangedListener?.invoke(position)}}funsetAlbums(albums:ListAlbum){adapterWrapper.submitList(albums)// 初始布局后应用变换post{applyTransformations()}}funsetOnCenterItemChangedListener(listener:(Int)-Unit){onCenterItemChangedListenerlistener}// 内部适配器包装器用于处理数据privateinnerclassAdapterWrapper:RecyclerView.AdapterCoverFlowAdapter.CoverFlowViewHolder(){privatevaralbumsemptyListAlbum()privatelateinitvaractualAdapter:CoverFlowAdapterfunsubmitList(newAlbums:ListAlbum){albumsnewAlbums actualAdapterCoverFlowAdapter(albums){position-smoothScrollToPosition(position)}notifyDataSetChanged()}overridefunonCreateViewHolder(parent:ViewGroup,viewType:Int):CoverFlowAdapter.CoverFlowViewHolder{returnactualAdapter.onCreateViewHolder(parent,viewType)}overridefunonBindViewHolder(holder:CoverFlowAdapter.CoverFlowViewHolder,position:Int){valactualPositionposition%albums.size actualAdapter.onBindViewHolder(holder,actualPosition)}overridefungetItemCount()Int.MAX_VALUE}dataclassConfig(valxDist:Int220,// 卡片水平间距valzDepth:Int180,// Z轴深度valrotateAngle:Int35,// 旋转角度valcenterPush:Int60,// 中心卡片凸出距离valmaxVisible:Int11// 最大可见卡片数)}5. 在 Activity 中使用最后在你的 Activity 或 Fragment 中使用这个自定义组件。// MainActivity.ktclassMainActivity:AppCompatActivity(){privatelateinitvarcoverFlowView:CoverFlowViewoverridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)coverFlowViewfindViewById(R.id.coverFlowView)valalbumslistOf(Album(爱的暴风雨,https://picsum.photos/seed/album1/400/600,Color.parseColor(#ff5f6d)),Album(咆哮2002,https://picsum.photos/seed/album2/400/600,Color.parseColor(#ffc371)),// ... 添加更多专辑)coverFlowView.setAlbums(albums)coverFlowView.setOnCenterItemChangedListener{position-Toast.makeText(this,当前选中:${albums[position%albums.size].title},Toast.LENGTH_SHORT).show()}}}res/layout/activity_main.xml:?xml version1.0 encodingutf-8?FrameLayoutxmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:background#1a1a1acom.yourpackage.CoverFlowViewandroid:idid/coverFlowViewandroid:layout_widthmatch_parentandroid:layout_height400dpandroid:layout_gravitycenter//FrameLayout