Android-自适用高度的ViewPager

news2024/11/13 12:50:19

需求

在项目中,我们常常遇到需要动态调整 ViewPager 的高度,以适应其内容大小的需求。默认情况下,ViewPager 的高度是固定的,无法根据每个页面的内容高度进行调整。这会导致在内容高度不一致时,出现不必要的空白区域或者内容被裁剪的情况。为了解决这个问题,我们设计了一个 AutoHeightViewPager,能够根据当前显示页面的内容高度动态调整自身的高度,保证内容完整且没有多余的空白。

效果

在这里插入图片描述
在这里插入图片描述

实现思路

1. 动态高度调整

首先,我们需要一个自定义的 ViewPager 类 AutoHeightViewPager,这个类可以根据当前页面的内容高度来动态调整自身的高度。通过重写 onMeasure 方法,可以在滑动过程中动态计算页面的高度并调整布局。

2. 监听页面滑动事件

为了平滑过渡,我们需要监听页面的滑动过程,并计算滑动比例,将当前页面的高度与下一个页面的高度按比例过渡,实现平滑过渡效果。

3. 自定义 Adapter

自定义的 PagerAdapter 必须实现一个接口 AutoHeightPager,用于获取指定位置页面的 View,这样我们可以测量页面内容的高度。

实现代码

1. AutoHeightViewPager

package com.yxlh.androidxy.demo.ui.viewpager.vp

import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager

interface AutoHeightPager {
    fun getView(position: Int): View?
}

class AutoHeightViewPager @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : ViewPager(context, attrs) {

    private var lastWidthMeasureSpec: Int = 0
    private var currentHeight: Int = 0
    private var lastPosition: Int = 0
    private var isScrolling: Boolean = false

    init {
        addOnPageChangeListener(object : SimpleOnPageChangeListener() {
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                if (positionOffset == 0f) {
                    isScrolling = false
                    requestLayout()
                    return
                }

                val srcPosition = if (position >= lastPosition) position else position + 1
                val destPosition = if (position >= lastPosition) position + 1 else position

                val srcHeight = getViewHeight(srcPosition)
                val destHeight = getViewHeight(destPosition)

                currentHeight = (srcHeight + (destHeight - srcHeight) *
                        if (position >= lastPosition) positionOffset else 1 - positionOffset).toInt()

                isScrolling = true
                requestLayout()
            }

            override fun onPageScrollStateChanged(state: Int) {
                if (state == SCROLL_STATE_IDLE) {
                    lastPosition = currentItem
                }
            }
        })
    }

    override fun setAdapter(adapter: PagerAdapter?) {
        require(adapter == null || adapter is AutoHeightPager) { "PagerAdapter must implement AutoHeightPager." }
        super.setAdapter(adapter)
    }

    private fun getViewHeight(position: Int): Int {
        val adapter = adapter as? AutoHeightPager ?: return 0

        return run {
            val view = adapter.getView(position) ?: return 0
            view.measure(
                lastWidthMeasureSpec,
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
            )
            view.measuredHeight
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        lastWidthMeasureSpec = widthMeasureSpec
        var heightSpec = heightMeasureSpec
        if (isScrolling) {
            heightSpec = MeasureSpec.makeMeasureSpec(currentHeight, MeasureSpec.EXACTLY)
        } else {
            getViewHeight(currentItem).takeIf { it > 0 }?.let {
                heightSpec = MeasureSpec.makeMeasureSpec(it, MeasureSpec.EXACTLY)
            }
        }
        super.onMeasure(widthMeasureSpec, heightSpec)
    }
}

2. AutoHeightPagerAdapter

package com.yxlh.androidxy.demo.ui.viewpager

import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightPager

class AutoHeightPagerAdapter : PagerAdapter(), AutoHeightPager {

    private val viewList = mutableListOf<View>()

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val view = viewList[position]
        val parent = view.parent as? ViewGroup
        parent?.removeView(view)
        container.addView(view)
        return view
    }

    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
        container.removeView(`object` as View)
    }

    fun setViews(views: List<View>) {
        viewList.clear()
        viewList.addAll(views)
        notifyDataSetChanged()
    }

    override fun getView(position: Int): View? {
        return viewList.getOrNull(position)
    }

    override fun getCount(): Int {
        return viewList.size
    }

    override fun isViewFromObject(view: View, `object`: Any): Boolean {
        return view == `object`
    }
}

3. Activity 代码

package com.yxlh.androidxy.demo.ui.viewpager

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.yxlh.androidxy.R
import com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightViewPager

class VpActivity : AppCompatActivity() {
    private var mAutoHeightVp: AutoHeightViewPager? = null

    private var mAdapter: AutoHeightPagerAdapter? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_vp)

        val viewList: MutableList<View> = ArrayList()
        viewList.add(LayoutInflater.from(this).inflate(R.layout.view_demo_1, null))
        viewList.add(LayoutInflater.from(this).inflate(R.layout.view_demo_2, null))

        mAutoHeightVp = findViewById(R.id.viewpager)
        mAutoHeightVp?.setAdapter(AutoHeightPagerAdapter().also { mAdapter = it })
        mAdapter?.setViews(viewList)
        mAutoHeightVp?.setCurrentItem(1)
    }
}

4. 布局 XML

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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">

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent">

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

            <com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightViewPager
                android:id="@+id/viewpager"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:scaleType="fitXY"
                android:src="@drawable/vp_content" />
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>

总结

通过自定义 ViewPager 的动态高度适配功能,我们可以解决内容高度不一致导致的布局问题。这种方案可以适应不同页面内容的高度变化,实现平滑的过渡效果,非常适用于动态内容展示的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2042637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cmake基础教程--第2章:打印信息和变量操作

Cmake基础教程--第2章&#xff1a;打印日志和变量操作 概述message打印日志打印一些CMake自带的信息 变量操作set操作list方法添加元素获取长度查找元素删除元素其他操作 概述 CMake项目时基于一个名为 CMakeLists.txt 的文件来构造的&#xff0c;注意大小写不能拼写错误。我们…

线程回收以及线程的问题处理

一、线程结束 1.1、pthread_exit 本身表示结束线程 如果用在main函数中 表示结束主线程 主线程结束并不表示进程 此时执行逻辑&#xff0c;主线程执行流结束&#xff0c;进程会在其余线程都结束后&#xff0c;结束 1.2、return 从线程中返回 线程执行函数执行结束&#x…

让回忆鲜活如初:13.3寸彩色墨水屏电子相框震撼上市

在这个数字化时代&#xff0c;我们习惯了在社交媒体上分享生活的点滴&#xff0c;但那些珍贵的记忆是否也能以更直观的方式呈现&#xff1f;近日&#xff0c;一款全新的13.3寸彩色墨水屏电子相框正式上市&#xff0c;它将以独特的方式让您的回忆鲜活如初。 高清彩色墨水屏&…

三维建模软件:地理信息与遥感领域的智慧构建者

在地理信息与遥感技术的广阔舞台中&#xff0c;建模软件如同一位卓越的建筑师&#xff0c;以数据为砖瓦&#xff0c;智慧为水泥&#xff0c;构建出一个又一个又一个逼真、动态的虚拟世界。本文将深入探究其技术核心、应用实例、未来趋势&#xff0c;揭示建模软件如何在地理信息…

论文干货|AI一键生成论文的AI工具!附使用攻略!速速码住!

在当前的学术研究和写作环境中&#xff0c;AI技术的应用已经变得越来越普遍。特别是在论文写作方面&#xff0c;许多学生和研究人员都在寻找能够提高效率、简化流程的工具。千笔-AIPassPaPer是一款备受推荐的AI论文生成工具&#xff0c;它不仅功能全面&#xff0c;而且用户体验…

Windows、Ubuntu安装mysql

今天我们来学习一下如何在Windows、Ubuntu安装mysql。 Windows安装mysql 第一步&#xff1a;在官网找到需要安装的mysql版本&#xff0c;下载 第二步&#xff1a;下载后打开安装包&#xff0c;进行安装。 点击 “Next”: 默认就行&#xff0c;单击next: 单击“Excute” 等…

Postgresql导入矢量数据

前期准备 工具&#xff1a;PgAdmin&#xff0c;postgis-bundle Postgres安装和postgis安装可以百度别的教程。 创建数据库添加扩展 如图&#xff0c;使用PgAdmin创建名为shp的数据库&#xff0c;并在扩展item中添加postgis扩展。 添加扩展方法可以用查询工具输入以下sql语句&…

贪吃蛇(C语言详解)

贪吃蛇游戏运行画面-CSDN直播 目录 贪吃蛇游戏运行画面-CSDN直播 1. 实验目标 2. Win32 API介绍 2.1 Win32 API 2.2 控制台程序&#xff08;Console&#xff09; 2.3 控制台屏幕上的坐标COORD 2.4 GetStdHandle 2.5 GetConsoleCursorlnfo 2.5.1 CONSOLE_CURSOR_INFO …

免费通配符泛域名SSL证书全自动申请、更新、续期、部署,支持部署到阿里云、腾讯云、ssh主机

