一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

[原]Android L中水波紋點(diǎn)擊效果的實(shí)現(xiàn)

 花荊 2015-10-22

博主參加了2014 CSDN博客之星評選,幫我投一票吧。

點(diǎn)擊給我投票

前言

前段時(shí)間android L(android 5.0)出來了,界面上做了一些改動(dòng),主要是添加了若干動(dòng)畫和一些新的控件,相信大家對view的點(diǎn)擊效果-水波紋很有印象吧,點(diǎn)擊一個(gè)view,然后一個(gè)水波紋就會(huì)從點(diǎn)擊處擴(kuò)散開來,本文就來分析這種效果的實(shí)現(xiàn)。首先,先說下L上的實(shí)現(xiàn),這種波紋效果,L上提供了一種動(dòng)畫,叫做Reveal效果,其底層是通過拿到view的canvas然后不斷刷新view來完成的,這種效果需要view的支持,而在低版本上沒有view的支持,因此,Reveal效果沒法直接在低版本運(yùn)行。但是,我們了解其效果、其原理后,還是可以通過模擬的方式去實(shí)現(xiàn)這種效果,平心而論,寫出一個(gè)具有波紋效果的自定義view不難,或者說很簡單,但是,view的子類很多,如果要一一去實(shí)現(xiàn)button、edit等控件,這樣比較繁瑣,于是,我們想是否有更簡單的方式呢?其實(shí)是有的,我們可以寫一個(gè)自定義的layout,然后讓layout中所有可點(diǎn)擊的元素都具有波紋效果,這樣做,就大大簡化了整個(gè)過程。接下來本文就會(huì)分析這個(gè)layout的實(shí)現(xiàn),在此之前,我們先看下效果。

實(shí)現(xiàn)思想

首先我們自定義一個(gè)layout,這里我們選取LinearLayout,至于原因,文章下面會(huì)進(jìn)行分析。當(dāng)用戶點(diǎn)擊一個(gè)可點(diǎn)擊的元素時(shí),比如button,我們需要得到用戶點(diǎn)擊的元素的信息,包含:用戶點(diǎn)擊了哪個(gè)元素、用戶點(diǎn)擊的那個(gè)元素的寬、高、位置信息等。得到了button的信息后,我就可以確定水波紋的范圍,然后通過layout進(jìn)行重繪去繪制水波紋,這樣水波紋效果就實(shí)現(xiàn)了,當(dāng)然,這只是大概步驟,中間還是有一些細(xì)節(jié)需要處理的。

layout的選取

既然我們打算實(shí)現(xiàn)一個(gè)自定義layout,那我們要選取那個(gè)layout呢,LinearLayout、RelativeLayout、FrameLayout?我這里選用LinearLayout。為什么呢?也許有人會(huì)問,不應(yīng)該用RelativeLayout嗎?因?yàn)镽elativeLayout比較強(qiáng)大,可以實(shí)現(xiàn)復(fù)雜的布局,但LinearLayout和FrameLayout就不行。沒錯(cuò),RelativeLayout是強(qiáng)大,但是考慮到水波效果是通過頻繁刷新layout來實(shí)現(xiàn)的,由于頻繁重繪,因此,我們要考慮性能問題,RelativeLayout的性能是最差的(因?yàn)樽龅氖虑槎啵?,因?yàn)椋瑸榱诵阅?,我們選擇LinearLayout,至于FrameLayout,它功能太簡單了,不太適合使用。當(dāng)實(shí)現(xiàn)復(fù)雜布局的時(shí)候,我們可以在具有波紋效果的元素外部包裹LinearLayout,這樣重繪的時(shí)候不至于有過重的任務(wù)。

根據(jù)上面的分析,我們定義如下的layout:

public class RevealLayout extends LinearLayout implements Runnable

實(shí)現(xiàn)過程

實(shí)現(xiàn)過程主要是如下幾個(gè)問題的解決:

1. 如何得知用戶點(diǎn)擊了哪個(gè)元素

