Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android程序開發——事件分發機制

android程序開發——事件分發機制

編輯:關於Android編程

1.android布局層次

\

在我們的布局之上還存在四層布局, 所以我們應該減少布局的嵌套

2.事件分發

Android 中與 Touch 事件相關的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能夠響應這些方法的控件包括:ViewGroup 及其子類、Activity。

@1

其中:

dispatchTouchEvent(MotionEvent ev) 事件分發 Activity ViewGroup View 都有此方法

onInterceptTouchEvent(MotionEvent ev) 事件攔截 ViewGroup 有此方法

onTouchEvent(MotionEvent ev) 事件響應 Activity ViewGroup View 都有此方法

onTouch:

onTouch()是OnTouchListener接口的方法,它是獲取某一個控件的觸摸事件,因此使用時,必須使用setOnTouchListener綁定到控件,然後才能鑒定該控件的觸摸事件。當一個View綁定了OnTouchLister後,當有touch事件觸發時,就會調用onTouch方法。通過getAction()方法可以獲取當前觸摸事件的狀態:
ACTION_DOWN:表示按下了屏幕的狀態。
ACTION_MOVE :表示為移動手勢
ACTION_UP :表示為離開屏幕
ACTION_CANCEL :表示取消手勢,不會由用戶產生,而是由程序產生的

onTouch方法的優先級高於onTouchEvent。當onTouch 方法返回true時,會進行觸摸事件處理,onTouchEvent不會處理。當onTouch返回false的時候,onTouchEvent擦會處理

@2

正常情況下:activitydispatchTouchEvent -->> layoutdispatchTouchEvent ->> buttondispatchTouchEvent -->button onTouchevent

@--1當被onInterceptTouchEvent攔截時,會執行當前的onTouchEvent

@--2 當onTouchEvent 返回false時,返回給上一層的onTouchEvent ,如果還返回false,繼續返回上一層的onTouchEvent ,直至拋出

事件分發:public boolean dispatchTouchEvent(MotionEvent ev)
當有監聽到事件時,首先由Activity的捕獲到,進入事件分發處理流程。無論是Activity還是View,如前文所說,事件分發自身也具有消費能力,
如果事件分發返回true,表示改事件在本層不再進行分發且已經在事件分發自身中被消費了。至此,事件已經完結。如果你不想Activity中的任何控件具有任何的事件消費能力,
最簡答的方法可以重寫此Activity的dispatchTouchEvent方法,直接返回true就ok。
如果事件分發返回 false,表明事件在本層不再繼續進行分發,並交由上層控件的onTouchEvent方法進行消費。
當然了,如果本層控件已經是Activity,那麼事件將被系統消費或處理。
如果事件分發返回系統默認的 super.dispatchTouchEvent(ev),事件將分發給本層的事件攔截onInterceptTouchEvent 方法進行處理
(如果本層控件是Activity,由於其沒有事件攔截,因此將直接將事件傳遞到子View,並交給子View的事件分發進行處理)。
事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)
如果 onInterceptTouchEvent 返回 true,則表示將事件進行攔截,並將攔截到的事件交由本層控件 的 onTouchEvent 進行處理;
如果返回結果是false;則表示不對事件進行攔截,事件得以成功分發到子View。並由子View的dispatchTouchEvent進行處理。
如果返回super.onInterceptTouchEvent(ev),事件默認不會被攔截,交由子View的dispatchTouchEvent進行處理。
事件響應:public boolean onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 並且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情況下 onTouchEvent 會被調用。

onTouchEvent 的事件響應邏輯如下:
如果事件傳遞到當前 View 的 onTouchEvent 方法,而該方法返回了 false,那麼這個事件會從當前 View 向上傳遞,並且都是由上層 View 的 onTouchEvent 來接收,如果傳遞到上面的 onTouchEvent 也返回 false,這個事件就會“消失”,而且接收不到下一次事件。
如果返回了 true 則會接收並消費該事件。
如果返回 super.onTouchEvent(ev) 默認處理事件的邏輯和返回 false 時相同。

(我個人的判斷是最裡面的 返回false 則 其上層所有的onTouchEvent的的super 默認為false)

\

 

ScrollView 和 ListView的沖突(@1 ScrollView 嵌套Listview @2 ScrollView 嵌套一個ScrollView)

@1 ScrollView 嵌套一個ListView

原因:

