1、View的坐标参数主要有哪些?分别有什么注意的要点?
Left、Right、top、Bottom : 注意这4个值其实就是 view 和他的父控件的相对坐标值。 并非是距离屏幕左上角的绝对值,这点要注意。
此外,X和Y 其实也是相对于父控件的坐标值。 TranslationX、TranslationY 这2个值默认都为0,是相对于父控件的左上角的偏移量。
换算关系:
x=left+tranX,y=top+tranY.
很多人不理解,为什么这样,其实就是View 如果有移动的话,比如平移这种,你们就要注意了,top和left 这种值是不会变化的。无论你把view怎么拖动,但是 x,y,tranX,tranY 的值是随着拖动平移而变化的。想明白这点就行了。
2、onTouchEvent和GestureDetector 在什么时候用哪个比较好?
只有滑动需求的时候就用前者,如果有双击等这种行为的时候就用后者。
3、Scroller 用来解决什么问题?
view的scrollTo和scrollBy 滑动效果太差了,是瞬间完成。而scroller可以配合view的computeScroll 来完成 渐变的滑动效果。体验更好。
4、ScrollTo和ScrollBy 有什么需要注意的?
ScrollTo是绝对滑动,ScrollBy是相对滑动。滑动的是view的内容而不是view本身。这很重要。比如textview 调用这2个方法 滑动的就是显示出来的字的内容。
一般而言 我们用scrollBy会比较多一些。传值的话其实记住几个法则就可以了。
右-左 x为正 否则x为负 上-下 y为负,否则y为正。
可以稍微看一下这2个方法的源码:
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();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
看到里面有2个变量 mScrollX 和mScrollY 这2个东西没,这2个单位的值是像素,前者代表 view的左边缘和view内容左边缘的距离。 后者代表 view上边缘和view内容上边缘的距离。
5、使用动画来实现view的滑动 有什么后果?
实际上view动画是对view的表面UI也就是给用户呈现出的视觉效果来做的移动,动画本身并不能移动view的真正位置。属性动画除外。动画播放结束以后,view最终还是会回到自己的位置的。当然了你可以设置fillafter 属性来让动画播放结束以后 view表象停留在变化以后的位置。所以这会带来一个很严重的后果。比如你的button在屏幕的左边,你现在用个动画 并且设置了fillafter属性让他去了右边。你会发现点击右边的button 没有click事件触发,但是点击左边的 却可以触发,原因就是右边的button 只是view的表象,真正的button 还在左边没有动过。你一定要这么做的话 可以提前在右边button移动后的位置放一个新的button,当你动画执行结束以后 把右边的enable 左边的让他gone就可以了。这么做就可以规避上述问题。
6、让view滑动总共有几种方式,分别要注意什么?都适用于那些场景?
- scrollto,scrollby。这种是最简单的,但是只能滑动view的内容 不可以滑动view本身。
- 动画。动画可以滑动view内容,但是注意非属性动画就如我们问题5说的内容 会影响到交互,使用的时候要多注意。不过多数复杂的滑动效果都是属性动画来完成的,属于大杀器级别。
- 改变布局参数。这种最好理解了,无非是动态的通过java代码来修改 margin等view的参数罢了。不过用的比较少。我本人不怎么用这种方法。
7.Scroller是干嘛的?原理是什么?
Scroller就是用于 让view有滑动渐变效果的。用法如下:
package com.example.administrator.motioneventtest;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Scroller;
import android.widget.TextView;
/**
* Created by Administrator on 2016/2/2.
*/
public class CustomTextView extends TextView{
private Scroller mScroller;
public CustomTextView(Context context) {
super(context);
mScroller=new Scroller(context);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller=new Scroller(context);
}
public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScroller=new Scroller(context);
}
//调用此方法滚动到目标位置
public void smoothScrollTo(int fx, int fy) {
int dx = fx - mScroller.getFinalX();
int dy = fy - mScroller.getFinalY();
smoothScrollBy(dx, dy);
}
//调用此方法设置滚动的相对偏移
public void smoothScrollBy(int dx, int dy) {
//设置mScroller的滚动偏移量
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy,4000);
invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
}
//使用scroller最重要不要遗漏这个方法
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset())
{
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//这个方法不要忘记调用。
postInvalidate();
}
super.computeScroll();
}
}
8、view的滑动渐变效果总共有几种方法?
三种,第一种是scroller 也是使用最多的。问题7里有解释。还有一种就是动画,动画我就不多说了,不属于本文范畴。最后一种也是我们经常使用的就是用handler ,每隔一个时间间隔 来更新view的状态。
9、view的事件传递机制 如何用伪代码来表示?
/**
* 对于一个root viewgroup来说,如果接受了一个点击事件,那么首先会调用他的dispatchTouchEvent方法。
* 如果这个viewgroup的onInterceptTouchEvent 返回true,那就代表要拦截这个事件。接下来这个事件就
* 给viewgroup自己处理了,从而viewgroup的onTouchEvent方法就会被调用。如果如果这个viewgroup的onInterceptTouchEvent
* 返回false就代表我不拦截这个事件,然后就把这个事件传递给自己的子元素,然后子元素的dispatchTouchEvent
* 就会被调用,就是这样一个循环直到 事件被处理。
*
*/
public boolean dispatchTouchEvent(MotionEvent ev)
{
boolean consume=false;
if (onInterceptTouchEvent(ev)) {
consume=onTouchEvent(ev);
}else
{
consume=child.dispatchTouchEvent(ev);
}
return consume;
}
10、view的onTouchEvent,OnClickListerner和OnTouchListener的onTouch方法 三者优先级如何?
onTouchListener优先级最高,如果onTouch方法返回 false ,那onTouchEvent就被调用了,返回true 就不会被调用。至于onClick 优先级最低。
11、点击事件的传递顺序如何?
Activity-Window-View。从上到下依次传递,当然了如果你最低的那个view的onTouchEvent返回false 那就说明他不想处理 那就再往上抛,都不处理的话,最终就还是让Activity自己处理了。举个例子,pm下发一个任务给leader,leader自己不做 给架构师a,小a也不做 给程序员b,b如果做了那就结束了这个任务。b如果发现自己搞不定,那就找a做,a要是也搞不定 就会不断向上发起请求,最终可能还是pm做。
//activity的dispatchTouchEvent 方法 一开始就是交给window去处理的
//win的superDispatchTouchEvent 返回true 那就直接结束了 这个函数了。返回false就意味
//这事件没人处理,最终还是给activity的onTouchEvent 自己处理 这里的getwindow 其实就是phonewindow
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//来看phonewindow的这个函数 直接把事件传递给了mDecor
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
//devorview就是 我们的rootview了 就是那个framelayout 我们的setContentView里面传递的那个layout
//就是这个decorview的 子view了
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
12、事件分为几个步骤?
down事件开头,up事件结尾,中间可能会有数目不定的move事件。
13、ViewGroup如何对点击事件分发?
viewgroup就是在actionMasked == MotionEvent.ACTION_DOWN 和 mFirstTouchTarget != null 这两种情况来判断是否会进入拦截事件的流程
看代码可以知道 如果是ACTION_DOWN事件 那就肯定进入 是否要拦截事件的流程
如果不是ACTION_DOWN事件 那就要看mFirstTouchTarget != null 这个条件是否成立
这个地方有点绕但是也好理解,其实就是 对于一个事件序列来说 down是事件的开头 所以肯定进入了这个事件是否拦截的流程 也就是if 括号内。
mFirstTouchTarget其实是一个单链表结构他指向的是 成功处理事件的子元素。
也就是说 如果有子元素成功处理了 事件,那这个值就不为NULL。反过来说
只要viewgroup拦截了事件,mFirstTouchTarget就不为NULL,所以括号内就不会执行,也就侧面说明了一个结论:
某个view 一旦决定拦截事件,那么这个事件所属的事件序列 都只能由他来执行。并且onInterceptTouchEvent 这个方法不会被调用了
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); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
14、如果某个view 处理事件的时候 没有消耗down事件 会有什么结果?
假如一个view,在down事件来的时候 他的onTouchEvent返回false, 那么这个down事件 所属的事件序列 就是他后续的move 和up 都不会给他处理了,全部都给他的父view处理。
15、如果view 不消耗move或者up事件 会有什么结果?
那这个事件所属的事件序列就消失了,父view也不会处理的,最终都给activity 去处理了。
16、ViewGroup 默认拦截事件吗?
默认不拦截任何事件,onInterceptTouchEvent返回的是false。
17、一旦有事件传递给view,view的onTouchEvent一定会被调用吗?
是的,因为view 本身没有onInterceptTouchEvent方法,所以只要事件来到view这里 就一定会走onTouchEvent方法。
并且默认都是消耗掉,返回true的。除非这个view是不可点击的,所谓不可点击就是clickable和longgclikable同时为fale。Button的clickable就是true,但是textview是false。
18、enable是否影响view的onTouchEvent返回值?
不影响,只要clickable和longClickable有一个为真,那么onTouchEvent就返回true。
19、requestDisallowInterceptTouchEvent 可以在子元素中干扰父元素的事件分发吗?如果可以,是全部都可以干扰吗?
肯定可以,但是down事件干扰不了。
20、dispatchTouchEvent每次都会被调用吗?
是的,onInterceptTouchEvent则不会。
21、滑动冲突问题如何解决 思路是什么?
要解决滑动冲突,其实最主要的就是有一个核心思想。你到底想在一个事件序列中,让哪个view 来响应你的滑动?比如从上到下滑,是哪个view来处理这个事件,从左到右呢?
用业务需求来想明白以后,剩下的其实就很好做了。核心的方法就是2个:外部拦截也就是父亲拦截,另外就是内部拦截,也就是子view拦截法。 学会这2种基本上所有的滑动冲突都是这2种的变种,而且核心代码思想都一样。
- 外部拦截法:思路就是重写父容器的onInterceptTouchEvent即可。子元素一般不需要管。可以很容易理解,因为这和android自身的事件处理机制逻辑是一模一样的。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
//down事件肯定不能拦截 拦截了后面的就收不到了
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (你的业务需求) {
//如果确定拦截了 就去自己的onTouchEvent里 处理拦截之后的操作和效果 即可了
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
//up事件 我们一般都是返回false的 一般父容器都不会拦截他。 因为up是事件的最后一步。这里返回true也没啥意义
//唯一的意义就是因为 父元素 up被拦截。导致子元素 收不到up事件,那子元素 就肯定没有onClick事件触发了,这里的小细节 要想明白
intercepted = false;
break;
default:
break;
}
return intercepted;
}
- 内部拦截法:内部拦截法稍微复杂一点,就是事件到来的时候,父容器不管,让子元素自己来决定是否处理。如果消耗了 就最好,没消耗 自然就转给父容器处理了。
子元素代码:
父亲容器代码也要修改一下,其实就是保证父亲别拦截down:@Override public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: if (如果父容器需要这个点击事件) { getParent().requestDisallowInterceptTouchEvent(false); }//否则的话 就交给自己本身view的onTouchEvent自动处理了 break; case MotionEvent.ACTION_UP: break; default: break; } return super.dispatchTouchEvent(event); }
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { return false; } return true; }
22、requestLayout、invalidate与postInvalidate作用与区别
View绘制分三个步骤,顺序是:onMeasure,onLayout,onDraw。经代码亲测,log输出显示:调用invalidate方法只会执行onDraw方法;调用requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。
所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。
- requestLayout : 当当前布局的宽高发生改变的时候, 此时需要重新调用父view的onMeaure和onLayout, 来给子view重新排版布局
- invalidate : 让页面刷新, 重新调用onDraw方法
- postInvalidate : 在子线程来让页面来进行刷新的方法