View基础知识
- View的位置参数
- MotionEvent
- TouchSlop
- VelocityTracker
- GestureDetector
- Scroller
什么是View
View是所有控件的基类,它是界面层控件的一种抽象 android studio查看类的继承体系(快捷键CTRL+H) 可见ViewGroup也是继承自View,这让我想到了组合设计模式
View的位置参数
在onCreate()回调方法中去调用view.getLeft(), getRight()…getX()、getY() 等值为0 View的显示必须经历Measure(测量)、Layout(布局)和Draw(绘制)过程。而在Measure与Layout过程完成之后,View的width、height、top、left等属性才被正确赋值,此时我们才能获取到正确的值,这几个过程都晚于onCreate执行 android view.getLeft(), getRight()...等获取值为0_轻狂书生YT的博客-CSDN博客_view getleft 0 疑问:View是在什么时候绘制的?? Activity启动后View何时开始绘制(onCreate中还是onResume之后?) View在平移的过程中,top和left是原始左上角的位置信息,其值不会发生变化,此时发生改变的是x,y,translationX和translationY。
MotionEvent和TouchSlop
Scroller弹性滑动
View的滑动
三种实现View滑动的方式
- 通过View本身提供的scrollTo/scrollBy
- 通过动画给View施加平移效果
- 通过改变View的LayoutParams使得View重新布局
使用scrollTo/scrollBy
scrollBy也是调用了scrollTo方法 scrollBy:基于当前位置的相对滑动 scrollTo:基于所传递参数的绝对滑动
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//绝对滑动
textView1.scrollTo(-100,0);
Log.e(TAG, "onClick: textview1 mScrollX="+textView1.getScrollX() );
//相对滑动
textView.scrollBy(-100,0);
Log.e(TAG, "onClick: textview mScrollX="+textView.getScrollX() );
}
});
滑动过程中的几个要点:详看Demo chapter3_1
- View本身不会移动,移动的是View中的内容
- mScrollX的大小是View左边缘到View中内容的左边缘的水平距离
- mScrollY的大小是View上边缘到View中内容的上边缘的水平距离
- 从左往右滑动,mScrollX为正值(View左边缘在View中内容的左边缘的右边)
使用动画
在1000ms内从原点水平右移200px,并保持状态
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:zAdjustment="normal" >
<translate
android:duration="1000"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="200"
android:toYDelta="0" />
</set>
//使用原生动画实现滑动
Animation animation= AnimationUtils.loadAnimation(this,R.anim.a1_translate);
findViewById(R.id.textView1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView1.startAnimation(animation);
}
});
// 使用属性动画实现滑动
textView2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator.ofFloat(textView2,"translationX",0,100).setDuration(1000).start();
}
});
原生动画与属性动画的区别(在Android3.0的前提下,因为Android3.0之前没有属性动画) 原生动画并不能真正改变View的位置,如果控件有交互事件,那么控件内容不在原始位置了,但是View容器还在原始位置能响应交互事件。 可以新位置只是View的一个影像。 但是属性动画可以解决这个问题。
改变布局参数
实现把textView3控件向右平移100px
textView3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewGroup.MarginLayoutParams params=(ViewGroup.MarginLayoutParams)textView3.getLayoutParams();
params.leftMargin+=100;
textView3.requestLayout();
}
});
滑动方式的对比
- scrollTo/scrollBy:操作简单,适合对View内容的滑动
- 动画:操作简单。主要适用没有交互的View和实现复杂的动画效果(3.0以上用属性动画既简单也适用交互View)
- 改变布局参数:操作稍稍复杂,适用于有交互的View
弹性滑动
使用Scroller
使用动画
使用延时策略
Android事件分发机制详解_谁谁谁动了我的博客-CSDN博客_安卓事件分发
总结一下RecyclerView侧滑菜单的两种实现 - 掘金RecyclerView - 使用ItemTouchHelper实现侧滑删除效果_fjnu_se的博客-CSDN博客
事件分发机制(难点)
View的事件分发机制Android事件分发机制详解:史上最全面、最易懂 点击事件的传递顺序:Activity->Window->ViewGroup->具体的View 点击事件的三大方法:
dispatchTouchEvent
对一个事件进行分发,可能是分发给下一层处理,或者分发给自己。onInterceptTouchEvent
这个方法只有 ViewGroup 有,用来判断对事件是否进行拦截,如果拦截就不会分发给下一层.onTouchEvent
对事件进行处理,消耗或者不消耗,不消耗就会返回给上层。对于 ViewGroup 和 View 这个方法还受到 OnTouchListener 和 enable 属性 的影响,具体的后面会阐述。
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
//空方法、空实现
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Window是如何将事件传递给ViewGroup的? Window是一个抽象类,其中的superDispatchTouchEvent
方法也是抽象方法。 它的实现类是PhoneWindow,这个类不能在AS中查看。
DecorView类也是在package com.android.internal.policy;
这个包目录下。
滑动冲突
常见的滑动冲突场景
场景一:外部控件左右滑动,内部控件上下滑动:类似ViewPager嵌套Fragment(ListView) 场景二:类似ScrollView嵌套ListView
滑动冲突的处理规则
场景一:滑动的角度、距离差和速度差 场景二:业务的划分
滑动冲突的解决方式
- 外部拦截法
事件先经过父容器的拦截处理,重写父容器的onInterceptTouchEvent
方法
- 内部拦截法
重写子元素的dispatchTouchEvent
方法
外部拦截法
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (父容器需要拦截的事件规则) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
Log.d(TAG, "intercepted=" + intercepted);
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
内部拦截法
父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
if (父容器需要当前点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}