@1. 因為ListView需要支持長按操作,所以ListView本身的ACTION_MOVE,會有一定的初始距離檢測,小於這個距離,代表沒有移動,只有從按下到移動之間坐標位置相差大 一點,才回認為ListView需要移動;
@2. ScrollView 在檢測滾動事件的時候,如果ScrollView的內容的高度>ScrollView顯示的高度,就認為內部的內容可以滾動,是整體滾動;ScrollView在上下滾動的時候如果 DOWN的位置與當前MOVE的位置相差很小,認為沒有滾動;
@3. 當按下位置與移動的位置超過一個固定的距離之後,ScrollView再分發事件的時候,就會檢測出可以滾動,那麼因為ScrollView內容高度可以滾動,那麼就直接滾動自身 內 容,而不把滾動事件,傳遞給ListView了
@4. 根據代碼的分析,ScrollView判斷是否開始滾動,采用的是 滾動幅度是否大於8dp的方式,ListView也是采用滾動幅度>8dp的檢測;只要>8的時候,ScrollView就會先收到 事件,然後把事件就給攔截了;不再給 ListView 了;

解決方案:

@1. 重寫 ListView 的 onMeasure() 讓ListView計算自身所有內容的高度,全部填滿ScrollView
@2. 這種方案會產生效率的問題,因為一次會把所有的Item全部顯示出來;沒有復用;一定要注意,通常都是顯示簡單的文字信息和小圖片;
@3. ListView在ScrollView中,高度測量的時候,傳遞的模式就是 “未指定”,所以ListView默認就會計算一行的高度;
@4. 解決方案就是在ListView計算尺寸之前,強制把 未指定 調整為AT_MOST, 指定的高度可以是一個非常大的數值,這樣全部顯示出來;

 

不可取的方法(原因:需要計算出ListView的高度,全部展現出來,這就有一個隱患:不敢加圖片 否則卡出翔)

原始方法,易理解:

要在listView添加完adapter後 計算高度,在之前好像貌似沒用

 

public static void setListViewHeight(ListView listView){
        if (listView == null){
            return;
        }
        ListAdapter adapter = listView.getAdapter();
        if (adapter == null) {
            return;
        }
        int Height = 0;
        /**
         * 內容的長度
         */
        for (int i = 0; i < adapter.getCount(); i++) {
            View adapterView = adapter.getView(i, null, listView);
            adapterView.measure(0,0);
            Height += adapterView.getMeasuredHeight();
        }
        ViewGroup.LayoutParams params = listView.getLayoutParams();
        /**
         * (listView.getDividerHeight()*(adapter.getCount() - 1)
         * ListView各個item的間隙長度
         */
        params.height = Height + (listView.getDividerHeight()*(adapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
簡單方法,只需要重寫ListView裡的onMeasure方法即可
package com.treasure_ct.android_xt.seniorcontrols.eventdistribution.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.AbsListView;
import android.widget.ListView;

/**
 * Created by treasure on 2016.09.27.
 */

public class MyListView extends ListView{

    public MyListView(Context context) {
        super(context);
    }

    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int spec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, spec);
    }
}

可取的方法(將可以滑動的權限調配,當手指點到LV的時候,不將滾動事件給父控件即SV 點到非LV時 吧滾動事件交給父控件SV)

 

重寫ListView裡的onInterceptTouchEvent方法

 

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                setParentScrollAble(false);//手指觸到listview的時候,讓父ScrollView交出ontouch權限,也就是讓父scrollview停住不能滾動
                Log.d(TAG, "onInterceptTouchEvent: down");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onInterceptTouchEvent: move");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onInterceptTouchEvent: up");
                break;
            case MotionEvent.ACTION_CANCEL:
                setParentScrollAble(true);//當手指松開時,讓父ScrollView重新拿到onTouch權限
                Log.d(TAG, "onInterceptTouchEvent: cancel");
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    /**
     * 是否把滾動事件交給父ScrollView
     */
    private void setParentScrollAble(boolean flag){
        getParent().requestDisallowInterceptTouchEvent(!flag);
    }
@2 ScrollView嵌套一個HorizontalScrollView 解決卡頓情況

 

網上的辦法

 

package com.treasure_ct.android_xt.seniorcontrols.eventdistribution.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;

/**
 * Created by treasure on 2016.09.27.
 */

public class MyScrollView2 extends ScrollView {
    private GestureDetector mGestureDetector;
    View.OnTouchListener mOnTouchListener;
    public MyScrollView2(Context context) {
        super(context);
    }

    public MyScrollView2(Context context, AttributeSet attrs) {
        super(context, attrs);
        mGestureDetector = new GestureDetector(new YScrollDetector());
        setFadingEdgeLength(0);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
    }
    //如果Y角移動的絕對值大於X軸移動的絕對值,即縱向滑動返回true
    class YScrollDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (Math.abs(distanceY) > Math.abs(distanceX)){
                return true;
            }
            return false;
        }
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved