You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Kotlin中实现可拉伸头部(Stretchy Header)?

Kotlin 实现可拉伸头部(Stretchy Header)方案

可拉伸头部效果

下面是几种在Android(Kotlin)中实现这种拉伸头部的常用方案:

一、RecyclerView + 自定义Header 手动实现

1. 准备布局

  • 主布局(activity_main.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>
  • 拉伸头部布局(header_stretchy.xml):
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/stretchyHeader"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:scaleType="centerCrop"
    android:src="@drawable/your_header_image" />

2. 编写RecyclerView Adapter

给Adapter添加Header类型,区分普通条目和头部:

class StretchyAdapter(private val dataList: List<String>, private val headerView: View) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val TYPE_HEADER = 0
    private val TYPE_ITEM = 1

    override fun getItemViewType(position: Int): Int = if (position == 0) TYPE_HEADER else TYPE_ITEM

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (viewType == TYPE_HEADER) {
            HeaderViewHolder(headerView)
        } else {
            val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_normal, parent, false)
            ItemViewHolder(itemView)
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as? ItemViewHolder)?.let {
            it.tvContent.text = dataList[position - 1]
        }
    }

    override fun getItemCount(): Int = dataList.size + 1

    inner class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
    inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvContent: TextView = itemView.findViewById(R.id.tvContent)
    }
}

3. 监听滚动实现拉伸逻辑

在Activity中监听RecyclerView滚动,计算偏移量来缩放头部:

class MainActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var stretchyHeader: ImageView
    private var headerHeight = 0
    private val minHeaderHeight by lazy { resources.getDimensionPixelSize(R.dimen.min_header_height) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView = findViewById(R.id.recyclerView)
        val headerView = LayoutInflater.from(this).inflate(R.layout.header_stretchy, recyclerView, false)
        stretchyHeader = headerView.findViewById(R.id.stretchyHeader)

        // 获取头部初始高度
        headerView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                headerView.viewTreeObserver.removeOnGlobalLayoutListener(this)
                headerHeight = stretchyHeader.height
            }
        })

        // 设置RecyclerView数据
        val dataList = List(20) { "列表条目 $it" }
        recyclerView.adapter = StretchyAdapter(dataList, headerView)
        recyclerView.layoutManager = LinearLayoutManager(this)

        // 滚动监听实现拉伸
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val scrollY = recyclerView.computeVerticalScrollOffset()

                // 计算缩放比例和垂直位移
                val scale = 1f + (-scrollY).coerceAtLeast(0) / headerHeight.toFloat()
                var translateY = (-scrollY / 2).coerceAtMost(0).toFloat()

                // 当头部收缩到最小高度时固定位置
                if (scrollY >= headerHeight - minHeaderHeight) {
                    translateY = (minHeaderHeight - headerHeight).toFloat()
                }

                stretchyHeader.scaleX = scale
                stretchyHeader.scaleY = scale
                stretchyHeader.translationY = translateY
            }
        })
    }
}

二、NestedScrollView 快速实现

如果页面内容不是RecyclerView,用NestedScrollView更简单:

1. 布局文件(activity_nested.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/nestedScrollView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/stretchyHeader"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="centerCrop"
            android:src="@drawable/your_header_image" />

        <!-- 下方是页面内容,按需添加 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="16dp"
            android:text="内容条目 1" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="16dp"
            android:text="内容条目 2" />
        <!-- 更多内容... -->
    </LinearLayout>
</androidx.core.widget.NestedScrollView>

2. 滚动监听实现拉伸

class NestedScrollActivity : AppCompatActivity() {

    private lateinit var nestedScrollView: NestedScrollView
    private lateinit var stretchyHeader: ImageView
    private var headerHeight = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_nested)

        nestedScrollView = findViewById(R.id.nestedScrollView)
        stretchyHeader = findViewById(R.id.stretchyHeader)

        // 获取头部初始高度
        stretchyHeader.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                stretchyHeader.viewTreeObserver.removeOnGlobalLayoutListener(this)
                headerHeight = stretchyHeader.height
            }
        })

        // 滚动监听
        nestedScrollView.setOnScrollChangeListener { _, _, scrollY, _, _ ->
            val scale = 1f + (-scrollY).coerceAtLeast(0) / headerHeight.toFloat()
            val translateY = (-scrollY / 2).coerceAtMost(0).toFloat()

            stretchyHeader.scaleX = scale
            stretchyHeader.scaleY = scale
            stretchyHeader.translationY = translateY
        }
    }
}

三、第三方库简化开发

不想手动写逻辑的话,可以用现成的开源库,比如StretchyHeaderLayout:

  1. 添加依赖到build.gradle:
implementation 'com.github.florent37:stretchy-header-layout:1.0.3'
  1. 布局中使用:
<com.github.florent37.stretchyheader.StretchyHeaderLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 可拉伸的头部视图 -->
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"
        android:src="@drawable/your_header_image" />

    <!-- 滚动内容视图,比如RecyclerView或其他布局 -->
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.github.florent37.stretchyheader.StretchyHeaderLayout>

这样无需额外代码,就能自动实现头部拉伸效果。

内容的提问来源于stack exchange,提问作者Limun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 12:30:41