每日一问:到底为什么属性动画后 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】使用属性动画碰到的困惑及讲解
属性动画的教程网上已经特别多了,本篇也不打算再去各种详解知识点,主要就是记录题主学习属性动画时的碰到的一些困惑,以及后来自己的理解.如果有人也碰到相似的问题,正好可以一起讨论下. 概要 本篇主要涉及的 ...
随机推荐
- ios 应用发布appStore
1.进入管理界面开发商 [点击 iTunes Connect] 2.进入管理apps界面 [点击 Manage Your Apps] watermark/2/text/aHR0cDovL2Jsb2cu ...
- 前端常见算法JS实现
算法是程序的灵魂,一个优秀的前端工程师对算法也是要有所了解的. 排序算法 1. 冒泡排序 //冒泡排序 function bubbleSort(arr){ var i = j = 0; for(i=1 ...
- 彩色图像--色彩空间 RGB系列
学习DIP第62天 转载请标明本文出处:http://blog.csdn.net/tonyshengtan ,出于尊重文章作者的劳动,转载请标明出处!文章代码已托管,欢迎共同开发:https://gi ...
- thread、Task、async & await
学习 Jesse 的文章 async & await 的前世今生(Updated) 而来 Thread是最开始使用的多线程.new一个Thread对象,将方法传进去.手动Start() 还可以 ...
- 关于FileZilla上传文件后服务器端文件与本地文件大小不一致的解决方法
最近在调试网站时发现,通过ftp上传工具FileZilla上传至服务器端的文件与本地文件大小不一致,虽然没有影响网站的最终显示效果,但仍让我困惑不解.后发现是传输类型的原因,解决方法如下: 中文版Fi ...
- 计算机程序设计的史诗TAOCP
倘若你去问一个木匠学徒:你需要什么样的工具进行工作,他可能会回答你:“我只要一把锤子和一个锯”.但是如果你去问一个老木工或者是大师级的建筑师,他会告诉你“我需要一些精确的工具”.由于计算机所解决的问题 ...
- matlab 三维图像的绘制
1. 基本绘图函数 plot3() scatter3() 2. 修饰与点缀 但仅使用默认的配置,调用这些绘图函数,不会很丑,但也漂亮不到哪里去. view(az, el),调整视野和视角:
- 分享一下Oracle 10g和Toad for Oracle的安装步骤
三年前用过Oracle,单纯的“用过”,主要就是说对数据库的一些操作,还不包含创建一些存储过程之类的,所以对Oracle仅仅只是了解一点儿,因为当时那家公司里面,数据库里面的东西都是那些顾问负责的,再 ...
- JavaScript知识树
- 关于JSP
JSP的本质 JSP本质上就是Servlet, 正常情况下, 它会在第一次被访问的时候被容器转化成Java代码, 然后再从Java代码编译成.class文件, 之后实际就和Servlet没区别了, 也 ...