Android修行手册 - 使用ViewPager2实现画廊效果

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总
游戏脚本-辅助自动化Android控件全解手册再战Android系列
Scratch编程案例软考全系列Unity3D学习专栏
蓝桥系列ChatGPT和AIGC

👉关于作者

专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)
有什么需要欢迎底部卡片私我,交流让学习不再孤单

在这里插入图片描述

👉实践过程

最近想试试用ViewPager2来实现画廊的效果,ViewPager2和ViewPager在API上有的地方不同,ViewPager2是通过内部嵌套一个RecyclerView来实现的

😜效果

ViewPager2初始化的部分代码

private void initialize(Context context, AttributeSet attrs) {...mRecyclerView = new RecyclerViewImpl(context);mRecyclerView.setId(ViewCompat.generateViewId());mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);mLayoutManager = new LinearLayoutManagerImpl(context);mRecyclerView.setLayoutManager(mLayoutManager);...attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
}

这是实现之后的效果
在这里插入图片描述

实现画廊效果首先我们要考虑的是,如何让ViewPager2同时显示多个页面Item

clipChildren
我们知道,在Android中,布局中的控件超出父布局的大小部分不会被绘制,但是当clipChildren设置为false时,子View的内容可以超出父布局被绘制出来。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/mLlRoot"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/material_on_background_disabled"android:gravity="bottom"android:orientation="horizontal"><LinearLayoutandroid:id="@+id/mLlFather"android:layout_width="match_parent"android:layout_height="70dp"android:background="@color/black"android:gravity="bottom"><ImageViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:src="@mipmap/ic_launcher" /><ImageViewandroid:layout_width="0dp"android:layout_height="100dp"android:layout_weight="1"android:src="@mipmap/ic_launcher" /><ImageViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:src="@mipmap/ic_launcher" /></LinearLayout>
</LinearLayout>

当前没有设置根布局LinearLayout(mLlRoot) 的clipChildren属性,黑色部分为ImageView的父布局,clipChildren默认为true,界面的效果为:

可以看出,中间ImageView限制在了它的父布局中,此时我们修改clipChildren为false

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/mLlRoot"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/material_on_background_disabled"android:gravity="bottom"android:clipChildren="false"android:orientation="horizontal"><LinearLayoutandroid:id="@+id/mLlFather"android:layout_width="match_parent"android:layout_height="70dp"android:background="@color/black"android:gravity="bottom"><ImageViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:src="@mipmap/ic_launcher" /><ImageViewandroid:layout_width="0dp"android:layout_height="100dp"android:layout_weight="1"android:src="@mipmap/ic_launcher" /><ImageViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:src="@mipmap/ic_launcher" /></LinearLayout>
</LinearLayout>

界面效果为:


可以看出,ImageView超出了它的父布局绘制出了剩余的部分,由此如果一个ViewPager2要显示多个Item,我们可以这样,给ViewPager左边和右边设置一个margin、固定ViewPager大小,或者根据想要显示的Item个数动态计算ViewPager的大小,然后设置clipChildren=false,允许ViewPager中看不到的界面绘制出来。

😜思路

由此我将ViewPager2封装了一下,目的只是为了给ViewPager2套一层父布局,方便使用

class SuperViewPager : RelativeLayout {val mViewPager: ViewPager2 by lazy {findViewById<ViewPager2>(R.id.mViewPager)}//自己定义了一个比率,来调整画廊效果最左侧和最右侧占用的宽度var edgeRatio = 0.3set(value) {field = valuerefreshPageSize()}//为了保证画廊效果,可见的Page处理为单数var visibleItem: Int = 1set(value) {field = if (value.rem(2) == 0) {value - 1} else {value}refreshPageSize()}//刷新页面大小private fun refreshPageSize() {//使用post为了保证获取根布局width的时候结果不为0mViewPager.post {mViewPager.offscreenPageLimit = visibleItem//根据想要显示的页面个数,动态给ViewPager2计算一个大小val mPageWidth = if (visibleItem == 1) {width} else {width.toDouble().div(visibleItem.minus(2).plus(edgeRatio)).toInt()}mViewPager.layoutParams = LayoutParams(LayoutParams(mPageWidth,ViewGroup.LayoutParams.MATCH_PARENT).apply { gravity = Gravity.CENTER })}}constructor(context: Context?) : super(context)constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr)init {clipChildren=falseLayoutInflater.from(context).inflate(R.layout.super_viewpager_layout, this, true)}/*** 为ViewPager2设置一个适配器,ViewPager2的适配器不再是PagerAdapter,而是RecyclerView.Adapter类型*/fun setAdapter(adapter: RecyclerView.Adapter<*>) {mViewPager.adapter = adapter}/*** 设置页面切换的效果*/fun setPageTransformer(pageTransformer: ViewPager2.PageTransformer) {mViewPager.setPageTransformer(pageTransformer)}}

然后我们要为ViewPager2设置一个适配器,因为我这里是用Fragment作为单页内容来实现的多页面效果

class HomePagerAdapter(fragmentActivity: FragmentActivity) :FragmentStateAdapter(fragmentActivity) {override fun getItemCount(): Int {return 3}override fun createFragment(position: Int): Fragment {return SimpleFragment()}
}

关于ViewPager以及Adapter的正确使用方式,这里推荐看一下鸿神的一篇博客,讲的很详细:https://mp.weixin.qq.com/s/MOWdbI5IREjQP1Px-WJY1Q
最后在Activity中使用xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><com.utils.core.weight.viewpager.SuperViewPagerandroid:id="@+id/mSuperViewPager"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/black"android:clipChildren="true" />
</LinearLayout>

onCreate中调用

 mSuperViewPager.visibleItem = 3mSuperViewPager.setAdapter(HomePagerAdapter(this))

我们就得到了这样的效果:step1
在这里插入图片描述

其次,我们需要设置每个页面Item的间距,ViewPager2和ViewPager不同,ViewPager使用setPageMargin,但是因为ViewPager2内部是RecyclerView,有类似addItemDecoration的功能,我们添加自带的MarginPageTransformer

