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

最终成果:

来一个图形剖析

接下来我要写一个简略示例,先剖析一下布局,见下图,最外层是NestedScrollView,之后嵌套一个LinearLayout头部,两头TabLayout选择器,底部一个ViewPager

ViewPager高度须要动态控制,看本人的需要了,如果是美团那种成果,就是ViewPager高度 = NestedScrollView高度 - TabLayout高度

话不多说,代码实现

接下来我写一个例子,如果依照一般控件的嵌套形式来实现,那么必定存在滑动抵触,会呈现RecyclerView先进行滑动其次才是ScrollView滑动,那么就须要先重写NestedScrollView控件,用于管制最大的滑动间隔,当达到最大滑动间隔,再分发给RecyclerView滑动!

NestedScrollView重写

须要继承自NestedScrollView并重写onStartNestedScrollonNestedPreScroll办法,如下

package com.cyn.mtimport android.content.Contextimport android.util.AttributeSetimport android.view.Viewimport androidx.core.view.NestedScrollingParent2import androidx.core.widget.NestedScrollView/** * @author cyn */class CoordinatorScrollview : NestedScrollView, NestedScrollingParent2 {    private var maxScrollY = 0    constructor(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        }    }}
布局文件

我依照美团的布局大体写出这样的布局

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context=".MainActivity">    <!--titleBar-->    <LinearLayout        android:id="@+id/titleBar"        android:layout_width="match_parent"        android:layout_height="45dp"        android:gravity="center_vertical"        android:orientation="horizontal"        android:paddingLeft="18dp"        android:paddingRight="18dp">        <EditText            android:layout_width="0dp"            android:layout_height="35dp"            android:layout_marginEnd="12dp"            android:layout_marginRight="12dp"            android:layout_weight="1"            android:background="@drawable/edit_style"            android:paddingLeft="12dp"            android:paddingRight="12dp" />        <TextView            android:layout_width="wrap_content"            android:layout_height="35dp"            android:background="@drawable/button_style"            android:gravity="center"            android:paddingLeft="15dp"            android:paddingRight="15dp"            android:text="搜寻"            android:textColor="#333333"            android:textStyle="bold" />    </LinearLayout>    <!--coordinatorScrollView-->    <com.cyn.mt.CoordinatorScrollview        android:id="@+id/coordinatorScrollView"        android:layout_width="match_parent"        android:layout_height="match_parent">        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:orientation="vertical">            <!--相当于剖析图中头部的LinearLayout,模仿动静增加的状况-->            <LinearLayout                android:id="@+id/titleLinerLayout"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:orientation="vertical" />            <!--相当于剖析图中红色标记处TabLayout-->            <com.google.android.material.tabs.TabLayout                android:id="@+id/tabLayout"                android:layout_width="match_parent"                android:layout_height="wrap_content" />            <!--相当于剖析图中绿色标记处ViewPager,代码中动静设置高度-->            <androidx.viewpager.widget.ViewPager                android:id="@+id/viewPager"                android:layout_width="match_parent"                android:layout_height="wrap_content" />        </LinearLayout>    </com.cyn.mt.CoordinatorScrollview></LinearLayout>
Fragment

退出,在Fragment中放入RecyclerView,提供给ViewPager应用,这里代码就不贴了,能够间接下源码!源码在文章开端!

次要代码(重点来了)

coordinatorScrollView最大滑动间隔即是titleLinerLayout的高度,所以实现titleLinerLayoutpost办法,来监听titleLinerLayout的高度,因为这一块布局经常是通过网络申请后加载,所以,网络申请结束后要再次实现post设置coordinatorScrollView最大滑动间隔,如第80行代码第90行代码,在这里,我并不举荐应用屡次回调监听的办法!应用post只用调用一次,如果应用屡次监听View变动的办法,应该在最初一次网络申请结束后将此监听事件remove掉!

package com.cyn.mtimport android.content.res.Resourcesimport android.os.Bundleimport android.os.Handlerimport android.util.DisplayMetricsimport android.view.LayoutInflater.fromimport android.view.Viewimport androidx.appcompat.app.AppCompatActivityimport androidx.fragment.app.Fragmentimport 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 = arrayOf("罕用药品", "夜间送药", "隐形眼镜", "成人用品", "医疗器械", "全副商家")    private val tabIconData = 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<Fragment>()    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

文末

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