android 自定义View 视差动画
本系列自定义View全部采用kt
**系统: **mac
android studio: 4.1.3
**kotlin version:**1.5.0
gradle: gradle-6.5-bin.zip
废话不多说,先来看今天要完成的效果:
在上一篇:android setContentView()解析中我们介绍了,如何通过Factory2来自己解析View,
那么我们就通过这个机制,来完成今天的效果《视差动画》,
回顾
先来回顾一下如何在Fragment中自己解析View
class MyFragment : Fragment(), LayoutInflater.Factory2 {override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?,): View {val newInflater = inflater.cloneInContext(activity)LayoutInflaterCompat.setFactory2(newInflater, this)return newInflater.inflate(R.layout.my_fragment, container, false)}// 重写Factory2的方法override fun onCreateView(parent: View?,name: String,context: Context,attrs: AttributeSet,): View? {val view = createView(parent, name, context, attrs)// 此时的view就是自己创建的view!// ...................return view}// 重写Factory2的方法override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {return onCreateView(null, name, context, attrs)}// SystemAppCompatViewInflater() 复制的系统源码private var mAppCompatViewInflater = SystemAppCompatViewInflater()private fun createView(parent: View?, name: String?, mContext: Context,attrs: AttributeSet,): View? {val is21 = Build.VERSION.SDK_INT < 21// 自己去解析Viewreturn mAppCompatViewInflater.createView(parent, name, mContext, attrs, false,is21, /* Only read android:theme pre-L (L+ handles this anyway) */true, /* Read read app:theme as a fallback at all times for legacy reasons */false /* Only tint wrap the context if enabled */)}
}
如果对这段代码有兴趣的,可以去看 上一篇:android setContentView()解析,
思路分析
viewpager + fragment
自定义属性:
- 旋转: parallaxRotate
- 缩放 : parallaxZoom
- 出场移动:parallaxTransformOutX,parallaxTransformOutY
- 入场移动:parallaxTransformInX,parallaxTransformInY
给需要改变变换的view设置属性
在fragment的时候自己创建view,并且通过AttributeSet解析所有属性
将需要变换的view保存起来,
在viewpager滑动过程中,通过addOnPageChangeListener{} 来监听viewpager变化,当viewpager变化过程中,设置对应view对应变换即可!
viewPager+Fragment
首先先实现最简单的viewpager+Fragment
代码块1.1
class ParallaxBlogViewPager(context: Context, attrs: AttributeSet?) : ViewPager(context, attrs) {fun setLayout(fragmentManager: FragmentManager, @LayoutRes list: ArrayList<Int>) {val listFragment = arrayListOf<C3BlogFragment>()// 加载fragmentlist.map {C3BlogFragment.instance(it)}.forEach {listFragment.add(it)}adapter = ParallaxBlockAdapter(listFragment, fragmentManager)}private inner class ParallaxBlockAdapter(private val list: List<Fragment>,fm: FragmentManager) : FragmentPagerAdapter(fm) {override fun getCount(): Int = list.sizeoverride fun getItem(position: Int) = list[position]}
}
C3BlogFragment:
代码块1.2
class C3BlogFragment private constructor() : Fragment(), LayoutInflater.Factory2 {companion object {@NotNullprivate const val LAYOUT_ID = "layout_id"fun instance(@LayoutRes layoutId: Int) = let {C3BlogFragment().apply {arguments = bundleOf(LAYOUT_ID to layoutId)}}}private val layoutId by lazy {arguments?.getInt(LAYOUT_ID) ?: -1}override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?,): View {val newInflater = inflater.cloneInContext(activity)LayoutInflaterCompat.setFactory2(newInflater, this)return newInflater.inflate(layoutId, container, false)}override fun onCreateView(parent: View?,name: String,context: Context,attrs: AttributeSet,): View? {val view = createView(parent, name, context, attrs)/// 。。。 在这里做事情。。。 return view}private var mAppCompatViewInflater = SystemAppCompatViewInflater()override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {return onCreateView(null, name, context, attrs)}private fun createView(parent: View?, name: String?, mContext: Context,attrs: AttributeSet,): View? {val is21 = Build.VERSION.SDK_INT < 21return mAppCompatViewInflater.createView(parent, name, mContext, attrs, false,is21, true, false )}
}
这个fragment目前的作用就是接收传过来的布局,展示,
并且自己解析view即可!
xml与调用:
R.layout.c3_1.item,这些布局很简单,就是
- 一张静态图片
- 一张动态图片
其他的布局都是一样的,这里就不看了.
来看看当前的效果
自定义属性
通常我们给一个view自定义属性,我们会选择在attrs.xml 中来进行,例如这样:
但是很明显,这么做并不适合我们的场景,因为我们想给任何view都可以设置属性,
那么我们就可以参考ConstraintLayout中的自定义属性:
我们自己定义属性:
并且给需要变换的view设置值
- app:parallaxRotate=“10” 表示在移动过程中旋转10圈
- app:parallaxTransformInY=“0.5” 表示入场的时候,向Y轴方向偏移 height * 0.5
- app:parallaxZoom=“1.5” 表示移动过程中慢慢放大1.5倍
Fragment中解析自定义属性
我们都知道,所有的属性都会存放到AttributeSet中,先打印看一看:
(0 until attrs.attributeCount).forEach {Log.i("szj属性","key:${attrs.getAttributeName(it)}\t" +"value:${attrs.getAttributeValue(it)}")
}
这样一来就可以打印出所有的属性,并且找到需要用的属性!
那么接下来只需要将这些属性保存起来,在当viewpager滑动过程中取出用即可!
这里我们的属性是保存到view的tag中,
需要注意的是,如果你的某一个view需要变换,那么你的view就一定得设置一个id,因为这里是通过id来存储tag!
监听ViewPager滑动事件
# ParallaxBlogViewPager.kt// 监听变化
addOnPageChangeListener(object : OnPageChangeListener {// TODO 滑动过程中一直回调override fun onPageScrolled(position: Int,positionOffset: Float,positionOffsetPixels: Int,) {Log.e("szjParallaxViewPager","onPageScrolled:position:$position\tpositionOffset:${positionOffset}\tpositionOffsetPixels:${positionOffsetPixels}")}//TODO 当页面切换完成时候调用 返回当前页面位置override fun onPageSelected(position: Int) {Log.e("szjParallaxViewPager", "onPageSelected:$position")}// override fun onPageScrollStateChanged(state: Int) {when (state) {SCROLL_STATE_IDLE -> {Log.e("szjParallaxViewPager", "onPageScrollStateChanged:页面空闲中..")}SCROLL_STATE_DRAGGING -> {Log.e("szjParallaxViewPager", "onPageScrollStateChanged:拖动中..")}SCROLL_STATE_SETTLING -> {Log.e("szjParallaxViewPager", "onPageScrollStateChanged:拖动停止了..")}}}
})
这三个方法介绍一下:
onPageScrolled(position:Int , positionOffset:Float, positionOffsetPixels)
@param position
: 当前页面下标@param positionOffset
:当前页面滑动百分比@param positionOffsetPixels
: 当前页面滑动的距离
在这个方法中需要注意的是,当假设当前是第0个页面,从左到右滑动,
position = 0
positionOffset = [0-1]
positionOffsetPixels = [0 - 屏幕宽度]
当第1个页面的时候,从左到右滑动,和第0个页面的状态都是一样的
但是从第1个页面从右到左滑动的时候就不一样了,此时
position = 0
positionOffset = [1-0]
positionOffsetPixels = [屏幕宽度 - 0]
onPageSelected(position:Int)
@param position
: 但页面切换完成的时候调用
onPageScrollStateChanged(state:Int)
@param state:
但页面发生变化时候调用,一共有3种状体- SCROLL_STATE_IDLE 空闲状态
- SCROLL_STATE_DRAGGING 拖动状态
- SCROLL_STATE_SETTLING 拖动停止状态
了解了viewpager滑动机制后,那么我们就只需要在滑动过程中,
获取到刚才在tag种保存的属性,然后改变他的状态即可!
# ParallaxBlogViewPager.kt// 监听变化
addOnPageChangeListener(object : OnPageChangeListener {// TODO 滑动过程中一直回调override fun onPageScrolled(position: Int,positionOffset: Float,positionOffsetPixels: Int,) {// TODO 当前fragmentval currentFragment = listFragment[position]currentFragment.list.forEach { view ->// 获取到tag中的值val tag = view.getTag(view.id)(tag as? C3Bean)?.also {// 入场view.translationX = -it.parallaxTransformInX * positionOffsetPixelsview.translationY = -it.parallaxTransformInY * positionOffsetPixelsview.rotation = -it.parallaxRotate * 360 * positionOffsetview.scaleX =1 + it.parallaxZoom - (it.parallaxZoom * positionOffset)view.scaleY =1 + it.parallaxZoom - (it.parallaxZoom * positionOffset)}}// TODO 下一个fragment// 防止下标越界if (position + 1 < listFragment.size) {val nextFragment = listFragment[position + 1]nextFragment.list.forEach { view ->val tag = view.getTag(view.id)(tag as? C3Bean)?.also {view.translationX =it.parallaxTransformInX * (width - positionOffsetPixels)view.translationY =it.parallaxTransformInY * (height - positionOffsetPixels)view.rotation = it.parallaxRotate * 360 * positionOffsetview.scaleX = (1 + it.parallaxZoom * positionOffset)view.scaleY = (1 + it.parallaxZoom * positionOffset)}}}}//TODO 当页面切换完成时候调用 返回当前页面位置override fun onPageSelected(position: Int) {...}override fun onPageScrollStateChanged(state: Int) { ... }
})
来看看现在的效果:
此时效果就基本完成了
但是一般情况下,引导页面都会在最后一个页面有一个跳转到主页的按钮
为了方便起见,我们只需要将当前滑动到的fragment页面返回即可!
这么一来,我们就可以在layout布局中为所欲为,因为我们可以自定义属性,并且自己解析,可以做任何自己想做的事情!
思路参考自
完整代码
原创不易,您的点赞与关注就是对我最大的支持!
热门文章:
android自定义View仿QQ拖动效果
android 图解 PhotoView,从‘百草园’到‘三味书屋’!
android Rv实现探探效果
android ViewPager 进阶(仿画廊/图书翻页) 与 palette 使用 (含完整Demo)
android MotionLayout从入门到实战…
android 自定义View 视差动画相关推荐
- android+属性动画+高度,android 自定义view+属性动画实现充电进度条
近期项目中需要使用到一种类似手机电池充电进度的动画效果,以前没学属性动画的时候,是用图片+定时器的方式来完成的,最近一直在学习动画这一块,再加上复习一下自定义view的相关知识点,所以打算用属性动画和 ...
- android 自定义view: 跑马灯-光圈
本系列自定义View全部采用kt **系统: **mac android studio: 4.1.3 **kotlin version:**1.5.0 gradle: gradle-6.5-bin.z ...
- android自定义View: 九宫格解锁
本系列自定义View全部采用kt 系统:mac android studio: 4.1.3 kotlin version1.5.0 gradle: gradle-6.5-bin.zip 废话不多说,先 ...
- android view 渐变动画,Android自定义view渐变圆形动画
本文实例为大家分享了Android自定义view渐变圆形动画的具体代码,供大家参考,具体内容如下 直接上效果图 自定义属性 attrs.xml文件 创建一个类 ProgressRing继承自 view ...
- android 自定义 对号,Android自定义View实现打钩动画功能
先上效果图 动图 静态图 1. 回顾 [Android自定义View:一个精致的打钩小动画]上一篇文章,我们已经实现了基本上实现了控件的效果了,但是...但是...过了三四天后,仔细看回自己写的代码, ...
- android+清除循环动画,android自定义View之(4)-一键清除动画
android自定义View之(四)------一键清除动画 1.前言: 自己也是参考别人的一些自定义view例子,学习了一些基本的自定义view的方法.今天,我参考了一些资料,再结合自已的一些理解, ...
- android自定义View之(四)------一键清除动画
1.前言: 自己也是参考别人的一些自定义view例子,学习了一些基本的自定义view的方法.今天,我参考了一些资料,再结合自已的一些理解,做了一个一键清除的动画.当年,我实现这个是用了几张图片,采用F ...
- android 自定义view 动画效果,Android自定义view实现阻尼效果的加载动画
效果: 需要知识: 1. 二次贝塞尔曲线 2. 动画知识 3. 基础自定义view知识 先来解释下什么叫阻尼运动 阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减的振动,又 ...
- Android自定义view之围棋动画
Android自定义view之围棋动画 好久不见,最近公众号内粉丝要求上新一篇有点难度的自定义view文章,所以它来了!! 干货文,建议收藏 文章目录 Android自定义view之围棋动画 前言 完 ...
最新文章
- 请求路径@PathVariable与请求参数@RequestParam的区别
- oracle 行级死锁_ORACLE死锁的分类
- 对比学习系列论文CPC(二)—Representation Learning with Contrastive Predictive Coding
- 数据中心冷热空气流控制优化方案
- jpa原生query_JPA执行原生SQL语句
- 14-angular.isDefined
- php月历,PHP生成月历代码
- frameset和iframe的区别
- easyui的validatebox重写自定义验证规则的几个实例
- PostgreSQL 10 高可用 本地SSD盘 版本发布
- 【1131】C/C++经典程序训练1---最大公约数与最小公倍数
- selenium-js
- mysql触发器更新前触发_mysql触发器实例:更新前触发
- 2022前端CSS经典面试题
- 关于Excel表格快捷键
- python爬虫豆瓣评论论文_Python爬虫(三)——对豆瓣图书各模块评论数与评分图形化分析...
- [Telink泰凌微825x]硬件开发环境搭建(一)
- 2021-下载酷狗音乐-爬虫-java
- 图解对称加密与非对称加密
- 安卓配置正式包和测试包不同的名字、图标、同时安装,(极光配置测试和正式)
热门文章
- OSChina 周三乱弹 ——你有社交恐惧症么?
- 人际交往的技巧包括哪些因素
- C++11新特性:移动构造函数和移动赋值
- Zotero(超好用的文献管理软件)安装+坚果云同步配置教程+常用插件介绍(全面)
- pandas中DataFrame字符串过滤之正则表达式
- sed 匹配非常规空格[[:space:]]+
- Springboot+thymeleaf实现excel文件上传+后台数据搜索
- win10右键点击文件夹没响应,解决
- 【目标检测无痛涨点篇】SWA:平均多个模型权值
- python3 爬取电影天堂最新电影