面试题:View相关

Q1:MotionEvent是什么?包含几种事件?什么条件下会产生?
Q2:scrollTo()和scrollBy()的区别?
Q3:Scroller中最重要的两个方法是什么?主要目的是?
Q4:谈一谈View的事件分发机制?
Q5:如何解决View的滑动冲突?
Q6:谈一谈View的工作原理?
Q7:MeasureSpec是什么?有什么作用?
Q8:onTouch()、onTouchEvent()和onClick()关系?
Q9:SurfaceView和View的区别?
Q10:invalidate()和postInvalidate()的区别?

要点提炼|开发艺术之View

Q1:MotionEvent是什么?包含几种事件?什么条件下会产生?

MotionEvent:是手指触摸屏幕锁产生的一系列事件。典型事件有:

  • ACTION_DOWN:手指刚接触屏幕
  • ACTION_MOVE:手指在屏幕上滑动
  • ACTION_UP:手指在屏幕上松开的一瞬间

事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件
任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件。如图:

Q2:scrollTo()和scrollBy()的区别?

Andrid_scrollTo/scrollBy方法的区别

Q3:Scroller中最重要的两个方法是什么?主要目的是?

跟Q2相同。

Q4:谈一谈View的事件分发机制?

Android事件分发机制详解:史上最全面、最易懂

a.事件分发本质:就是对MotionEvent事件分发的过程。即当一个MotionEvent产生了以后,系统需要将这个点击事件传递到一个具体的View上。(关于MotionEvent介绍见本篇2.a)

b.点击事件的传递顺序:Activity(Window) -> ViewGroup -> View

c.需要的三个主要方法:

  • dispatchTouchEvent:进行事件的分发(传递)。返回值是 boolean 类型,受当前onTouchEvent和下级view的dispatchTouchEvent影响

  • onInterceptTouchEvent:对事件进行拦截。该方法只在ViewGroup中有,View(不包含 ViewGroup)是没有的。一旦拦截,则执行ViewGroup的onTouchEvent,在ViewGroup中处理事件,而不接着分发给View。且只调用一次,所以后面的事件都会交给ViewGroup处理。

  • onTouchEvent:进行事件处理。

事件分发过程图:

  • 事件分发是逐级下发的,目的是将事件传递给一个View。
  • ViewGroup一旦拦截事件,就不往下分发,同时调用onTouchEvent处理事件。

Q5:如何解决View的滑动冲突?

a.产生原因:

  • 一般情况下,在一个界面里存在内外两层可同时滑动的情况时,会出现滑动冲突现象。

b.可能场景:

  • 外部滑动和内部滑动方向不一致:如ViewPager嵌套ListView(实际这么用没问题,因为ViewPager内部已处理过)。
  • 外部滑动方向和内部滑动方向一致:如ScrollView嵌套ListView(实际上也已被解决)。
  • 上面两种情况的嵌套

c.处理规则:

  • 对场景一:当用户左右/上下滑动时让外部View拦截点击事件,当用户上下/左右滑动时让内部View拦截点击事件。即根据滑动的方向判断谁来拦截事件。关于判断是上下滑动还是左右滑动,可根据滑动的距离或者滑动的角度去判断。
  • 对场景二:一般从业务上找突破点。即根据业务需求,规定何时让外部View拦截事件何时由内部View拦截事件。
  • 对场景三:相对复杂,可同样根据需求在业务上找到突破点。

d.解决方式:

  • 法一:外部拦截法
    • 含义:指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。
    • 方法:需要重写父容器的onInterceptTouchEvent方法,在内部做出相应的拦截。以下是代码:
//重写父容器的拦截方法
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://对于ACTION_DOWN事件必须返回false,一旦拦截后续事件将不能传递给子View
         intercepted = false;
         break;
      case MotionEvent.ACTION_MOVE://对于ACTION_MOVE事件根据需要决定是否拦截
         if (父容器需要当前事件) {
             intercepted = true;
         } else {
             intercepted = flase;
         }
         break;
   }
      case MotionEvent.ACTION_UP://对于ACTION_UP事件必须返回false,一旦拦截子View的onClick事件将不会触发
         intercepted = false;
         break;
      default : break;
   }
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
   }

法二:内部拦截法

  • 含义:指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。
  • 方法:需要配合requestDisallowInterceptTouchEvent方法。以下是子View的dispatchTouchEvent方法的代码:
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);//为true表示禁止父容器拦截
         break;
      case MotionEvent.ACTION_MOVE:
         int deltaX = x - mLastX;
         int deltaY = y - mLastY;
         if (父容器需要此类点击事件) {
             parent.requestDisallowInterceptTouchEvent(false);
         }
         break;
      case MotionEvent.ACTION_UP:
         break;
      default :
         break;        
 }

  mLastX = x;
  mLastY = y;
  return super.dispatchTouchEvent(event);
}

除子容器需要做处理外,父容器也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子容器调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。因此,父View需要重写onInterceptTouchEvent方法:

public boolean onInterceptTouchEvent (MotionEvent event) {
 int action = event.getAction();
 if(action == MotionEvent.ACTION_DOWN) {
     return false;
 } else {
     return true;
 }
}

内部拦截法要求父容器不能拦截ACTION_DOWN的原因:由于该事件并不受FLAG_DISALLOW_INTERCEPT(由requestDisallowInterceptTouchEvent方法设置)标记位控制,一旦ACTION_DOWN事件到来,该标记位会被重置。所以一旦父容器拦截了该事件,那么所有的事件都不会传递给子View,内部拦截法也就失效了。

Q6:谈一谈View的工作原理?

1.View工作流程:measure测量->layout布局->draw绘制

  • measure确定View的测量宽高
  • layout确定View的最终宽高四个顶点的位置
  • draw将View 绘制到屏幕
  • 对应onMeasure()、onLayout()、onDraw()三个方法。

具体过程:

  • ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带。
  • View的绘制流程是从ViewRootperformTraversals开始。
  • performTraversals()依次调用performMeasure()、performLayout()和performDraw()三个方法,分别完成顶级 View的绘制。
  • 其中,performMeasure()会调用measure(),measure()中又调用onMeasure(),实现对其所有子元素的measure过程,这样就完成了一次measure过程;接着子元素会重复父容器的measure过程,如此反复至完成整个View树的遍历。layout和draw同理。过程图如下:

Q7:MeasureSpec是什么?有什么作用?

View的MeasureSpec确定过程(onMeasure分析)

Q8:onTouch()、onTouchEvent()和onClick()关系?

onTouch和onTouchEvent以及onClick的顺序

OnTouch事件和onTouchEvent事件

Q9:SurfaceView和View的区别?

surfaceView和View区别

surfaceView和View的区别

View与SurfaceView

Q10:invalidate()和postInvalidate()的区别?

android中Invalidate和postInvalidate的区别

猜你喜欢

转载自blog.csdn.net/songzi1228/article/details/82772052