CertD&#xff1a;全自动SSL证书管理平台 CertD是一款创新性的开源工具&#xff0c;专注于提供免费且全自动化的SSL证书申请及更新服务。它的命名灵感来源于Linux守护进程的命名方式&#xff0c;“D”代表证书守护进程&#xff08;Certificate Daemon&#xff09;&#xff0c;…

SpringBoot-读取配置文件内容

目录 前言 主页&#xff08;端口号默认8080&#xff09; 1 Value 注解 引用变量的使用 2 Environment 对象 3 ConfigurationProperties &#xff08;配置内容和对象&#xff0c;进行相互绑定&#xff09; 前言 读取配置文件有3 种方式 (1) Value注解 (2) Environm…

基于springboot的网上服装商城

TOC springboot182基于springboot的网上服装商城 第一章 课题背景及研究内容 1.1 课题背景 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性…

C++竞赛初阶L1-11-第五单元-for循环(25~26课)514: T454425 奥运奖牌计数

题目内容 2008 年北京奥运会&#xff0c;A 国的运动员参与了 n 天的决赛项目 (1≤n≤100)。现在要统计一下 A 国所获得的金、银、铜牌数目及总奖牌数。输入第 1 行是 A 国参与决赛项目的天数 n&#xff0c;其后 n 行&#xff0c;每一行是该国某一天获得的金、银、铜牌数目&…

C++ stack与queue的使用与简单实现

目录 0. 适配器 1. stack的简要介绍 2. stack的简单使用 3. queue的简要介绍 4. queue的简单使用 STL标准库中stack和queue的底层结构 deque简单介绍 5. stack的模拟实现 6. queue的模拟实现 0. 适配器 在文章开始前我们先了解一下适配器的概念 适配器是一种设计模式(设计…

【第二节】80x86汇编-寄存器和标志位

目录 前言 一、汇编相关概念 1.1 数据表示与类型 1.2 汇编语言的构成 1.3 存储器及指令、数据 1.4 存储单元 1.5 CPU对存储器的读写操作 1.6 CPU读写内存单元的过程 1.7 intel CPU发展 1.8 8086 内部结构 二、寄存器 2.1 寄存器概览 2.2 32位寄存器 2.3 16位寄存器…

浅谈C语言预处理

文章目录 预处理1、预定义符号2、#define定义标识符和宏A、#define定义标识符B、#define定义宏a、宏的定义b、宏的使用c、宏和函数 3、条件编译4、头文件包含A、两种包含形式B、防止头文件被重复包含 预处理 什么是预处理&#xff1f;预处理是C语言编译的三个过程&#xff08;…

一口气把halcon的所有运算符说清楚

halcon的运算符大体分以下几类 一&#xff1a;赋值运算符&#xff1a; (1) 赋值(:)&#xff08;左边的赋值给右边&#xff09; 二&#xff1a;算术运算符 (1)加()、减(-)、乘(*)、除(/)、求余(%) 三&#xff1a;关系运算符 (3)#、&#xff01; 不等于 四&#xff1a;逻辑运…

java中运算符的详细知识点

算数运算符 a 先赋值再加1 a 先加1在赋值 --的道理是一样的 赋值运算符 1. - * / % 当两侧数据类型不一致时&#xff0c;可以使用自动类型转换或使用 强制类型转换原则 支持连续赋值 - * / % 不会改变基础类型 测试一下&#xff1a; 比较运算符 运算结果为布尔类型 &#x…

Windows上安装WSL,学习Linux

1. 什么是WSL 先说大白话WSL就是让Windows不安装虚拟机可以额外拥有Linux操作系统&#xff0c;以供学习和测试 WSL&#xff08;Windows Subsystem for Linux&#xff09;和WSL2 是微软推出的两个工具&#xff0c;旨在让用户能够在 Windows 操作系统上运行 Linux 的命令行工具…

MySQL数据分析进阶(十四)保护数据库

※食用指南&#xff1a;文章内容为‘CodeWithMosh’SQL进阶教程系列学习笔记&#xff0c;笔记整理比较粗糙&#xff0c;主要目的自存为主&#xff0c;记录完整的学习过程。&#xff08;图片超级多&#xff0c;慎看&#xff01;&#xff09; 【中字】SQL进阶教程 | 史上最易懂S…

Unity游戏开发004:如何在Unity中对物体进行基本操作

Unity游戏开发 “好读书&#xff0c;不求甚解&#xff1b;每有会意&#xff0c;便欣然忘食。” 本文目录&#xff1a; Unity游戏开发 Unity游戏开发前言左侧工具栏概述1. **创建物体**2. **移动&#xff08;Move&#xff09;**3. **旋转&#xff08;Rotate&#xff09;**4. **缩…