专栏名称: 安卓开发精选
伯乐在线旗下账号,分享安卓应用相关内容,包括:安卓应用开发、设计和动态等。
目录
相关文章推荐
开发者全社区  ·  华为青浦的工作餐,被网友调侃太贵...... ·  11 小时前  
开发者全社区  ·  WC!阿里P7总包超150w ·  21 小时前  
开发者全社区  ·  AI提升成人玩具用户体验 ·  昨天  
开发者全社区  ·  离谱瓜!约会8个女同事的大厂男 ·  昨天  
开发者全社区  ·  人大发TT了 ·  3 天前  
51好读  ›  专栏  ›  安卓开发精选

Android View事件机制 21问21答

安卓开发精选  · 公众号  · android  · 2017-01-09 21:07

正文

(点击 上方公众号 ,可快速关注)


来源:希尔瓦娜斯女神

http://www.cnblogs.com/punkisnotdead/p/5179115.html

如有好文章投稿,请点击 → 这里了解详情


Android View事件机制 21问21答


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 有什么需要注意的?


答:前者是绝对滑动,后者是相对滑动。滑动的是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滑动总共有几种方式,分别要注意什么?都适用于那些场景?


答:总共有三种:


a:scrollto,scrollby。这种是最简单的,但是只能滑动view的内容 不可以滑动view本身。


b:动画。动画可以滑动view内容,但是注意非属性动画 就如我们问题5说的内容 会影响到交互,使用的时候要多注意。不过多数复杂的滑动效果都是属性动画来完成的,属于大杀器级别、


c:改变布局参数。这种最好理解了,无非是动态的通过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();

}

}


其实上述代码 很多人应该都能搜到。我们这里主要讲一下 他的原理。


//参数很好理解 前面滑动起始点 中间滑动距离 最后一个是 渐变时间

//而且我们看到startScroll 这个方法就是设置了一下参数 并没有什么滑动的代码在

//回到前面的demo能看到我们通常调用完这个方法以后 都会马上调用invalidate()方法

public void startScroll(int startX, int startY, int dx, int dy, int duration) {

mMode = SCROLL_MODE;

mFinished = false;

mDuration = duration;

mStartTime = AnimationUtils.currentAnimationTimeMillis();

mStartX = startX;

mStartY = startY;

mFinalX = startX + dx;

mFinalY = startY + dy;

mDeltaX = dx;

mDeltaY = dy;

mDurationReciprocal = 1.0f / (float) mDuration;

}



//我们都知道invalidate 会触发view的 draw方法

//我们跟进去看 会发现draw方法里 会调用下面的代码:

//也就是说会调用    computeScroll方法 而view本身这个方法

//是空的所以会留给我们自己实现

int sx = 0;

int sy = 0;

if (!drawingWithRenderNode) {

computeScroll();

sx = mScrollX;

sy = mScrollY;

}



//然后回到我们的customtextview 可以看到我们实现的    computeScroll方法如下:

//你看在这个方法里 我们调用了scrollTo方法 来实现滑动,滑动结束以后再次触发view的重绘

//然后又会再次触发computeScroll 实现一个循环。

public void computeScroll() {

if (mScroller.computeScrollOffset())

{

scrollTo(mScroller.getCurrX(),mScroller.getCurrY());

//这个方法不要忘记调用。

postInvalidate();

}

super.computeScroll();

}



//返回true就代表滑动还没结束 false就是结束了

//其实这个方法 就跟属性动画里的插值器一样 你在使用startScroll方法的时候 会传一个事件的值,

//这个方法就是根据这个事件的值来计算你每一次scrollx和scrolly的值

public boolean computeScrollOffset() {

if (mFinished) {

return false;

}


int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

if (timePassed

switch (mMode) {

case SCROLL_MODE:

final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);

mCurrX = mStartX + Math.round(x * mDeltaX);

mCurrY = mStartY + Math.round(x * mDeltaY);

break;

case FLING_MODE:

final float t = (float) timePassed / mDuration;

final int index = (int) (NB_SAMPLES * t);

float distanceCoef = 1.f;

float velocityCoef = 0.f;

if (index

final float t_inf = (float) index / NB_SAMPLES;

final float t_sup = (float) (index + 1) / NB_SAMPLES;

final float d_inf = SPLINE_POSITION[index];

final float d_sup = SPLINE_POSITION[index + 1];

velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);

distanceCoef = d_inf + (t - t_inf) * velocityCoef;

}


mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;

mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));

// Pin to mMinX

mCurrX = Math.min(mCurrX, mMaxX);

mCurrX = Math.max(mCurrX, mMinX);

mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));

// Pin to mMinY

mCurrY = Math.min(mCurrY, mMaxY);

mCurrY = Math.max(mCurrY, mMinY);


if (mCurrX == mFinalX && mCurrY == mFinalY) {

mFinished = true;

}


break;

}

}

else {

mCurrX = mFinalX;

mCurrY = mFinalY;

mFinished = true;

}

return true;

}


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事件 会有什么结果?







请到「今天看啥」查看全文


推荐文章
开发者全社区  ·  华为青浦的工作餐,被网友调侃太贵......
11 小时前
开发者全社区  ·  WC!阿里P7总包超150w
21 小时前
开发者全社区  ·  AI提升成人玩具用户体验
昨天
开发者全社区  ·  离谱瓜!约会8个女同事的大厂男
昨天
开发者全社区  ·  人大发TT了
3 天前
视觉志  ·  对方回复了你的消息
8 年前
智囊团达人  ·  教你们一个约炮的暗号
7 年前