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">
<LinearLayout
android:id="@+id/mLlFather"
android:layout_width="match_parent"
android:layout_height="70dp"
android:background="@color/black"
android:gravity="bottom">
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="1"
android:src="@mipmap/ic_launcher" />
<ImageView
android: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">
<LinearLayout
android:id="@+id/mLlFather"
android:layout_width="match_parent"
android:layout_height="70dp"
android:background="@color/black"
android:gravity="bottom">
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="1"
android:src="@mipmap/ic_launcher" />
<ImageView
android: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.3
set(value) {
field = value
refreshPageSize()
}
//为了保证画廊效果,可见的Page处理为单数
var visibleItem: Int = 1
set(value) {
field = if (value.rem(2) == 0) {
value - 1
} else {
value
}
refreshPageSize()
}
//刷新页面大小
private fun refreshPageSize() {
//使用post为了保证获取根布局width的时候结果不为0
mViewPager.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=false
LayoutInflater.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.SuperViewPager
android:id="@+id/mSuperViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:clipChildren="true" />
</LinearLayout>
onCreate中调用
mSuperViewPager.visibleItem = 3
mSuperViewPager.setAdapter(HomePagerAdapter(this))
我们就得到了这样的效果:step1
其次,我们需要设置每个页面Item的间距,ViewPager2和ViewPager不同,ViewPager使用setPageMargin,但是因为ViewPager2内部是RecyclerView,有类似addItemDecoration的功能,我们添加自带的MarginPageTransformer
mSuperViewPager.setPageTransformer(MarginPageTransformer(20))
mSuperViewPager.visibleItem = 3
mSuperViewPager.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.5f
private const val TARGET_SCALE = 0.8f
}
override fun transformPage(page: View, position: Float) {
if (position < -1 || position > 1) {
//当前页面左侧以及右侧的页面效果
page.alpha = TARGET_ALPHA
page.scaleX = TARGET_SCALE
page.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 = scale
page.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.3
set(value) {
field = value
refreshPageSize()
}
//为了保证画廊效果,可见的Page处理为单数
var visibleItem: Int = 1
set(value) {
field = if (value.rem(2) == 0) {
value - 1
} else {
value
}
refreshPageSize()
}
//刷新页面大小
private fun refreshPageSize() {
//使用post为了保证获取根布局width的时候结果不为0
mViewPager.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=false
LayoutInflater.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.SuperViewPager
android: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/
📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。
温馨提示:点击下方卡片获取更多意想不到的资源。