Android Toolbar双折叠状态实现:仿状态栏滑动展开技术问询
Got it, let's build this two-state Toolbar that mimics the system status bar's behavior—first expanding to a compact view with your essential buttons when you start scrolling down from the top, then fully opening up to show all content on further scrolling. We'll use CollapsingToolbarLayout paired with NestedScrollView for seamless scroll interactions.
1. Layout Setup
First, let's define the layout structure. We'll use CoordinatorLayout as the root (required for scroll-driven UI interactions), then nest an AppBarLayout and CollapsingToolbarLayout to handle the Toolbar's multi-stage expansion. We'll explicitly define three Toolbar states:
- Collapsed: Minimal height matching the system status bar
- Compact: Standard action bar height with your go-to buttons
- Expanded: Full height with extra content/titles
<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" app:elevation="0dp"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!-- Full expanded content (hidden initially) --> <LinearLayout android:id="@+id/full_toolbar_content" android:layout_width="match_parent" android:layout_height="120dp" android:orientation="vertical" android:padding="16dp" android:visibility="gone"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Full Toolbar Title" android:textSize="24sp" android:textStyle="bold"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:gravity="end" android:orientation="horizontal"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Extra Action 1"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="Extra Action 2"/> </LinearLayout> </LinearLayout> <!-- Compact toolbar view (shown after first scroll) --> <androidx.appcompat.widget.Toolbar android:id="@+id/compact_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/primary" app:layout_collapseMode="pin"> <!-- Add your essential action buttons here --> <ImageView android:id="@+id/btn_home" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_home"/> <ImageView android:id="@+id/btn_search" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:src="@drawable/ic_search"/> </androidx.appcompat.widget.Toolbar> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <!-- Scrollable main content --> <androidx.core.widget.NestedScrollView android:id="@+id/scroll_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Scroll down to expand the toolbar..." android:textSize="18sp"/> <!-- Add long dummy content to enable scrolling --> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@string/lorem_ipsum_long"/> </LinearLayout> </androidx.core.widget.NestedScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout>
2. Scroll Logic Implementation
Next, we'll listen to the NestedScrollView's scroll events to handle transitions between the three states. We'll calculate key heights upfront, then update the Toolbar's height and visibility based on scroll direction and distance.
import android.os.Bundle import android.view.View import android.view.ViewTreeObserver import androidx.appcompat.app.AppCompatActivity import androidx.core.widget.NestedScrollView import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.CollapsingToolbarLayout class TwoStageToolbarActivity : AppCompatActivity() { private lateinit var appBarLayout: AppBarLayout private lateinit var collapsingToolbar: CollapsingToolbarLayout private lateinit var compactToolbar: androidx.appcompat.widget.Toolbar private lateinit var fullToolbarContent: View private lateinit var scrollView: NestedScrollView private var statusBarHeight = 0 private var compactHeight = 0 private var expandedHeight = 0 // Track current state: 0 = collapsed, 1 = compact, 2 = expanded private var currentState = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_two_stage_toolbar) initViews() calculateHeights() setupScrollListener() // Start with fully collapsed state (only status bar visible) setToolbarHeight(statusBarHeight) compactToolbar.visibility = View.GONE } private fun initViews() { appBarLayout = findViewById(R.id.app_bar_layout) collapsingToolbar = findViewById(R.id.collapsing_toolbar) compactToolbar = findViewById(R.id.compact_toolbar) fullToolbarContent = findViewById(R.id.full_toolbar_content) scrollView = findViewById(R.id.scroll_view) } private fun calculateHeights() { // Get system status bar height val statusBarResId = resources.getIdentifier("status_bar_height", "dimen", "android") if (statusBarResId > 0) { statusBarHeight = resources.getDimensionPixelSize(statusBarResId) } // Get compact toolbar height (standard action bar size) compactHeight = resources.getDimensionPixelSize(R.dimen.abc_action_bar_default_height_material) // Get expanded toolbar height after the view is laid out fullToolbarContent.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { expandedHeight = fullToolbarContent.height fullToolbarContent.viewTreeObserver.removeOnGlobalLayoutListener(this) } }) } private fun setupScrollListener() { scrollView.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> val isScrollingDown = scrollY > oldScrollY // Downward scroll = content moves up, scrollY increases when (currentState) { 0 -> { // Collapsed → Compact if (isScrollingDown && scrollY > 50) { // Trigger after small downward scroll setToolbarHeight(compactHeight) compactToolbar.visibility = View.VISIBLE currentState = 1 } } 1 -> { // Compact → Expanded or back to Collapsed if (isScrollingDown && scrollY > 200) { // Further scroll to expand fully setToolbarHeight(expandedHeight) fullToolbarContent.visibility = View.VISIBLE currentState = 2 } else if (!isScrollingDown && scrollY <= 50) { // Scroll up to collapse setToolbarHeight(statusBarHeight) compactToolbar.visibility = View.GONE currentState = 0 } } 2 -> { // Expanded → Compact if (!isScrollingDown && scrollY <= 200) { // Scroll up to return to compact view setToolbarHeight(compactHeight) fullToolbarContent.visibility = View.GONE currentState = 1 } } } }) } private fun setToolbarHeight(height: Int) { val params = collapsingToolbar.layoutParams as AppBarLayout.LayoutParams params.height = height collapsingToolbar.layoutParams = params } }
3. Polishing & Tweaks
- Smooth Animations: Replace the direct
setToolbarHeight()calls with aValueAnimatorfor smoother transitions:private fun animateToolbarHeight(fromHeight: Int, toHeight: Int) { val animator = android.animation.ValueAnimator.ofInt(fromHeight, toHeight) animator.duration = 300 animator.addUpdateListener { animation -> val params = collapsingToolbar.layoutParams as AppBarLayout.LayoutParams params.height = animation.animatedValue as Int collapsingToolbar.layoutParams = params } animator.start() } - Adjust Scroll Thresholds: Tweak the
scrollYvalues (50, 200) to make transitions feel natural for your content. - State Persistence: Save
currentStateinonSaveInstanceState()and restore it inonCreate()to retain state across screen rotations. - Short Content Handling: If your main content isn't long enough to scroll, add logic to disable transitions or force the compact/expanded state.
内容的提问来源于stack exchange,提问作者asad

