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);
public customtextview(context context, attributeset attrs, int defstyleattr) {
super(context, attrs, defstyleattr);
//調用此方法滾動到目标位置
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() {
//傳回true就代表滑動還沒結束 false就是結束了
//其實這個方法 就跟屬性動畫裡的插值器一樣 你在使用startscroll方法的時候 會傳一個事件的值,
//這個方法就是根據這個事件的值來計算你每一次scrollx和scrolly的值
public boolean computescrolloffset() {
if (mfinished) {
return false;
int timepassed = (int)(animationutils.currentanimationtimemillis() - mstarttime);
if (timepassed < mduration) {
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 < nb_samples) {
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 <= mmaxx
mcurrx = math.min(mcurrx, mmaxx);
mcurrx = math.max(mcurrx, mminx);
mcurry = mstarty + math.round(distancecoef * (mfinaly - mstarty));
// pin to mminy <= mcurry <= mmaxy
mcurry = math.min(mcurry, mmaxy);
mcurry = math.max(mcurry, mminy);
if (mcurrx == mfinalx && mcurry == mfinaly) {
mfinished = true;
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事件 會有什麼結果?
答:假如一個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;
case motionevent.action_move:
if (你的業務需求) {
//如果确定攔截了 就去自己的ontouchevent裡 處理攔截之後的操作和效果 即可了
intercepted = true;
case motionevent.action_up:
//up事件 我們一般都是傳回false的 一般父容器都不會攔截他。 因為up是事件的最後一步。這裡傳回true也沒啥意義
//唯一的意義就是因為 父元素 up被攔截。導緻子元素 收不到up事件,那子元素 就肯定沒有onclick事件觸發了,這裡的
//小細節 要想明白
default:
return intercepted;
内部攔截法:内部攔截法稍微複雜一點,就是事件到來的時候,父容器不管,讓子元素自己來決定是否處理。如果消耗了 就最好,沒消耗 自然就轉給父容器處理了。
子元素代碼:
public boolean dispatchtouchevent(motionevent event) {
int x = (int) event.getx();
int y = (int) event.gety();
switch (event.getaction()) {
getparent().requestdisallowintercepttouchevent(true);
if (如果父容器需要這個點選事件) {
getparent().requestdisallowintercepttouchevent(false);
}//否則的話 就交給自己本身view的ontouchevent自動處理了
return super.dispatchtouchevent(event);
父親容器代碼也要修改一下,其實就是保證父親别攔截down:
本文作者:佚名
來源:51cto