Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> View的事件體系

View的事件體系

編輯:關於Android編程

1.View的位置參數
這裡寫圖片描述

2.MotionEvent
手指觸摸屏幕後的一系列事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP

3.TouchSlop
系統所能識別的被認為是滑動的最小距離,獲取方式為:

ViewConfiguration.get(getContext()).getScaledTouchSlop()

4.VelocityTracker速度追蹤
VelocityTracker的使用方式

       //初始化
       VelocityTracker mVelocityTracker = VelocityTracker.obtain();
       //在onTouchEvent方法中
       mVelocityTracker.addMovement(event);
       //獲取速度
       mVelocityTracker.computeCurrentVelocity(1000);
       float xVelocity = mVelocityTracker.getXVelocity();
       //重置和回收
       mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的時候調用
       mVelocityTracker.recycle(); //一般在onDetachedFromWindow中調用

5.GestureDetector
GestureDetector用於輔助檢測用戶的單擊、滑動、長按、雙擊等行為
使用方法:

先創建GestureDetector 並實現onGestureListener接口,需要時還可以實現OnDoubleTapListener實現雙擊行為
GestureDetector mGestureDetector = new GestureDetector(this);
//解決長按屏幕後無法拖動現象
mGestureDetector .setIsLongpressEnabled(false);
然後接管view的onTouchEvent方法,添加如下實現
return mGestureDetector.onTouchEvent(event);

做完以上步驟就可以有選擇的實現onGestureListener和OnDoubleTapListener的方法(具體實現這裡不詳細說明)
建議:如果只是監聽滑動相關的事件在onTouchEvent中實現;如果要監聽雙擊這種行為的話,那麼就使用GestureDetector。

6.Scroller
當使用View的scrollBy和scrollTo的時候,過程是瞬間完成的,用戶體驗差。因此,可以使用scroller來實現有過渡效果的滑動
使用方法

Scroller scroller = new Scroller(getContext());

//緩慢移動到指定位置
private void smoothScrollBy(int dx, int dy) {
        int delta = dx - getScrollX();
        //500ms內劃向dx
        scroller.startScroll(getScrollX(), 0, delta, 0, 500);
        invalidate();
}

@Override
public void computeScroll() {
    if (scroller.computeScrollOffset()) {
        scrollTo(scroller.getCurrX(), scroller.getCurrY());
        postInvalidate();
    }
}

7.View的滑動
(1)使用scrollTo/scrollBy

/**
   * Set the scrolled position of your view. This will cause a call to
   * {@link #onScrollChanged(int, int, int, int)} and the view will be
   * invalidated.
   * @param x the x position to scroll to
   * @param y the y position to scroll to
   */
  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();
      }
    }
  }

/**
  * Move the scrolled position of your view. This will cause a call to
  * {@link #onScrollChanged(int, int, int, int)} and the view will be
  * invalidated.
  * @param x the amount of pixels to scroll by horizontally
  * @param y the amount of pixels to scroll by vertically
  */
    public void scrollBy(int x, int y) {
     scrollTo(mScrollX + x, mScrollY + y);
    }

可以看出,scrollBy其實調用了scrollTo函數。scrollTo滑動到絕對位置,scrollBy是相對位置。兩者只能改變view內容的位置,不能改變在布局中的位置。
(2)使用動畫
網上例子很多,不再說明
(3)改變布局參數
比如讓一個button向右平移100px可以用如下代碼

        MarginLayoutParams params = (MarginLayoutParams)button.getLayoutParams();
        params.width += 100;
        params.leftMargin += 100;
        button.requestLayout();
        //或者button。setLayoutParams(params)

(4)各種滑動方式對比
使用scrollTo/scrollBy:操作簡單,適合對View內容的滑動
動畫:操作簡單,主要適合於沒有交互的view和實現復雜的動畫效果
改變布局參數:操作稍微復雜,適用於有交互的view

8.彈性滑動
(1)使用scroller
使用方法之前已經講過,現在看一下如何實現的。主要是startScroll和computeScrollOffset兩個函數

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;  
    // This controls the viscous fluid effect (how much of it)  
    mViscousFluidScale = 8.0f;  
    // must be set to 1.0 (used in viscousFluid())  
    mViscousFluidNormalize = 1.0f;  
    mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);  
} 

在這個方法中我們只看到了對一些滾動的基本設置動作,比如設置滾動模式,開始時間,持續時間等等,並沒有任何對View的滾動操作
其實整個過程是這樣的:View重繪後會在draw方法中調用computeScroll,這是個空方法,要自己實現

@Override
public void computeScroll() {
    if (scroller.computeScrollOffset()) {
        scrollTo(scroller.getCurrX(), scroller.getCurrY());
        postInvalidate();
    }
}

在我們自己實現的方法中先調用scrollTo再調用postInvalidate進行第二次重繪,然後調用draw函數,這樣如此反復知道滑動結束
再看一下computeScrollOffset方法

public boolean computeScrollOffset() {  
    if (mFinished) {  
        return false;  
    }  

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

    if (timePassed < mDuration) {  
        switch (mMode) {  
        case SCROLL_MODE:  
            float x = (float)timePassed * mDurationReciprocal;  

            if (mInterpolator == null)  
                x = viscousFluid(x);   
            else  
                x = mInterpolator.getInterpolation(x);  

            mCurrX = mStartX + Math.round(x * mDeltaX);  
            mCurrY = mStartY + Math.round(x * mDeltaY);  
            break;  
        case FLING_MODE:  
            float timePassedSeconds = timePassed / 1000.0f;  
            float distance = (mVelocity * timePassedSeconds)  
                    - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);  

            mCurrX = mStartX + Math.round(distance * mCoeffX);  
            // Pin to mMinX <= mCurrX <= mMaxX  
            mCurrX = Math.min(mCurrX, mMaxX);  
            mCurrX = Math.max(mCurrX, mMinX);  

            mCurrY = mStartY + Math.round(distance * mCoeffY);  
            // Pin to mMinY <= mCurrY <= mMaxY  
            mCurrY = Math.min(mCurrY, mMaxY);  
            mCurrY = Math.max(mCurrY, mMinY);  

            break;  
        }  
    }  
    else {  
        mCurrX = mFinalX;  
        mCurrY = mFinalY;  
        mFinished = true;  
    }  
    return true;  
}  

它根據時間的流逝來計算當前的scrollX和scrollY的值
(2)動畫
(3)延時策略
通過handler的sendEmptyMessageDelayed方法

8.事件分發機制

9.滑動沖突
解決方法
1.外部攔截法:點擊事件都先經過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要就不攔截。該方法需要重寫父容器的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: {
         intercepted = false;
         break;
     }
     case MotionEvent.ACTION_MOVE: {
         int deltaX = x - mLastXIntercept;
         int deltaY = y - mLastYIntercept;
         if (父容器需要攔截當前點擊事件的條件) {
             intercepted = true;
         } else {
             intercepted = false;
         }
         break;
     }
     case MotionEvent.ACTION_UP: {
         intercepted = false;
         break;
     }
     default:
         break;
     }

     mLastXIntercept = x;
     mLastYIntercept = y;

     return intercepted;
 }

2.內部攔截法:父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交給父容器來處理。這種方法和Android中的事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。我們需要重寫子元素的dispatchTouchEvent

   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: {
           int deltaX = x - mLastX;
           int deltaY = y - mLastY;
           if (父容器需要點擊事件) {
               getParent().requestDisallowInterceptTouchEvent(false);
           }
           break;
       }
       case MotionEvent.ACTION_UP: {
           break;
       }
       default:
           break;
       }
       mLastX = x;
       mLastY = y;
       return super.dispatchTouchEvent(event);
   }

當然,不能攔截父容器的ACTION_DOWN事件

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

為什麼不能攔截呢?因為攔截了ACTION_DOWN,會導致所有時間都無法傳遞到子元素

看該文的最後,scrollview和listview的滑動沖突
我決定采用外部攔截法。因此,我需要自定義MyScrollView來繼承ScrollView並重寫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:
            Log.e("ACTION_DOWN", "ACTION_DOWN");
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e("ACTION_MOVE", "ACTION_MOVE");
            //getChildAt(0)得到的就是listview
            if (y > getChildAt(0).getBottom()) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            Log.e("ACTION_UP", "ACTION_UP");
            intercepted = false;
            break;
        }

        Log.e("intercepted", "" + intercepted);

        return intercepted;
    }

這樣,完美解決

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved