Androidタッチスクリーンイベント配布メカニズムの詳細とソース分析一(View編)

25101 ワード

記事参照先:http://blog.csdn.net/yanbober/article/details/45887547作者の无私な分かち合いに感谢して、私はここでただ自分の学习と総括をします.まず、サンプルプログラムcodeです.
package com.qs.clickeventdispatchdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.LinearLayout;

public class MainActivity extends Activity implements OnClickListener, OnTouchListener{
    private MyButton myButton ;
    private LinearLayout myLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myButton = (MyButton) findViewById(R.id.myButton);
        myLayout = (LinearLayout) findViewById(R.id.myLayout);
        myButton.setOnClickListener(this);
        myButton.setOnTouchListener(this);
        myLayout.setOnTouchListener(this);
        myLayout.setOnClickListener(this);

    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        PrintLog.show_parent("  onTouch "+event.getAction()+" "+v+"----->");
        return false;
    }
    @Override
    public void onClick(View v) {
        PrintLog.show_parent("onClick execute  "+v+"----->");
    }

}

activity_main.xmlファイル:
"http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/myLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
     >

    <com.qs.clickeventdispatchdemo.MyButton
        android:id="@+id/myButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="   " />


システムからのButtonクリックイベントについては説明しませんが、最上位のリンクを参照してください.ここでは、まず構造:java.lang.Object|--android.view.View|--android.widget.TextView|--android.widget.widget.TextView|--android.widget.Button Android触摸屏事件派发机制详解与源码分析一(View篇)_第1张图片をまとめます.buttonはViewのサブクラスに属し、LinearLayoutはView Groupのサブクラスに属する.Buttonを書き換えていない場合:1.onTouch実行はonClickの前に行われます.dispatchTouchEventで呼び出され、onTouchEventを呼び出すかどうかを決定します.また、action=0は下にaction=2を押してaction=1を上に移動します.2.onClick実行はaction=1も弾けた後に3.onTouchを実行するのは戻り値があり、デフォルトでfalseを返します.onClickイベントは実行され、trueが返されます.onClickイベントは、カスタムButtonでcodeを深く理解する実行プロセスを実行しません.
public class MyButton extends Button {

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        PrintLog.show_myButton(event.getAction()+" dispatchTouchEvent ");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        PrintLog.show_myButton(event.getAction()+" onTouchEvent ");
        return super.onTouchEvent(event);
    }

}

Android触摸屏事件派发机制详解与源码分析一(View篇)_第2张图片図から完全な実行プロセスを見ることができます.まず、dispatchTouchEvent-->onTouch->>onTouchEvent->onClickの下に、ソースコードがどのように書かれているかを見てみましょう.
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

Androidでのクリックイベント配信はまずdispatchTouchEventを呼び出すためこの関数から見ると他のクラスでは実現していないので、Viewでの定義を見ると、onTouchはonClickの前で実行されていると言います.li.mOnTouchListener.onTouch(this,event)という言葉がif文で呼び出されているのがわかります.この判断条件liはListenerInfo li=mListenerInfo;——->mListenerInfo:
    ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

mListenerInfoが単一のインスタンスであることに相当し、li.mOnTouchListener付与関数を使用します.
  public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

これにより、Activityで実装されたインタフェースオブジェクトが渡されるので、if判定文の前の2つの条件はtrueになります.3番目の条件Buttonのデフォルト属性はenableです.そのため、決定権は4番目の条件の上にあります.onTouchメソッドを呼び出して、戻り値を判定します.trueであれば、次のifに入ることなく直接戻ります.(onTouchEvent)では、これまでonClick関数の呼び出しは見られなかったので、onClickはonTouchEventで呼び出されたに違いないと考えられています.では、codeを追いかけ続けます.
public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true);
                       }

                        if (!mHasPerformedLongPress) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }
            return true;
        }

        return false;
    }

ソースコードが少し長いので、大きなif判断if((((viewFlags&CLICKABLE)==CLICKABLE|(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE))を見て、おなじみのswitch(event.getAction(){case MotionEvent.ACTION_UP:.....私たちのclickイベントの呼び出しをスキップし続けるとperformClick();関数については、onClickがupイベントの後に印刷されたと思うので、upでonClick呼び出しをしたに違いありません.
   public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }

やっとli.mOnClickListener.onClick(this)を見ました;この文では、上記のListerインタフェースの割り当てにより、ActivityでListerオブジェクトが設定され、up後にonClickイベントが呼び出され、最後にonClickの内容が印刷されると判断しました.そのため、プロセス全体が終了しました.buttonをカスタマイズした後、次の状況を見てみましょう.1.dispatchTouchEventがsuを呼び出さずに直接trueに戻るとper.dispatchTouchEvent(event);这里写图片描述親メソッドを呼び出さずにtrueを直接返すと、私たちは何もしていないに違いない.onTouchもonClickメソッドも呼び出さない.falseに直接戻ると、Android触摸屏事件派发机制详解与源码分析一(View篇)_第3张图片 downイベントがトリガーされただけで、upイベントもトリガーされていない.次はなぜこんなに多くのイベントが出てくるのか.注意深く観察する必要があります.このオブジェクトはLinearLayoutオブジェクトであり、私たちのbuttonオブジェクトではありません.そのため、View Groupに関連しています.ここではdispatchTouchEventがfalseに戻ることを知っていれば、次のサブクラスはこのクリックイベントを消費できないことを示し、親クラスに戻ってクリックイベントを消費することを示しています.そのため、LinearLayoutのクリックイベントの実行がトリガーされ、falseに戻った後残りの移動や上向きのクリックイベントは上には伝わりませんので、0しか見えません.2 1は見えません.
2.super.dispatchTouchEvent(event)が呼び出され、trueが返されると、onTouchおよびonClick動作が多くなるだけである:Android触摸屏事件派发机制详解与源码分析一(View篇)_第4张图片上図のように、buttonをカスタマイズしていないときの呼び出し順序と同じであるかどうか、オブジェクトがLinearLayoutではなくMyButtonで印刷されていることに注意してください.呼び出しはtrueを返し、super.dispatchTouchEvent(event)に直接戻るためです.;親クラスを呼び出してデフォルトで返されるのもtrueなので、違いはありません.
3.return super.dispatchTouchEvent(event);の場合、onTouchはfalseを返します.dispatchTouchEventのactionが切断されているかどうかは、onTouchEventという関数の戻り値を見てみましょう.trueを直接返す場合
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        PrintLog.show_myButton(event.getAction()+" onTouchEvent ");
//      return super.onTouchEvent(event);
        return true;
    }

Android触摸屏事件派发机制详解与源码分析一(View篇)_第5张图片でonTouchEventの呼び出しが表示されなかったのは、onTouchが直接trueに戻ったためであり、まだtrueに戻っていないためであり、onTouchを変更してfalseに戻ると、Android触摸屏事件派发机制详解与源码分析一(View篇)_第6张图片でonTouchEventイベントが呼び出されたことが明らかになったが、onClickは呼び出されていない.親クラスを呼び出すこの方法がないため、直接faに戻るとlse、イベントは必ず遮断されます.onTouchはfalseを返し、onTouchEventメソッドに入り、falseを返します.dispatchTouchEvent全体がfalseを返します.
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        PrintLog.show_myButton(event.getAction()+" dispatchTouchEvent ");
         return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        PrintLog.show_myButton(event.getAction()+" onTouchEvent ");
//      return super.onTouchEvent(event);
        return false;
    }

Android触摸屏事件派发机制详解与源码分析一(View篇)_第7张图片は、MyButtonオブジェクトに対してsuper.onTouchEvent(event)が呼び出された場合、押下されたイベントのみを見ることができます.;その後falseに戻り、上記と何の変化もなく、onClickは実行しないため、upはそこに渡されず、trueを返すと、onClickイベントの呼び出しが1つ増えただけでAndroid触摸屏事件派发机制详解与源码分析一(View篇)_第8张图片**
まとめ:
**1.onTouchEventを呼び出すかどうかはonTouch戻り値に依存し、trueは呼び出されず、falseが呼び出されます.2.切断されるかどうかはdispatchTouchEvent戻り値に依存し、falseは切断され、trueは切断されません.3.onClickを呼び出すかどうかはonTouchEventが呼び出されたかどうかに依存します.4.dispatchTouchEventが戻り値であり、親を呼び出さない場合、onTouchEventは呼び出されない5.onTouchEventが親クラスを呼び出さずに値を返すだけである場合、onClickは呼び出されません.
何か悪いところがあったら、皆さんに教えてください.ありがとうございます.