制作网站的程序,杭州网站建设公司有哪些,想看装修效果图在哪里看,杭州网站优化培训Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC
#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧#xff0c;以及各种资源分…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 version1.0 encodingutf-8?
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:idid/mLlRootandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:backgroundcolor/material_on_background_disabledandroid:gravitybottomandroid:orientationhorizontalLinearLayoutandroid:idid/mLlFatherandroid:layout_widthmatch_parentandroid:layout_height70dpandroid:backgroundcolor/blackandroid:gravitybottomImageViewandroid:layout_width0dpandroid:layout_heightwrap_contentandroid:layout_weight1android:srcmipmap/ic_launcher /ImageViewandroid:layout_width0dpandroid:layout_height100dpandroid:layout_weight1android:srcmipmap/ic_launcher /ImageViewandroid:layout_width0dpandroid:layout_heightwrap_contentandroid:layout_weight1android:srcmipmap/ic_launcher //LinearLayout
/LinearLayout当前没有设置根布局LinearLayoutmLlRoot 的clipChildren属性黑色部分为ImageView的父布局clipChildren默认为true界面的效果为 可以看出中间ImageView限制在了它的父布局中此时我们修改clipChildren为false
?xml version1.0 encodingutf-8?
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:idid/mLlRootandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:backgroundcolor/material_on_background_disabledandroid:gravitybottomandroid:clipChildrenfalseandroid:orientationhorizontalLinearLayoutandroid:idid/mLlFatherandroid:layout_widthmatch_parentandroid:layout_height70dpandroid:backgroundcolor/blackandroid:gravitybottomImageViewandroid:layout_width0dpandroid:layout_heightwrap_contentandroid:layout_weight1android:srcmipmap/ic_launcher /ImageViewandroid:layout_width0dpandroid:layout_height100dpandroid:layout_weight1android:srcmipmap/ic_launcher /ImageViewandroid:layout_width0dpandroid:layout_heightwrap_contentandroid:layout_weight1android:srcmipmap/ic_launcher //LinearLayout
/LinearLayout界面效果为 可以看出ImageView超出了它的父布局绘制出了剩余的部分由此如果一个ViewPager2要显示多个Item我们可以这样给ViewPager左边和右边设置一个margin、固定ViewPager大小或者根据想要显示的Item个数动态计算ViewPager的大小然后设置clipChildrenfalse允许ViewPager中看不到的界面绘制出来。 思路
由此我将ViewPager2封装了一下目的只是为了给ViewPager2套一层父布局方便使用
class SuperViewPager : RelativeLayout {val mViewPager: ViewPager2 by lazy {findViewByIdViewPager2(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 {clipChildrenfalseLayoutInflater.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 version1.0 encodingutf-8?
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentcom.utils.core.weight.viewpager.SuperViewPagerandroid:idid/mSuperViewPagerandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:backgroundcolor/blackandroid:clipChildrentrue /
/LinearLayoutonCreate中调用 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,除去position0当前页面其他页面设置一个默认效果透明度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 version1.0 encodingutf-8?
androidx.viewpager2.widget.ViewPager2 xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:idid/mViewPagerandroid:clipChildrenfalseandroid:layout_widthmatch_parentandroid:overScrollModeneverandroid:layout_heightmatch_parent
/androidx.viewpager2.widget.ViewPager2再次运行效果如下step4
并没有解决这个问题因为ViewPager2内部并没有对overScrollMode进行处理并且内部使用RecyclerView实现的RecyclerView是ViewPager2的第一个子View,由此我们在SuperViewPager中加入
val mViewPager: ViewPager2 by lazy {findViewByIdViewPager2(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 {findViewByIdViewPager2(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 {clipChildrenfalseLayoutInflater.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 version1.0 encodingutf-8?LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentcom.utils.core.weight.viewpager.SuperViewPagerandroid:idid/mSuperViewPagerandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:backgroundcolor/black //LinearLayoutmSuperViewPager.setPageTransformer(CompositePageTransformer().apply {addTransformer(GalleryTransformer())addTransformer(MarginPageTransformer(20))
})
mSuperViewPager.visibleItem 3
mSuperViewPager.setAdapter(HomePagerAdapter(this))遗留的问题 有心的小伙伴可以发现step1中ViewPager2多页面的情况下页面切换时边缘的页面会出现闪动目前还没发现什么原因。 在SuperViewPager的layout布局中我为ViewPager2设置了android:clipChildren“false”然后在初始化SuperViewPager,我为根布局也设置了clipChildrenfalse, 我搜了下资料因为ViewPager2 设置android:clipChildrenfalse是为了使得内部的View突破限制显示根布局再设置一次是为了承载页面的ViewPager2 能突破限制所以要设置两次但目前我在上面讲clipChildren的时候根LinearLayout嵌套了一个子LinearLayout在子LinearLayout中添加的ImageView我只在根LinearLayout设置了android:clipChildren“false”就实现了我想要的效果不知道这里是为何是因为ViewPager2 内部是RecyclerView吗 在处理多页面边缘手势事件时我一开始使用的方法是
override fun onTouchEvent(event: MotionEvent?): Boolean {return mViewPager.dispatchTouchEvent(event)
}将事件分发给内部的ViewPager,但是出现一个问题 我又仔细看了一次ViewViewGroup的事件分发机制的但是按理说左边已经响应的话右边也应该响应由于Android 11 的API ViewGroup这块 dispatchTouchEvent内容有点多打断点由于使用的API和手机版本不同也没找到原因。有没有小伙伴清楚这个问题出现的原因能够分享一下
其他 作者小空和小芝中的小空 转载说明-务必注明来源https://zhima.blog.csdn.net/ 这位道友请留步☁️我观你气度不凡谈吐间隐隐有王者霸气日后定有一番大作为旁边有点赞收藏今日传你点了吧未来你成功☀️我分文不取若不成功⚡️也好回来找我。 温馨提示点击下方卡片获取更多意想不到的资源。