"树":
交互(触控):2维坐标系 + 时间 (A/NotificationBar)
dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent。接下来就按照Activity -> ViewGroup -> View 。
手指触摸到屏幕时会触发一个 Action_Down 类型的事件,当前页面的 Activity 会首先走Activity 的 dispatchTouchEvent() 方法,内部:调用 getWindow.superDispatchTouchEvent()。
-
如果上一步返回 true,直接返回 true;否则就 return 自己的 onTouchEvent()。
这个逻辑很好理解,getWindow().superDispatchTouchEvent() 如果返回 true 代表当前事件已经被处理,无需调用自己的 onTouchEvent;否则代表事件并没有被处理,需要 Activity 自己处理,也就是调用自己的 onTouchEvent。 -
Activity#boolean dispatchTouchEvent(){
if(getWindow().superDispatchTouchEvent()){//实际调用了mDecor.superDispatchTouchEvent(event){if(onInterceptTouchEvent()//内部views#dispatchTouchEvent()中直接 return 了自己的 onTouchEvent()){return true;//默认的 ViewGroup 都是不拦截的;}else return onTouchEvent();}
-
return true;
}else{
return onTouchEvent();
}
}
getWindow()方法返回了一个 Window 类型的对象,这个我们都知道,在 Android 中,PhoneWindow 是Window 的唯一实现类。所以这句本质上是调用了`PhoneWindow中的superDispatchTouchEvent()。
而在 PhoneWindow 的这个方法中实际调用了mDecor.superDispatchTouchEvent(event)。这个 mDecor 就是 DecorView,它是 FrameLayout 的一个子类,在 DecorView 中的 superDispatchTouchEvent() 中调用的是 super.dispatchTouchEvent()。到这里就很明显了,DecorView 是一个 FrameLayout 的子类,FrameLayout 是一个 ViewGroup 的子类,本质上调用的还是 ViewGroup的dispatchTouchEvent()。
事件已经从 Activity 传递到了 ViewGroup,接下来我们来分析下 ViewGroup 中的这几个事件处理方法。
在 ViewGroup 中的 dispatchTouchEvent()中的逻辑大致如下:
-
通过 onInterceptTouchEvent() 判断当前 ViewGroup 是否拦截事件,默认的 ViewGroup 都是不拦截的;
-
如果拦截,则 return 自己的 onTouchEvent();
-
如果不拦截,则根据 child.dispatchTouchEvent()的返回值判断。如果返回 true,则 return true;否则 return 自己的 onTouchEvent(),在这里实现了未处理事件的向上传递。
通常情况下 ViewGroup 的 onInterceptTouchEvent()都返回 false,也就是不拦截。这里需要注意的是事件序列,比如 Down 事件、Move 事件……Up事件,从 Down 到 Up 是一个完整的事件序列,对应着手指从按下到抬起这一系列的事件,如果 ViewGroup 拦截了 Down 事件,那么后续事件都会交给这个 ViewGroup的onTouchEvent。如果 ViewGroup 拦截的不是 Down 事件,那么会给之前处理这个 Down 事件的 View 发送一个 Action_Cancel 类型的事件,通知子 View 这个后续的事件序列已经被 ViewGroup 接管了,子 View 恢复之前的状态即可。
这里举一个常见的例子:在一个 Recyclerview 钟有很多的 Button,我们首先按下了一个 button,然后滑动一段距离再松开,这时候 Recyclerview 会跟着滑动,并不会触发这个 button 的点击事件。这个例子中,当我们按下 button 时,这个 button 接收到了 Action_Down 事件,正常情况下后续的事件序列应该由这个 button处理。但我们滑动了一段距离,这时 Recyclerview 察觉到这是一个滑动操作,拦截了这个事件序列,走了自身的 onTouchEvent()方法,反映在屏幕上就是列表的滑动。而这时 button 仍然处于按下的状态,所以在拦截的时候需要发送一个 Action_Cancel 来通知 button 恢复之前状态。
事件分发最终会走到 View 的 dispatchTouchEvent()中。在 View 的 dispatchTouchEvent() 中没有 onInterceptTouchEvent(),这也很容易理解,View 不是 ViewGroup,不会包含其他子 View,所以也不存在拦截不拦截这一说。忽略一些细节,View 的 dispatchTouchEvent()中直接 return 了自己的 onTouchEvent()。如果 onTouchEvent()返回 true 代表事件被处理,否则未处理的事件会向上传递,直到有 View 处理了事件或者一直没有处理,最终到达了 Activity 的 onTouchEvent() 终止。
这里经常有人问 onTouch 和 onTouchEvent 的区别。首先,这两个方法都在 View 的 dispatchTouchEvent()中,是这么一个逻辑:
-
如果 touchListener 不为 null,并且这个 View 是 enable 的,而且 onTouch 返回的是 true,满足这三个条件时会直接 return true,不会走 onTouchEvent()方法。
-
上面只要有一个条件不满足,就会走到 onTouchEvent()方法中。所以 onTouch 的顺序是在 onTouchEvent 之前的。
触控三阶段时序:Down(时长): Move(时长):Up(点击完成标志):
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if(onInterceptTouchEvent(ev)) { consume = onTouchEvent(ev); } else { consume = child.dispatchTouchEvent(ev); } return consume; }//源自 android开发艺术探索//一时刻一个容器消费事件 弊如同向嵌套滑动(NestedScrolling(子View驱动): NestedScrollingChild NestedScrollingParent NestedScrollingChildHelper NestedScrollingParentHelper)
CoordinatorLayout:加强版FrameLayout,便于直接子View间实现“观察者”模式public class FBehavior extends CoordinatorLayout.Behavior<TextView> {//交由Behavior管理:观察者设置app:layout_behavior=".FBehavior";layout_anchor设为被观察者的id,layout_anchorGravity具体反应
public FBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
return dependency instanceof Button;//确定被观察者
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child,
View dependency) {//观察dependency并反应
child.setX(dependency.getX() + 100);
return true;
}
}
容器:(绝/相对)位置:位移 容积
内容:形态:旋转,(A)RGB,缩放,平移, 错切(skew),透视(3D)
焦点:V:requestFocus和clearFocus, onFocusChange,onFocusChangeListener;
ViewGroup, 如果只需要在子View获取焦点时得到通知, requestChildFocus,onRequestFocusInDescendants可控制某些情景下ViewGroup焦点
,控制焦点移动focusSearch;FocusFinder工具 beforeDescendants:viewgroup会优先其子类控件而获取到焦点 afterDescendants:viewgroup只有当其子类控件不需要焦点时才获取焦点 blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点
eg:识别点击v里任何区域:drawPath标记区域,创建一全局Region(区域)再Region.setPath为可识别区域:class RegionClickV extends View {
Paint mPaint;
Region globalRegion;
Region circleRegion1;
Region circleRegion2;
Path circlePath1;
Path circlePath2;
public RegionClickView(Context context) {
super(context);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.GRAY);
circlePath1 = new Path();
circlePath2 = new Path();
circleRegion1 = new Region();
circleRegion2 = new Region();
circlePath1.addRect(100, 100, 300,300, Path.Direction.CW);//值域
circlePath2.addRect(-100,-100,100,100, Path.Direction.CW);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
globalRegion = new Region(0, 0, w, h);
circleRegion1.setPath(circlePath1, globalRegion);
circleRegion2.setPath(circlePath2, globalRegion);
}
@Override
public boolean onTouchEvent(MotionEvent event) {//return m(Scale)GestureDetector.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
if (circleRegion1.contains(x,y)){
//somewhere do sth.
}
if (circleRegion2.contains(x,y)){
//
} break;
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(circlePath1,mPaint);
canvas.drawPath(circlePath2,mPaint);
}
}
child.getHitRect(new Rect()).bottom += 100; //child.get,,,绘制完再执行 new Rect().setEmpty(//reset)
parent.setTouchDelegate(new TouchDelegate(rc, child)); //将自身某局域接收到的点击分发给child,若范围超出Parent,则超出部分无效,一个Parent默认只能设置一个View的TouchDelegate
EditText焦点:
。android:windowSoftInputMode="stateHidden";
。EditText.clearFocus();
。强制隐藏Android输入法窗口:
EditText edt = (EditText)findViewById(R.id.edt);
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(edt.getWindowToken(), 0);
。EditText始终不弹出虚拟键盘:
EditText.setInputType(InputType.TYPE_NULL);
。让EditText自动获得焦点并弹出软键盘,在设置了EditText自动获得焦点后,软件盘不会弹出。
注:此时是由于刚跳到一个新的界面,界面未加载完全而无法弹出软键盘。此时应该适当的延迟弹出软键盘,如500毫秒(保证界面的数据加载完成,如果500毫秒仍未弹出,则延长至1000毫秒)。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
InputMethodManager inputManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.showSoftInput(editText, 0);
}
}, 500);再给activity配置加入属性:
android:windowSoftInputMode="adjustResize"
updating
位置,跟手dragChildInParentHelper=ViewDragHelper.create(this::vp, sensitivity, new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) {//想好捕获谁了吗 return true; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { } @Override public int clampViewPositionVertical(View child, int top, int dy) { } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) {eg. dragChildInParentHelper.settleCapturedViewAt(x, y);invalidate();}@Override public void onEdgeDragStarted(int edgeFlags, int pointerId){//dragChildInParentHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
dragChildInParentHelper.captureChildView(mEdgeTrackerView, pointerId); }});
接管@Override public boolean onInterceptTouchEvent(MotionEvent event) { return dragChildInParentHelper.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) {dragChildInParentHelper.processTouchEvent(event); return true; }