每日一问:到底为什么属性动画后 View 在新位置还能响应事件
在 Android 开发中,我们难免会使用动画来处理各种各样的动画效果,以满足 UI 的高逼格设计。对于比较复杂的动画效果,我们通常会采用著名的开源库:lottie-android,或许你会对 lottie 的原理充满好奇,但这并不在我们这篇文章的讨论范围,感兴趣的自行 Google 吧~
属性动画和补间动画的基本编写方式
我一度在论坛上看到人使用了 TranslateAnimation
对控件做了移动操作,然后发现在 View 的新位置点击并没有响应自己的点击事件,反倒是之前的位置能够响应。实际上,补间动画仅仅是对 View 在视觉效果上做了移动、缩放、旋转和淡入淡出的效果,其实并没有真正改变 View 的属性。但我们大多数情况下肯定希望 View 在经过动效后响应触摸事件的位置和视觉效果相同,所以在 Android 3.0 之后引入了属性动画,彻底解决了这个难题。
可能还有一些小伙伴不明白怎样的代码是属性动画,怎样的代码是补间动画。下面针对 View 向右平移 500 px 做一下简单的演示。
对于属性动画,你可以用下面的两种方式。
ObjectAnimator.ofFloat(tv1, "translationX", 0f, 500f)
.setDuration(1000)
.start()
// 或者像这样
tv1.animate().setDuration(1000).translationX(500f)
但用补间动画,并且你想达到同样的效果的话。
val anim = TranslateAnimation(0f, 500f, 0f, 0f)
anim.duration = 1000
anim.fillAfter = true // 设置保留动画后的状态
tv1.startAnimation(anim)
属性动画的使用注意点
对于属性动画来说,尤其需要注意的是操作的属性需要有 set 和 get 方法,不然你的 ObjectAnimator
操作就不会生效。比如水平平移,我们知道,View 的 translationX
属性设置方法接受的是 float
值,所以你把上面的操作编写为 ofInt
就不会生效,比如:
ObjectAnimator.ofInt(tv1, "translationX", 0, 500)
.setDuration(1000)
.start()
对于我们需要用到但又没有写好的属性,比如我们自定义一个进度条 View,我们需要实时展示进度,这时候我们就可以自己定义一个属性,并让它支持 set 和 get,那么在外面就可以对这个自定义的 View 做属性动画操作了。
属性动画和补间动画工作原理
属性动画
属性动画的工作原理很简单,其实就是在一定的时间间隔内,通过不断地对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在属性上的动画效果。
这个属性可以是任意对象的属性。
从上述工作原理可以看出属性动画有两个非常重要的类:ValueAnimator
类 & ObjectAnimator
类,二者的区别在于:
ValueAnimator
类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;而 ValueAnimator
类本质上是一种 改变值 的操作机制。
ObjectAnimator
类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作;可以理解为:ObjectAnimator
更加智能、自动化程度更高。
补间动画
而对于补间动画,我们不妨跟进源码,看看到底做了什么操作。
/**
* Start the specified animation now.
*
* @param animation the animation to start now
*/
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
看到了非常明显 invalidate()
方法,很明显,补间动画在执行的时候,直接导致了 View
执行 onDraw()
方法。总的来说,补间动画的核心本质就是在一定的持续时间内,不断改变 Matrix
变换,并且不断刷新的过程。
为什么属性动画移动一个 View 后,目标位置还可以响应触摸事件呢?
这个问题来自 wanandroid,在此前,我一直认为既然 View
的属性得到了改变,那么经过属性动画后的控件应该所有属性都等同于直接设置在动画后的位置的控件。
看完「陈小缘」的回答后,我突然才想到,虽然 View
做了属性上的改变,但其实并没有更改 View
的 left
、right
、top
、bottom
这些属性,而这些属性恰恰决定了 ViewGroup
的触摸区域判断。
tv1.animate().setDuration(1000).translationX(500f)
那么,假定我们的 View
经过了上面的平移操作后,为什么点击新的位置能够响应到这个点击事件呢?
看了「陈小缘」的回答,我顺便深入了一波源码,想想必须在这分享给大家。
我们知道,在 ViewGroup
没有重写 onInterceptTouchEvent()
方法进行事件拦截的时候,我们一定会通过其 dispatchTouchEvent()
方法进行事件分发,而决定我们哪一个子 View
响应我们的触摸事件的条件又是 我们手指的位置必须在这个子 View
的边界范围内,也就是 left
、right
、top
、bottom
这四个属性形成的矩形区域。
那么,如果我们的 View
已经进行了属性动画后,现在手指响应的触摸位置区域肯定不是 View
自己的left
、right
、top
、bottom
这四个属性形成的区域了,但这个 View
却神奇的响应了我们的点击事件。
/**
* Returns a MotionEvent that's been transformed into the child's local coordinates.
*
* It's the responsibility of the caller to recycle it once they're finished with it.
* @param event The event to transform.
* @param child The view whose coordinate space is to be used.
* @return A copy of the the given MotionEvent, transformed into the given View's coordinate
* space.
*/
private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
final MotionEvent transformedEvent = MotionEvent.obtain(event);
transformedEvent.offsetLocation(offsetX, offsetY);
if (!child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
return transformedEvent;
}
/**
* Returns true if the transform matrix is the identity matrix.
* Recomputes the matrix if necessary.
*
* @return True if the transform matrix is the identity matrix, false otherwise.
*/
final boolean hasIdentityMatrix() {
return mRenderNode.hasIdentityMatrix();
}
/**
* Utility method to retrieve the inverse of the current mMatrix property.
* We cache the matrix to avoid recalculating it when transform properties
* have not changed.
*
* @return The inverse of the current matrix of this view.
* @hide
*/
public final Matrix getInverseMatrix() {
ensureTransformationInfo();
if (mTransformationInfo.mInverseMatrix == null) {
mTransformationInfo.mInverseMatrix = new Matrix();
}
final Matrix matrix = mTransformationInfo.mInverseMatrix;
mRenderNode.getInverseMatrix(matrix);
return matrix;
}
原来,ViewGroup
在 getTransformedMotionEvent()
方法中会通过子 View
的 hasIdentityMatrix()
方法来判断子 View
是否应用过位移、缩放、旋转之类的属性动画。如果应用过的话,那还会调用子 View
的 getInverseMatrix()
做「反平移」操作,然后再去判断处理后的触摸点是否在子 View
的边界范围内。
感叹,今天又发现了一些非常通用却被我们忽略掉的东西,不得不说,鸿洋的 wanandroid 带给了我们很多东西,更加惊叹的是「陈小缘」同学的 View
相关功底确实很强,这也难怪,他能写出如何有逼格的自定义 View 了。
对 View
相关的非常渴望了解的可以到小缘的博客去一探究竟。 https://me.csdn.net/u011387817
每日一问:到底为什么属性动画后 View 在新位置还能响应事件的更多相关文章
- 属性动画 LayoutTransition AnimatorInflater Keyframe 新特性
LayoutTransition设置动画 使用LayoutTransition可为布局的容器设置动画,当容器中的视图层次发生变化时产生相应的过渡的动画效果 过渡的类型一共有四种: LayoutTran ...
- Android 用属性动画自定义view的渐变背景
自定义view渐变背景,同时监听手势自动生成小圆球. 宿主Activity如下: package com.edaixi.tempbak; import java.util.ArrayList; imp ...
- Android属性动画完全解析(中)
转载:http://blog.csdn.net/guolin_blog/article/details/43536355 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法,当然也是 ...
- Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43536355 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法 ...
- Android View体系(三)属性动画
上一篇文章讲了View滑动的六种方法,其中一种是使用动画,这篇文章我们来讲一讲动画的其中一种:属性动画. 1.android视图动画和属性动画 视图动画我们都了解,它提供了AlphaAnimation ...
- 第三部分:Android 应用程序接口指南---第四节:动画和图形---第一章 属性动画及动画与图形概述
第1章 属性动画及动画与图形概述 Android提供了一系列强大的API来把动画加到UI元素中,以及绘制自定义的2D和3D图像中去.下面的几节将综述这些可用的API以及系统的功能,同时帮你做出最优的选 ...
- Android之属性动画(一)
一.概述 Android平台中常用的动画主要有两类,一类是View动画,一类是3.0后新增的属性动画.属性动画与View动画相比功能更加强大,主要体现在以下两个方面: 1. 属性动画不仅仅能应用到V ...
- Android属性动画ObjectAnimator的使用1
版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/106 属性动画ObjectAnimator的使用 属性动画 ...
- 【Android】使用属性动画碰到的困惑及讲解
属性动画的教程网上已经特别多了,本篇也不打算再去各种详解知识点,主要就是记录题主学习属性动画时的碰到的一些困惑,以及后来自己的理解.如果有人也碰到相似的问题,正好可以一起讨论下. 概要 本篇主要涉及的 ...
随机推荐
- win7 64位系统下进入debug
win7 64位无法直接通过命名行输入debug命令的方式进入到debug,好在我们可是使用一个工具DOSbox来进入debug.操作步骤如下:1.下载DOSbox进行安装.下载地址:点击打开链接.如 ...
- Vue插件资料
UI组件element ★11612 - 饿了么出品的Vue2的web UI工具套件 Vux ★7503 - 基于Vue和WeUI的组件库 iview ★5801 - 基于 Vuejs 的开源 UI ...
- 利用.net Core 对程序集中的类 进行统一依赖注入
1.创建特性 用于标注依赖注入 using Microsoft.Extensions.DependencyInjection; using System; using System.Collectio ...
- C++第11周(春)项目4 - 类族的设计
课程首页在:http://blog.csdn.net/sxhelijian/article/details/11890759,内有完整教学方案及资源链接 [项目4 - 类族的设计]按下面的提示,由基类 ...
- mac在下面Apache 创 .htaccess档
在设定一个固定的链接将提下面的例子说明样题: 若您的 .htaccess 文件可写.我们能够自己主动改动它.但似乎它不可写,因此我们在下方列出了您 .htaccess 文件里应该增加的URL 重写规则 ...
- Oltu在Jersey框架上实现oauth2.0授权模块
oltu是一个开源的oauth2.0协议的实现,本人在此开源项目的基础上进行修改,实现一个自定义的oauth2.0模块. 关于oltu的使用大家可以看这里:http://oltu.apache.org ...
- 关于 Facebook 的 React 专利许可证
本文转载自:酷 壳 – CoolShell 作者:陈皓 随着 Apache.百度.Wordpress 都在和 Facebook 的 React.js 以及其专利许可证划清界限,似乎大家又在讨论 Fac ...
- SQL Server 阻止组件 xp_cmdshell
错误信息描述 消息 15281,级别 16,状态 1,过程 xp_cmdshell,第 1 行SQL Server 阻止了对组件 'xp_cmdshell' 的 过程 'sys.xp_cmdshell ...
- Django 创建超级管理员失败
django.db.utils.DataError: (1406, "Data too long for column 'gender' at row 1") 解决方案 在执行mi ...
- ELINK编程器典型场景之多APP文件下载
有些应用场合中,单MCU内会采用BootLoader+APP1+APP2的加载模式,程序启动时先进入BootLoader程序,依据设定条件跳转至APPx应用运行:为满足此类需求,设计多达5个程序文件( ...