我们进行转场动画最常用的就是Translation。我们查看源代码知道Translation的父类是Visibility。我们在自定义转场动画的时候只需要实现onAppear() 和 onDisappear() 方法。只需要在这里面去创建对应的Animator对象,实现各自的动画就好了。现有的实现类有Fade,Slide,Explode
View有自带的做缩放平移旋转…..的api:
这些动画的底层实现都是交给了RenderNode来做。
1 2 3 4 5 6 public void setScaleY (float scaleY) { mRenderNode.setScaleY(scaleY); } public void setRotationY (float rotationY) { mRenderNode.setRotationY(rotationY); }
通过上面这个方法修改view的属性,他的点击事件也会跟随view走。
补间动画不能改变触摸事件的区域,但是属性动画可以。但是为什么可以呢?
通过查看属性动画的源码得知:
1 2 3 4 5 6 public float getX () { return mLeft + getTranslationX(); } public float getY () { return mTop + getTranslationY(); }
我们获取x 和 y的时候都分别加上了对应的Translation,而属性动画更新的就是这个值。所以通过getX getY可以获取到正确的值。
那问题又来了,如果说坐标指的是left,top,right,bottom,那就不对了。通过上面分析可以看到,我们在做动画的时候只更改了getX 和 getY的值,而并没有去修改这四个值。我们可以打开开发者选项里的显示布局边界:
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 mView.setOnTouchListener(new View.OnTouchListener() { int lastX, lastY; Toast toast = Toast.makeText(TestActivity.this , "" , Toast.LENGTH_SHORT); @Override public boolean onTouch (View v, MotionEvent event) { int x = (int ) event.getRawX(); int y = (int ) event.getRawY(); if (event.getAction() == MotionEvent.ACTION_MOVE) { int toolbarHeight = (getWindow().getDecorView().getHeight() - findViewById(R.id.root_view).getHeight()); int widthOffset = mView.getWidth() / 2 ; int heightOffset = mView.getHeight() / 2 ; mView.setTranslationX(x - mView.getLeft() - widthOffset); mView.setTranslationY(y - mView.getTop() - heightOffset - toolbarHeight); toast.setText(String.format("left: %d, top: %d, right: %d, bottom: %d" , mView.getLeft(), mView.getTop(), mView.getRight(), mView.getBottom())); toast.show(); } lastX = x; lastY = y; return true ; } });
可以发现,当view移动的时候,哪个框框并没有移动,打印的left值也没变。
那我们直接改layout:
1 2 3 4 5 6 7 8 9 10 11 @Override public boolean onTouch (View v, MotionEvent event) { ... if (event.getAction() == MotionEvent.ACTION_MOVE) { ... mView.layout(x - widthOffset, y - heightOffset - toolbarHeight, x + widthOffset, y + heightOffset - toolbarHeight); ... } return true ; }
可以发现用layout移动view,那个框框也是会动的。
还有一个实现改变view的位置的方法:这个方法也不是正真改变控件的位置。
1 2 view.offsetTopAndBottom(offectY) view.offsetLeftAndRight(offectX)
那现在问题来了。既然setTranslation没有改变正真的坐标,那为什么触摸的区域确会跟着移动呢?
Android实现圆弧滑动效果之ArcSlidingHelper篇_陈小缘的博客-CSDN博客
这里我们就需要看事件分发的代码了:
事件传递顺序:
先从Activity传到PhonwWindow才传到Decorview,DecorView是继承自ViewGroup,然后就走到了ViewGroup的dispatchTouchEvent(Event e)方法。
进入viewGroup首先判断这个view是不是可见 and 有没有被遮挡:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 if (onFilterTouchEventForSecurity(ev)) { if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null ) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 ; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); } else { intercepted = false ; } } else { intercepted = true ; } } protected boolean isTransformedTouchPointInView (float x, float y, View child, PointF outLocalPoint) { final float [] point = getTempLocationF(); point[0 ] = x; point[1 ] = y; transformPointToViewLocal(point, child); final boolean isInView = child.pointInView(point[0 ], point[1 ]); if (isInView && outLocalPoint != null ) { outLocalPoint.set(point[0 ], point[1 ]); } return isInView; }
手指按下屏幕的时候,首先进入viewgroup的dispatchTouchEvent(),在onInterceptTouchEvent方法里面判断是否拦截,如果不拦截,开始到序的方式遍历viewGroup里面的全部子view。遍历的过程中,依次判断手指按下的坐标,是否在当前子view的区间里面,如果不在里面,就continue,开始下一个子view的判断。当找到合适的时候,执行dispatchTransformedTouchEvent()方法,看看子view是否处理这个事件,在里面执行了child.dispatchTouchEvent(event);方法。如果这个child是一个viewgroup方法,那么会再次执行上面这一串逻辑,如果是view,那么我们进入view的dispatchTouchEvent方法。在进入view的dispatchTouchEvent方法我们可以看到如果这个view设置了setOnTouchListener方法,那么点击事件就会传给这个回调里面,就不会走setOnClickListener设置的点击事件,如果没设置setOnTouchListener,那么就会接着执行view的onTouchEvent方法。
这个child.dispatchTouchEvent(event)返回true的时候代表这次的down事件有人处理了。
然后方法继续回到viewGroup的dispatchTouchEvent方法,给newTouchTargets附上当前这个view的值,然后mFirstTouchTarget赋值 = newTouchTargets,然后告诉这次的事件找到了执行者 alreadyDispatchedToNewTouchTarget = true