2. 如何取得被點(diǎn)擊元素的信息

3. 如何通過layout進(jìn)行重繪繪制水波紋

4. 如果延遲up事件的分發(fā)

下面一一進(jìn)行分析

如何得知用戶點(diǎn)擊了哪個(gè)元素

這個(gè)問題好弄,為了得知用戶點(diǎn)擊了哪個(gè)元素(這個(gè)元素一般來說要是可點(diǎn)擊的,否則是無意義的),我們要提前攔截所有的點(diǎn)擊事件,于是,我們應(yīng)該重寫layout中的dispatchTouchEvent方法,注意,這里不推薦用onInterceptTouchEvent,因?yàn)閛nInterceptTouchEvent不是一直會(huì)被回調(diào)的,具體原因請參看我之前寫的view系統(tǒng)解析系列。然后當(dāng)用戶點(diǎn)擊的時(shí)候,會(huì)有一系列的down、move、up事件,我們要在down的時(shí)候來確定事件落在哪個(gè)元素上,down的元素就是用戶點(diǎn)擊的元素,當(dāng)然為了嚴(yán)謹(jǐn),我們還要判斷up的時(shí)候是否也落在同一個(gè)元素上面,因?yàn)?,系統(tǒng)click事件的判斷規(guī)則就是:down和up同時(shí)落在同一個(gè)可點(diǎn)擊的元素上。

@Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    int x = (int) event.getRawX();
    int y = (int) event.getRawY();
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
      View touchTarget = getTouchTarget(this, x, y);
      if (touchTarget.isClickable() && touchTarget.isEnabled()) {
        mTouchTarget = touchTarget;
        initParametersForChild(event, touchTarget);
        postInvalidateDelayed(INVALIDATE_DURATION);
      }
    } else if (action == MotionEvent.ACTION_UP) {
      mIsPressed = false;
      postInvalidateDelayed(INVALIDATE_DURATION);
      mDispatchUpTouchEventRunnable.event = event;
      postDelayed(mDispatchUpTouchEventRunnable, 400);
      return true;
    } else if (action == MotionEvent.ACTION_CANCEL) {
      mIsPressed = false;
      postInvalidateDelayed(INVALIDATE_DURATION);
    }
    return super.dispatchTouchEvent(event);
  }

通過上述代碼,我們可以知道,當(dāng)down的時(shí)候,我們?nèi)〕鳇c(diǎn)擊事件的屏幕坐標(biāo),然后去遍歷view樹找到用戶所點(diǎn)擊的那個(gè)view,代碼如下,就是判斷事件的坐標(biāo)是否落在view的范圍內(nèi),這個(gè)不再多說了,比較好理解。需要注意的是,事件的坐標(biāo)我們不能用getX和getY,而要用getRawX和getRawY,二者的區(qū)別是:前者是相對于被點(diǎn)擊view的坐標(biāo),后者是相對于屏幕的坐標(biāo),而我們的目標(biāo)view具體位于layout的哪一層我們無法知道,所以,必須用屏幕的絕對坐標(biāo)來進(jìn)行計(jì)算。而有了事件的坐標(biāo),再根據(jù)view在屏幕中的絕對坐標(biāo),只要判斷事件的xy是否落在view的上下左右四個(gè)角之內(nèi),就可以知道事件是否落在view上,從而取出用戶所點(diǎn)擊的那個(gè)view。

private View getTouchTarget(View view, int x, int y) {
    View target = null;
    ArrayList<View> TouchableViews = view.getTouchables();
    for (View child : TouchableViews) {
      if (isTouchPointInView(child, x, y)) {
        target = child;
        break;
      }
    }
    return target;
  }
  private boolean isTouchPointInView(View view, int x, int y) {
    int[] location = new int[2];
    view.getLocationOnScreen(location);
    int left = location[0];
    int top = location[1];
    int right = left + view.getMeasuredWidth();
    int bottom = top + view.getMeasuredHeight();
    if (view.isClickable() && y >= top && y <= bottom
        && x >= left && x <= right) {
      return true;
    }
    return false;
  }

