本章内容: 相识View动画的总体操持理念,关键是头脑, 而非代码细节.
一. 怎样让View动起来.
1. 起主要相识View是怎样展示到屏幕上的?
①. 先确定View的位置, 如下图:
②. 在View上面绘制内容, 如下图:
2. 得出两种让View运动的方案:
①. layout() 改变结构位置
②. draw() 改变 绘制内容的位置
二. 体系接纳的时哪种方案呢?
答:第2种, draw() 绘制时,改变绘制内容的位置.
如许做的长处:无论怎样运动, 生存了原始位置 (相对父控件的位置), 如下图:
三. View中,setScrollX、setScrollY怎样实现滚动的?
从操持者的角度看: 为了低沉耦合度, 我们应该把必要滚动的信息单独记录下来,然后在draw()绘制的时间 ,加上必要滚动的坐标, 终极的出新的绘制坐标, 如下图:
从源码实现的角度看:
以setScrollY为例:
protected int mScrollY; public void setScrollY(int value) { scrollTo(mScrollX, value); } public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { mScrollX = x; mScrollY = y; if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } } public void draw(Canvas canvas) { int left = mScrollX + paddingLeft; int right = left + mRight - mLeft - mPaddingRight - paddingLeft; int top = mScrollY + getFadeTop(offsetRequired); int bottom = top + getFadeHeight(offsetRequired); canvas.saveUnclippedLayer(left、top、right、位置信息); canvas.drawRect(left、top、right、bootm 位置信息)}四. 补间动画 和属性动画 怎样实现的?
从操持者的角度看: 原理和上面雷同, 先把动画信息先用单独对象生存起来,然后在draw()绘制的时间,举行矩阵厘革, 如下图:
从源码的角度看:
①. 补间动画生存,实在就是生存到一个变量中 mCurrentAnimation
View.java protected Animation mCurrentAnimation = null; public void startAnimation(Animation animation) { ... setAnimation(animation); invalidate(true); } public void setAnimation(Animation animation) { mCurrentAnimation = animation; }②. 属性动画生存, 以setTranslationX为例,实在就是把值生存到一个变量中mRenderNode
View.java final RenderNode mRenderNode; public View(Context context) { mRenderNode = RenderNode.create(getClass().getName(), new ViewAnimationHostBridge(this)); } public void setTranslationX(float translationX) { if (translationX != getTranslationX()) { mRenderNode.setTranslationX(translationX); invalidateViewProperty(false, true); }③. 绘制过程
View.javaboolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { Transformation transformToApply = null; //获取补间动画 final Animation a = getAnimation(); if (a != null) { //根据时间, 盘算出必要的矩阵厘革信息,并用Transformation包起来 applyLegacyAnimation(parent, drawingTime, a, scalingRequired); //这时transformToApply内里已经有变动矩阵的信息 transformToApply = parent.getChildTransformation(); } //canvas 矩阵变动 if (transformToApply != null) { canvas.concat(transformToApply.getMatrix()); } //获取属性动画的矩阵厘革信息,canvas 矩阵变动 if (!childHasIdentityMatrix && !drawingWithRenderNode) { canvas.concat(getMatrix()); } //开始常见的draw绘制 draw(canvas);}④. 补间动画,怎样盘算Matrix()信息 (矩阵变动信息)
Matrix()信息, 现实上是包裹起来的,结构如下图:
View.javaboolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { applyLegacyAnimation(parent, drawingTime, a, scalingRequired);}private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, Animation a, boolean scalingRequired) { Transformation t = parent.getChildTransformation(); a.getTransformation(drawingTime, t, 1f);}Animation.java public boolean getTransformation(long currentTime, Transformation outTransformation,float scale) { return getTransformation(currentTime, outTransformation); } public boolean getTransformation(long currentTime, Transformation outTransformation) { //估值器处理惩罚, 不是本章重点 final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); applyTransformation(interpolatedTime, outTransformation); } //获取Matrix()信息, protected void applyTransformation(float interpolatedTime, Transformation t) { }可以看到, Animation类中具体的算法applyTransformation()是空实现, 必要交给子类来实现, 如果必要自界说动画算法, 关键是重写这个方法, 比方体系提供的平移动画:TranslateAnimation.java
@Override protected void applyTransformation(float interpolatedTime, Transformation t) { float dx = mFromXDelta; float dy = mFromYDelta; if (mFromXDelta != mToXDelta) { dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime); } if (mFromYDelta != mToYDelta) { dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime); } t.getMatrix().setTranslate(dx, dy); }⑤. 属性动画,怎样盘算Matrix()信息 (矩阵变动信息)
mRenderNode就是前面生存的属性动画信息,将matrix传到RenderNode举行赋值, 末了跑到native内里处理惩罚了,具体赋值的算法这里就不穷究了.
View.java boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { canvas.concat(getMatrix()); } public Matrix getMatrix() { final Matrix matrix = mTransformationInfo.mMatrix; mRenderNode.getMatrix(matrix); // 传进去后, 在内里给matrix赋值 return matrix; } RenderNode.java public void getMatrix(@NonNull Matrix outMatrix) { nGetTransformMatrix(mNativeRenderNode, outMatrix.native_instance); } @CriticalNative private static native void nGetTransformMatrix(long renderNode, long nativeMatrix);五. 动画是怎样做到一帧一帧运动的?
- 每次draw()的时间,都是根据当前时间, 来获取动画对应的坐标.
- 如果动画没到竣事时间,调用invalidate(),期待下一次垂直同步信号, 会继续实行draw(), 具体的可以去相识 屏幕渲染机制.
六. 补间动画 和 属性动画 对于点击触摸事件有什么差异?
动画只是位置上有所厘革, 以是我们只必要 事件 对于位置是怎样判定的就可以了,源码如下:
ViewGroup.javapublic boolean dispatchTouchEvent(MotionEvent ev) { for (int i = childrenCount - 1; i >= 0; i--) { View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); //判定触摸的坐标 是否在child内 if (!isTransformedTouchPointInView(x, y, child, null)) { continue; } }}protected boolean isTransformedTouchPointInView(float x, float y, View child,PointF outLocalPoint) { //关键:原始坐标+属性动画 ---> 新的坐标 transformPointToViewLocal(point, child); //实行完这一句之后,point就已经算上动画后的坐标了 final boolean isInView = child.pointInView(point[0], point[1]); return isInView;} public void transformPointToViewLocal(float[] point, View child) { if (!child.hasIdentityMatrix()) { child.getInverseMatrix().mapPoints(point); //重新盘算point坐标 } }View.java //获取逆矩阵 public final Matrix getInverseMatrix() { final Matrix matrix = mTransformationInfo.mInverseMatrix; mRenderNode.getInverseMatrix(matrix); return matrix; } /*package*/ final boolean pointInView(float localX, float localY) { return pointInView(localX, localY, 0); } //终极盘算位置 public boolean pointInView(float localX, float localY, float slop) { return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) && localY < ((mBottom - mTop) + slop); }结论: 事件处理惩罚盘算坐标的时间,只是把属性动画思量进去了, 并没有把补间动画算进去, 以是属性动画运动后,点击触摸事件是可以触发的,补间动画则不可.
思索: 如果补间动画也必要处理惩罚点击触摸事件, 那怎么办呢?能看懂本章内容的话, 那应该很好办理了.
以上内容, 仅代表个人观点, 如有差异意见, 接待指出一起讨论. |