Cool ~ RecyclerView parallax decorator ParallaxDecoration

Posted by lisa3711 on Sat, 15 Jan 2022 01:00:39 +0100

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/ThirdGodde...

Advanced notes of Android advanced development system, latest interview review notes PDF, My GitHub

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!

Topics: Android