如何取得被點(diǎn)擊元素的信息

這個(gè)比較簡單,被點(diǎn)擊元素的信息有:寬、高、left、top、right、bottom,獲取它們的代碼如下:

int[] location = new int[2];
  mTouchTarget.getLocationOnScreen(location);
  int left = location[0] - mLocationInScreen[0];
  int top = location[1] - mLocationInScreen[1];
  int right = left + mTouchTarget.getMeasuredWidth();
  int bottom = top + mTouchTarget.getMeasuredHeight();

說明:mTouchTarget指的是用戶點(diǎn)擊的那個(gè)view

如何通過layout進(jìn)行重繪繪制水波紋

這個(gè)會(huì)水波紋比較簡單,只要用drawCircle繪制一個(gè)半透明的圓環(huán)即可,這里主要說下繪制時(shí)機(jī)。一般來說,我們會(huì)選擇在onDraw中去進(jìn)行繪制,這是沒錯(cuò)的,但是對于L中的效果不太適合,查看view的繪制過程,我們會(huì)明白,view的繪制大致遵循如下流程:先繪制背景,再繪制自己(onDraw),接著繪制子元素(dispatchDraw),最后繪制一些裝飾等比如滾動(dòng)條(onDrawScrollBars),因此,如果我們在onDraw中繪制波紋,那么由于子元素的繪制在onDraw之后,就會(huì)導(dǎo)致子元素蓋住我們所繪制的圓環(huán),這樣,圓環(huán)就有可能看不全了,因?yàn)?,把我繪制的時(shí)機(jī)很重要。根據(jù)view的繪制流程,我們選擇dispatchDraw比較合適,當(dāng)所有的子元素都繪制完成后,再進(jìn)行波紋的繪制。讀到這里,大家會(huì)更加明白,為什么我們要選擇LinearLayout以及為什么不建議view的嵌套層級太深,因?yàn)槿绻鹶iew本身比較重或者嵌套層級太深,就會(huì)導(dǎo)致dispatchDraw執(zhí)行的耗時(shí)增加,這樣水波的繪制就會(huì)收到些許影響。因此,性能的平滑在代碼中也很重要,也是需要考慮的。同時(shí),為了不讓繪制的圓環(huán)超出被點(diǎn)擊元素的范圍,我們需要對canvas進(jìn)行clip。為了有波紋效果,我們需要頻繁地進(jìn)行l(wèi)ayout重繪,并且在重繪的過程中改變圓環(huán)的半徑,這樣一個(gè)動(dòng)態(tài)的水波紋就出來了。仍然,我來性能的考慮,我們選擇用postInvalidateDelayed(long delayMilliseconds, int left, int top, int right, int bottom)來進(jìn)行view的部分重繪,因?yàn)?,其他區(qū)域是不需要重繪的,僅僅是被點(diǎn)擊的元素所在的區(qū)域需要重繪。為什么要采用Delayed這個(gè)方法,原因是我們不能一直進(jìn)行刷新,必須有一點(diǎn)點(diǎn)時(shí)間間隔,這樣做的好處是:避免view的重繪搶占過多時(shí)間片從而造成潛在的間接棧溢出,因?yàn)閕nvalidate會(huì)直接導(dǎo)致draw的調(diào)用。

具體代碼如下:

protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {
      return;
    }
    if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {
      mRevealRadius += mRevealRadiusGap * 4;
    } else {
      mRevealRadius += mRevealRadiusGap;
    }
    int[] location = new int[2];
    mTouchTarget.getLocationOnScreen(location);
    int left = location[0] - mLocationInScreen[0];
    int top = location[1] - mLocationInScreen[1];
    int right = left + mTouchTarget.getMeasuredWidth();
    int bottom = top + mTouchTarget.getMeasuredHeight();
    canvas.save();
    canvas.clipRect(left, top, right, bottom);
    canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);
    canvas.restore();
    if (mRevealRadius <= mMaxRevealRadius) {
      postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
    } else if (!mIsPressed) {
      mShouldDoAnimation = false;
      postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
    }
  }

