According to the old rule, the picture above first to see if it is what you want. Meituan effect:
Final effect:
A graphic analysis
Next, I want to write a simple example. First analyze the layout, as shown in the figure below. The outermost layer is NestedScrollView, and then nest a LinearLayout header, a TabLayout selector in the middle and a ViewPager at the bottom
The ViewPager height needs dynamic control. It depends on your own needs. If it is the effect of meituan, it is ViewPager height = NestedScrollView height - TabLayout height
Don't say much, code implementation
Next, I'll write an example. If it is implemented according to the nesting method of ordinary controls, there must be a sliding conflict. RecyclerView will slide first, followed by ScrollView. Then, you need to rewrite the NestedScrollView control to control the maximum sliding distance. When the maximum sliding distance is reached, it will be distributed to RecyclerView for sliding!
NestedScrollView override
Inherit from NestedScrollView and override onStartNestedScroll and onNestedPreScroll methods, as follows
package com.cyn.mt import 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 = 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 } /** * Set maximum sliding distance * * @param maxScrollY Maximum sliding distance */ fun setMaxScrollY(maxScrollY: Int) { this.maxScrollY = maxScrollY } /** * @param target Triggered nested sliding View * @param dx Represents the total distance of the View scrolling in the x direction this time * @param dy Represents the total distance of the View in the y direction this time * @param consumed Represents the horizontal and vertical distances consumed by the parent layout * @param type Type of sliding event triggered */ override fun onNestedPreScroll( target: View, dx: Int, dy: Int, consumed: IntArray, type: Int ) { if (dy > 0 && scrollY < maxScrollY) { scrollBy(0, dy) consumed[1] = dy } } }
Layout file
I write this layout roughly according to the layout of meituan
<?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="search" 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"> <!--Equivalent to the head in the analysis diagram LinearLayout,Simulate dynamic addition--> <LinearLayout android:id="@+id/titleLinerLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> <!--It is equivalent to the red mark in the analysis diagram TabLayout--> <com.google.android.material.tabs.TabLayout android:id="@+id/tabLayout" android:layout_width="match_parent" android:layout_height="wrap_content" /> <!--It is equivalent to the green mark in the analysis diagram ViewPager,Dynamically set height in code--> <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
Join, put RecyclerView in the Fragment and provide it to ViewPager for use. The code here will not be pasted. You can directly download the source code! The source code is at the end of the article!
Main code (here comes the point)
The maximum sliding distance of coordinatorScrollView is the height of titleLinerLayout, so the post method of titleLinerLayout is implemented to monitor the height of titleLinerLayout. Since this layout is often loaded after network request, post should be implemented again after network request. Set the maximum sliding distance of coordinatorScrollView, such as line 80 and line 90, Here, I do not recommend using multiple callback monitoring! Use post only to call once. If you use the method of monitoring View changes for many times, you should remove this monitoring event after the last network request is completed!
package com.cyn.mt import 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() { //Screen width var screenWidth = 0 //Screen height var screenHeight = 0 //Text and pictures of tabLayout private val tabTextData = arrayOf("Common drugs", "Night delivery", "contact lenses", "adult erotica products", "medical apparatus and instruments", "All merchants") 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() { //Get screen width and height val resources: Resources = this.resources val dm: DisplayMetrics = resources.displayMetrics screenWidth = dm.widthPixels screenHeight = dm.heightPixels //Status bar immersion StatusBarUtil.immersive(this) //titleBar fill StatusBarUtil.setPaddingSmart(this, titleBar) //The font color of the status bar is set to black StatusBarUtil.darkMode(this) //Dynamically set ViewPager height coordinatorScrollView.post { val layoutParams = viewPager.layoutParams layoutParams.width = screenWidth layoutParams.height = coordinatorScrollView.height - tabLayout.height viewPager.layoutParams = layoutParams } } private fun initData() { //I simulate dynamically adding three layouts in the head and use pictures instead. The height of the pictures to be set is calculated in advance according to the proportion of the screen val titleView1 = getTitleView(screenWidth * 0.42F, R.mipmap.title1) val titleView2 = getTitleView(screenWidth * 0.262F, R.mipmap.title2) titleLinerLayout.addView(titleView1) titleLinerLayout.addView(titleView2) //Set maximum sliding distance titleLinerLayout.post { coordinatorScrollView.setMaxScrollY(titleLinerLayout.height) } //Used to dynamically add sub layouts after requesting a network Handler().postDelayed({ val titleView3 = getTitleView(screenWidth * 0.589F, R.mipmap.title3) titleLinerLayout.addView(titleView3) //Set the maximum sliding distance again titleLinerLayout.post { coordinatorScrollView.setMaxScrollY(titleLinerLayout.height) } }, 200) //Add TabLayout for (i in tabTextData.indices) { tabLayout.addTab(tabLayout.newTab()) tabLayout.getTabAt(i)!!.setText(tabTextData[i]).setIcon(tabIconData[i]) //Add Fragment fragmentData.add(TestFragment.newInstance(tabTextData[i])) } //Fragment ViewPager viewPager.adapter = ViewPagerAdapter(supportFragmentManager, fragmentData) //TabLayout associated with ViewPager tabLayout.setupWithViewPager(viewPager) //Set TabLayout data for (i in tabTextData.indices) { tabLayout.getTabAt(i)!!.setText(tabTextData[i]).setIcon(tabIconData[i]) } } /** * Get a title Layout * I'll simulate it with three pictures here * * @height Picture height to set */ 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 } }
Final effect
Source Github address: https://github.com/ThirdGoddess/ViewPager
Advanced notes of Android advanced development system and the latest interview review notes PDF, please scan the csdn official QR code below for free
end of document
Your favorite collection is my greatest encouragement!
Welcome to follow me, share Android dry goods and exchange Android technology.
If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!