 mSuperViewPager.setPageTransformer(MarginPageTransformer(20))mSuperViewPager.visibleItem = 3mSuperViewPager.setAdapter(HomePagerAdapter(this))

就实现了这样的效果:step2
在这里插入图片描述

然后我们还要为ViewPager2添加一个画廊缩放的效果,ViewPager2的页面切换效果是通过PageTransformer实现的

public interface PageTransformer {/*** Apply a property transformation to the given page.** @param page 当前页的View* @param 代表当前页面值和一个滑动距离的数值,在当前手机屏幕能看到的页面永远为0,往左递减,往右递增*/void transformPage(@NonNull View page, float position);}

由此,我们实现PageTransformer,除去position=0(当前页面),其他页面设置一个默认效果,透明度0.5,缩放0.9,然后为页面由非0到0,以及0到非0设置一个过渡。

class GalleryTransformer : ViewPager2.PageTransformer {companion object {private const val TARGET_ALPHA = 0.5fprivate const val TARGET_SCALE = 0.8f}override fun transformPage(page: View, position: Float) {if (position < -1 || position > 1) {//当前页面左侧以及右侧的页面效果page.alpha = TARGET_ALPHApage.scaleX = TARGET_SCALEpage.scaleY = TARGET_SCALE} else {//从不可见变为可见效果//透明度效果if (position <= 0) {page.alpha =TARGET_ALPHA + TARGET_ALPHA * (1 + position)} else {page.alpha =TARGET_ALPHA + TARGET_ALPHA * (1 - position)}//缩放效果val scale = Math.max(TARGET_SCALE, 1 - Math.abs(position))page.scaleX = scalepage.scaleY = scale}}
}

最后在Activity设置PageTransformer,目前我们已经为ViewPager2设置过一个PageTransformer了,ViewPager2为我们提供了CompositePageTransformer,可以同时设置多个PageTransformer如下:

mSuperViewPager.setPageTransformer(CompositePageTransformer().apply {addTransformer(GalleryTransformer())addTransformer(MarginPageTransformer(20))
})

最后就实现了如下效果:step3
在这里插入图片描述

目前我们看似完成了期望效果,但目前有小伙伴应该发现因为我们设置了ViewPager的宽度是没有填满根布局的,过渡滑动的效果很影响美感,我们第一反应肯定实在xml中加入android:overScrollMode=“never”

<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/mViewPager"android:clipChildren="false"android:layout_width="match_parent"android:overScrollMode="never"android:layout_height="match_parent">
</androidx.viewpager2.widget.ViewPager2>

再次运行效果如下:step4
在这里插入图片描述

并没有解决这个问题,因为ViewPager2内部并没有对overScrollMode进行处理,并且内部使用RecyclerView实现的,RecyclerView是ViewPager2的第一个子View,由此我们在SuperViewPager中加入

val mViewPager: ViewPager2 by lazy {findViewById<ViewPager2>(R.id.mViewPager).apply {//设置关闭过度滑动的效果getChildAt(0).overScrollMode = View.OVER_SCROLL_NEVER}}

再次运行,过渡滑动的效果就被去除了:step5
在这里插入图片描述
到这里,我们看似完成了一切的工作,但是目前有这样一个问题

经过多次试验,我用这种方式解决了这个问题,讲跟布局的Touch事件直接传递给ViewPager中的RecyclerView,在SuperViewPager中添加

override fun onTouchEvent(event: MotionEvent?): Boolean {return mViewPager.getChildAt(0).onTouchEvent(event)
}

到这,达到了我们期望的效果,下面是SuperViewPager完整代码

class SuperViewPager : RelativeLayout {val mViewPager: ViewPager2 by lazy {findViewById<ViewPager2>(R.id.mViewPager).apply {//设置关闭过度滑动的效果getChildAt(0).overScrollMode = View.OVER_SCROLL_NEVER}}//自己定义了一个比率,来调整画廊效果最左侧和最右侧占用的宽度var edgeRatio = 0.3set(value) {field = valuerefreshPageSize()}//为了保证画廊效果,可见的Page处理为单数var visibleItem: Int = 1set(value) {field = if (value.rem(2) == 0) {value - 1} else {value}refreshPageSize()}//刷新页面大小private fun refreshPageSize() {//使用post为了保证获取根布局width的时候结果不为0mViewPager.post {mViewPager.offscreenPageLimit = visibleItem//根据想要显示的页面个数,动态给ViewPager2计算一个大小val mPageWidth = if (visibleItem == 1) {width} else {width.toDouble().div(visibleItem.minus(2).plus(edgeRatio)).toInt()}mViewPager.layoutParams = LayoutParams(LayoutParams(mPageWidth,ViewGroup.LayoutParams.MATCH_PARENT).apply { gravity = Gravity.CENTER })}}/*** 将根布局的触摸事件直接传递给ViewPager*/@SuppressLint("ClickableViewAccessibility")override fun onTouchEvent(event: MotionEvent?): Boolean {return mViewPager.getChildAt(0).onTouchEvent(event)}constructor(context: Context?) : super(context)constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr)init {clipChildren=falseLayoutInflater.from(context).inflate(R.layout.super_viewpager_layout, this, true)}/*** 为ViewPager2设置一个适配器,ViewPager2的适配器不再是PagerAdapter,而是RecyclerView.Adapter类型*/fun setAdapter(adapter: RecyclerView.Adapter<*>) {mViewPager.adapter = adapter}/*** 设置页面切换的效果*/fun setPageTransformer(pageTransformer: ViewPager2.PageTransformer) {mViewPager.setPageTransformer(pageTransformer)}
}

调用时

  <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><com.utils.core.weight.viewpager.SuperViewPagerandroid:id="@+id/mSuperViewPager"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/black" /></LinearLayout>
mSuperViewPager.setPageTransformer(CompositePageTransformer().apply {addTransformer(GalleryTransformer())addTransformer(MarginPageTransformer(20))
})
mSuperViewPager.visibleItem = 3
mSuperViewPager.setAdapter(HomePagerAdapter(this))

😜遗留的问题

  • 有心的小伙伴可以发现,step1中,ViewPager2多页面的情况下,页面切换时,边缘的页面会出现闪动,目前还没发现什么原因。

  • 在SuperViewPager的layout布局中,我为ViewPager2设置了android:clipChildren=“false”,然后在初始化SuperViewPager,我为根布局也设置了clipChildren=false, 我搜了下资料,因为ViewPager2 设置android:clipChildren="false"是为了使得内部的View突破限制显示,根布局再设置一次是为了承载页面的ViewPager2 能突破限制,所以要设置两次,但目前我在上面讲clipChildren的时候,根LinearLayout嵌套了一个子LinearLayout,在子LinearLayout中添加的ImageView,我只在根LinearLayout设置了android:clipChildren=“false”,就实现了我想要的效果,不知道这里是为何,是因为ViewPager2 内部是RecyclerView吗?

  • 在处理多页面边缘手势事件时,我一开始使用的方法是

override fun onTouchEvent(event: MotionEvent?): Boolean {return mViewPager.dispatchTouchEvent(event)
}

将事件分发给内部的ViewPager,但是出现一个问题

我又仔细看了一次View,ViewGroup的事件分发机制的,但是按理说左边已经响应的话,右边也应该响应,由于Android 11 的API ViewGroup这块 dispatchTouchEvent内容有点多,打断点由于使用的API和手机版本不同也没找到原因。有没有小伙伴清楚这个问题出现的原因能够分享一下

👉其他

📢作者:小空和小芝中的小空
📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。

温馨提示点击下方卡片获取更多意想不到的资源。
空名先生

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

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

相关文章

小航助学题库蓝桥杯题库stem选拔赛(22年3月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

解决ansible批量加入新IP涉及known_hosts报错的问题

我们把一批新的IP加入到ansible的hosts文件&#xff0c;比如/etc/ansible/hosts&#xff0c;往往会有这样的提示&#xff0c; 因为本机的~/.ssh/known_hosts文件中并有fingerprint key串&#xff0c;使用ssh连接目标主机时&#xff0c;一般会提示是否将key字符串加入到~/.ssh/…

如何使用内网穿透将Tomcat网页发布到公共互联网上【内网穿透】

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

中国北斗:守护萨雷兹湖一方安澜

中国北斗&#xff1a;守护萨雷兹湖一方安澜 在第三届“一带一路”国际合作高峰论坛数字经济高级别论坛上&#xff0c;由中国经济信息社、国家发展改革委高技术司、国家数据局联合编制的《数字“慧”就发展之路》中英文图文集正式发布&#xff0c;展现了中国与共建“一带一路”国…

PHP中关于func_get_args()方法

首先呢这个函数出现的是比较早的,大致应该是PHP4出现的, func_get_args — 返回一个包含函数参数列表的数组 说明 func_get_args(): array 获取函数参数列表的数组。 该函数可以配合 func_get_arg() 和 func_num_args() 一起使用&#xff0c;从而使得用户自定义函数可以接…

如何将mobi、awz3、epub格式转化为pdf

偶然之间有个需求就是网上下载了一些书籍的格式没法打开看&#xff0c;或者是想把kindle的书籍转换成pdf 那么经过一番折腾找到了两个可以用的工具站分享给大家&#xff0c;有需要的可是尝试下&#xff0c;小编这边测试了可以用&#xff0c;就是下载的时候慢的一匹。。。 第一…

PHP 双门双向门禁控制板实时监控源码

本示例使用设备&#xff1a; 实时网络双门双向门禁控制板可二次编程控制网络继电器远程开关-淘宝网 (taobao.com) <?PHPheader("content-type:text/html;charsetGBK");$ThisIpget_local_ip(); //获取电脑IP地址 $server udp://.$ThisIp.:39192; $sock…

MATLAB中fft与fftshift的区别

两者的区别在于&#xff1a; fft函数将时域信号转换为频域信号&#xff0c;即将信号从时间域转换为频率域。fftshift函数用于对fft计算结果进行移位操作&#xff0c;将频域信号的零频率分量移到频谱的中心&#xff0c;方便观察和处理。fftshift函数将fft计 算结果沿着中心点进…

MySQL--InnoDB引擎

InnoDB引擎 逻辑存储引擎 表空间→段→区→页→行 Tablespace 表空间&#xff08;ibd文件&#xff09;&#xff1a;一个mysql实例可以对应多个表空间&#xff0c;用于存储记录、索引等数据Segment 段&#xff1a;段分为数据段、索引段、回滚段&#xff0c;InnoDB是索引组织表…

【Unity程序技巧】加入缓存池存储地图资源,节省资源,避免多次CG

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

Python语言学习笔记之二(基础语法)

本课程对于有其它语言基础的开发人员可以参考和学习&#xff0c;同时也是记录下来&#xff0c;为个人学习使用&#xff0c;文档中有此不当之处&#xff0c;请谅解。 Python几种字符串的表示&#xff1a; 在Python中&#xff0c;字符串是一种基本的数据类型&#xff0c;可以使…

ChatGPT等模型:到2026年,将消耗尽高质量训练数据

《麻省理工技术评论》曾在官网发表文章表示&#xff0c;随着ChatGPT等大模型的持续火热&#xff0c;对训练数据的需求越来越大。大模型就像是一个“网络黑洞”不断地吸收&#xff0c;最终会导致没有足够的数据进行训练。 而知名AI研究机构Epochai直接针对数据训练问题发表了一…

Python Subprocess教程:创建和管理子进程的完整指南

更多Python学习内容&#xff1a;ipengtao.com 在Python中&#xff0c;Subprocess模块为我们提供了强大的工具&#xff0c;使得创建和管理子进程变得十分便捷。本文将深入探讨Subprocess的各种功能和用法&#xff0c;通过丰富的示例代码&#xff0c;带你领略其强大的子进程管理能…

s_v_web_id或fp协议过签名,dy滑块

某音s_web_id或fp协议过签名 ‘h5_sdk_version’, ‘2.36.0’ "search_impr":{"entity_id":"1135137973613200"},"link_item_list":null,"user_permissions":null,"offline_info_list":null,"is_cf":…

迁移redis数据库中的数据到另一台服务器

方案一 下面我使用的redis是用docker安装的&#xff0c;不是通过下载安装包安装的&#xff0c;所以和我安装方式不一样的小伙伴可以不看&#xff0c;因为很多操作是基于docker的 话不多说&#xff0c;直接开搞&#xff01; 1.首先一定要确保两台服务器上面的redis版本要一致…

C++:OJ练习(每日练习系列)

编程题&#xff1a; 题一&#xff1a;把字符串转换成整数 把字符串转换成整数_牛客题霸_牛客网 示例1 输入&#xff1a; "2147483647" 返回值&#xff1a; 2147483647思路一&#xff1a; 第一步&#xff1a;it从str的第一个字符开始遍历&#xff0c;定义一个最后输…

使用 SIEM 管理安全事件

每家公司都必须处理检测、管理和解决安全事件&#xff0c;未能制定事件响应计划可能会对任何组织产生重大的影响&#xff0c;无论是在财务损失还是声誉损害方面。本文探讨了事件响应的重要性、检测和管理事件的关键要素&#xff0c;以及帮助组织处理安全事件的最佳实践。 安全…

感染了后缀为.404mckay-V-XXXXXXXX勒索病毒如何应对?数据能够恢复吗?

导言&#xff1a; 近年来&#xff0c;网络安全威胁日益严峻&#xff0c;其中之一便是V系列的勒索病毒之.404mckay-V-XXXXXXXX勒索病毒和.ad3for-V-XXXXXXXX勒索病毒。本文将深入介绍这一威胁的特点、感染方式&#xff0c;并提供详尽的数据恢复方法和有效的预防措施&#xff0c…

基于IDEA+SpringBoot+Mysql开发的在线考试系统

基于springboot的在线考试系统 项目介绍&#x1f481;&#x1f3fb; 项目背景&#xff1a; 随着互联网的普及和技术的发展&#xff0c;传统的考试方式已经无法满足人们的需求。为了提高考试的效率和准确性&#xff0c;我们决定开发一个在线考试系统。该系统将提供登录、试卷列表…

lua完整学习笔记

lua注释 &#xff0d;&#xff0d; 单行注释 &#xff0d;&#xff0d;[[ 多行注释 ]]-- lua数据结构 nil 无效值与Java的Null类似&#xff0c;但是在条件表示中是false boolean 布尔值&#xff0c;ture或者false number 双精度类型的浮点数 string 字…