到此為止,這個(gè)layout我們已經(jīng)實(shí)現(xiàn)了,但是細(xì)心的你,一定會(huì)發(fā)現(xiàn),還有什么不妥的地方。比如,你可以給button加一個(gè)點(diǎn)擊事件,當(dāng)button被點(diǎn)擊的時(shí)候起一個(gè)activity,很快你就會(huì)發(fā)現(xiàn)問題所在了:水波還沒播完呢,activity就起來了,導(dǎo)致水波效果大打折扣,而仔細(xì)觀察android L的效果,我們發(fā)現(xiàn),L中總是要等到水波效果播放完畢才會(huì)進(jìn)行下一步的行為。所以,最后一個(gè)待解決的問題也就出來了,請看下面的分析

如何延遲up事件的分發(fā)

針對上面所說的問題,如果我們能夠延遲up時(shí)間的分發(fā),比如延遲400ms,這樣水波就有足夠的時(shí)間去播放完畢,然后再分發(fā)up事件,這樣就可以解決問題。最開始,我的確是這樣做的,先看如下的代碼:

else if (action == MotionEvent.ACTION_UP) {
  mIsPressed = false;
  postInvalidateDelayed(INVALIDATE_DURATION);
  mDispatchUpTouchEventRunnable.event = event;
  postDelayed(mDispatchUpTouchEventRunnable, 400);
  return true;
        }

可以發(fā)現(xiàn),當(dāng)up的時(shí)候,我并沒有直接走系統(tǒng)的分發(fā)流程,只是強(qiáng)行消耗點(diǎn)up事件然后再延遲分發(fā),請看代碼:

private class DispatchUpTouchEventRunnable implements Runnable {
  public MotionEvent event;
  @Override
  public void run() {
      if (mTouchTarget == null || !mTouchTarget.isEnabled()) {
    return;
      }
      if (isTouchPointInView(mTouchTarget, (int)event.getRawX(), (int)event.getRawY())) {
    mTouchTarget.dispatchTouchEvent(event);
      }
  }
    };

到此為止,上述幾個(gè)問題都已經(jīng)分析完畢了,我們就可以輕易地實(shí)現(xiàn)水波紋的點(diǎn)擊效果了。

源碼下載

本文中的demo源碼暫時(shí)未開放到互聯(lián)網(wǎng)上,請加群 215680213 ,在群共享中下載源碼。 

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    夫妻激情视频一区二区三区| 自拍偷拍一区二区三区| 日韩精品一级片免费看| 亚洲天堂精品一区二区| 国产又色又粗又黄又爽| 欧美乱码精品一区二区三| 日韩精品视频香蕉视频| 精品香蕉一区二区在线| 黄片三级免费在线观看| 国产免费一区二区三区av大片| 99视频精品免费视频| 久久精品久久精品中文字幕| 青青免费操手机在线视频| 91久久国产福利自产拍| 五月的丁香婷婷综合网| 国产自拍欧美日韩在线观看| 暴力性生活在线免费视频| 熟妇久久人妻中文字幕| 91麻豆精品欧美视频| 午夜福利激情性生活免费视频| 台湾综合熟女一区二区| 免费在线播放一区二区| 亚洲天堂精品在线视频| 国产精品成人一区二区在线| 日韩中文字幕在线不卡一区| 国产在线不卡中文字幕| a久久天堂国产毛片精品| 成人国产激情福利久久| 中文久久乱码一区二区| 深夜视频在线观看免费你懂| 日韩欧美二区中文字幕| 国产免费无遮挡精品视频| 日本一二三区不卡免费| 免费观看一区二区三区黄片| 国产一区二区三区不卡| 九九热国产这里只有精品| 日韩精品人妻少妇一区二区| 日韩精品一区二区三区含羞含羞草| 日韩精品一区二区一牛| 青青操在线视频精品视频| 亚洲中文字幕乱码亚洲|