炫酷~RecyclerView视差装饰器-ParallaxDecoration

老规矩,先上图,看看是不是你想要的,美团效果:

【炫酷~RecyclerView视差装饰器-ParallaxDecoration】最终效果:
来一个图形分析 接下来我要写一个简单示例,先分析一下布局,见下图,最外层是NestedScrollView,之后嵌套一个LinearLayout头部,中间TabLayout选择器,底部一个ViewPager
ViewPager高度需要动态控制,看自己的需求了,如果是美团那种效果,就是ViewPager高度 = NestedScrollView高度 - TabLayout高度
炫酷~RecyclerView视差装饰器-ParallaxDecoration
文章图片

话不多说,代码实现 接下来我写一个例子,如果按照普通控件的嵌套方式来实现,那么肯定存在滑动冲突,会出现RecyclerView先进行滑动其次才是ScrollView滑动,那么就需要先重写NestedScrollView控件,用于控制最大的滑动距离,当达到最大滑动距离,再分发给RecyclerView滑动!
NestedScrollView重写 需要继承自NestedScrollView并重写onStartNestedScrollonNestedPreScroll方法,如下

package com.cyn.mtimport android.content.Context import android.util.AttributeSet import android.view.View import androidx.core.view.NestedScrollingParent2 import androidx.core.widget.NestedScrollView/** * @author cyn */ class CoordinatorScrollview : NestedScrollView, NestedScrollingParent2 { private var maxScrollY = 0constructor(context: Context?) : super(context!!) constructor(context: Context?, attrs: AttributeSet?) : super( context!!, attrs )constructor( context: Context?, attrs: AttributeSet?, defStyleAttr: Int ) : super(context!!, attrs, defStyleAttr)override fun onStartNestedScroll( child: View, target: View, axes: Int, type: Int ): Boolean { return true }/** * 设置最大滑动距离 * * @param maxScrollY 最大滑动距离 */ fun setMaxScrollY(maxScrollY: Int) { this.maxScrollY = maxScrollY }/** * @param target触发嵌套滑动的View * @param dx表示 View 本次 x 方向的滚动的总距离 * @param dy表示 View 本次 y 方向的滚动的总距离 * @param consumed 表示父布局消费的水平和垂直距离 * @param type触发滑动事件的类型 */ override fun onNestedPreScroll( target: View, dx: Int, dy: Int, consumed: IntArray, type: Int ) { if (dy > 0 && scrollY < maxScrollY) { scrollBy(0, dy) consumed[1] = dy } } }

布局文件 我按照美团的布局大体写出这样的布局

Fragment 加入,在Fragment中放入RecyclerView,提供给ViewPager使用,这里代码就不贴了,可以直接下源码!源码在文章末尾!
主要代码(重点来了) coordinatorScrollView最大滑动距离即是titleLinerLayout的高度,所以实现titleLinerLayoutpost方法,来监听titleLinerLayout的高度,由于这一块布局常常是通过网络请求后加载,所以,网络请求完毕后要再次实现post设置coordinatorScrollView最大滑动距离,如第80行代码第90行代码,在这里,我并不推荐使用多次回调监听的方法!使用post只用调用一次,如果使用多次监听View变化的方法,应该在最后一次网络请求完毕后将此监听事件remove掉!
package com.cyn.mtimport android.content.res.Resources import android.os.Bundle import android.os.Handler import android.util.DisplayMetrics import android.view.LayoutInflater.from import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.title_layout.view.*class MainActivity : AppCompatActivity() {//屏幕宽 var screenWidth = 0//屏幕高 var screenHeight = 0//tabLayout的文本和图片 private val tabTextData = https://www.it610.com/article/arrayOf("常用药品", "夜间送药", "隐形眼镜", "成人用品", "医疗器械", "全部商家") private val tabIconData = https://www.it610.com/article/arrayOf( R.mipmap.tab_icon, R.mipmap.tab_icon, R.mipmap.tab_icon, R.mipmap.tab_icon, R.mipmap.tab_icon, R.mipmap.tab_icon ) private var fragmentData = mutableListOf()override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)initView() initData() }private fun initView() {//获取屏幕宽高 val resources: Resources = this.resources val dm: DisplayMetrics = resources.displayMetrics screenWidth = dm.widthPixels screenHeight = dm.heightPixels//状态栏沉浸 StatusBarUtil.immersive(this)//titleBar填充 StatusBarUtil.setPaddingSmart(this, titleBar)//状态栏字体颜色设置为黑色 StatusBarUtil.darkMode(this)//动态设置ViewPager高度 coordinatorScrollView.post { val layoutParams = viewPager.layoutParams layoutParams.width = screenWidth layoutParams.height = coordinatorScrollView.height - tabLayout.height viewPager.layoutParams = layoutParams }}private fun initData() {//我模拟在头部动态添加三个布局,就用图片代替了,要设置的图片高度都是我提前算好的,根据屏幕的比例来计算的 val titleView1 = getTitleView(screenWidth * 0.42F, R.mipmap.title1) val titleView2 = getTitleView(screenWidth * 0.262F, R.mipmap.title2) titleLinerLayout.addView(titleView1) titleLinerLayout.addView(titleView2)//设置最大滑动距离 titleLinerLayout.post { coordinatorScrollView.setMaxScrollY(titleLinerLayout.height) }//用于请求网络后动态添加子布局 Handler().postDelayed({ val titleView3 = getTitleView(screenWidth * 0.589F, R.mipmap.title3) titleLinerLayout.addView(titleView3)//再次设置最大滑动距离 titleLinerLayout.post { coordinatorScrollView.setMaxScrollY(titleLinerLayout.height) }}, 200)//添加TabLayout for (i in tabTextData.indices) { tabLayout.addTab(tabLayout.newTab()) tabLayout.getTabAt(i)!!.setText(tabTextData[i]).setIcon(tabIconData[i])//添加Fragment fragmentData.add(TestFragment.newInstance(tabTextData[i])) }//Fragment ViewPager viewPager.adapter = ViewPagerAdapter(supportFragmentManager, fragmentData)//TabLayout关联ViewPager tabLayout.setupWithViewPager(viewPager)//设置TabLayout数据 for (i in tabTextData.indices) { tabLayout.getTabAt(i)!!.setText(tabTextData[i]).setIcon(tabIconData[i]) } }/** * 获取一个title布局 * 我这里就用三张图片模拟的 * * @height 要设置的图片高度 */ private fun getTitleView(height: Float, res: Int): View { val inflate = from(this).inflate(R.layout.title_layout, null, false) val layoutParams = inflate.titleImage.layoutParams layoutParams.width = screenWidth layoutParams.height = height.toInt() inflate.titleImage.setImageResource(res) return inflate } }

最终效果
源码Github地址:https://github.com/ThirdGodde...
Android高级开发系统进阶笔记、最新面试复习笔记PDF,我的GitHub
炫酷~RecyclerView视差装饰器-ParallaxDecoration
文章图片

文末 您的点赞收藏就是对我最大的鼓励!
欢迎关注我,分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!

    推荐阅读