【Android】图文解密Android View

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/iEearth/article/details/86169732

1、简介

首先,简单介绍一下大家比较熟悉的Android View构成,如下图所示。

在这里插入图片描述

一个App可以有多个Activity。Activity是Android的四大组件之一,四大组件ABCS即A-Activity,B-BroadcastReceiver,C-ContentProvider,S-Service。每个Activity都有一个Window,Window是个abstract类,目前的唯一实现类为PhoneWindow,PhoneWindow是一种policy,从名字来看,可能还会有WatchWindow、TvWindow、CarWindow等。另外,还有一种特殊的Window,悬浮窗,这里不作详细介绍。PhoneWindow中的top-level View是DecorView,DecorView实际上是ViewGroup。DecorView包含DecorCaptureView和ContentRoot,ContentRoot便是各种父子关系的View树。下图是相关的class图。

在这里插入图片描述

2、View与ViewGroup

本文主要介绍View及ViewGroup。View是个基本的UI控件,在屏幕上占据一个矩形区域,负责描画和处理事件。一些Widget如TextView、ImageView等是View的子类。ViewGroup也是View的子类,ViewGroup作为View的容器本身不可见,常见的Layout如RelativeLayout、LinearLayout等便是ViewGroup的子类。下图是相关的class图。

在这里插入图片描述

3、UiThread

View和ViewGroup使用了UiThread注解,表示必须在UI线程即主线程中使用,包括构造函数以及其它所有的函数调用。相关代码如下。

@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {}

@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {}

4、属性

Android定义了一些属性,可以在xml中设置,也可以在java中使用。以TextView Widget的text属性为例,如下例子在xml中将text设置为“Hello World!”,text的xmlns为andriod,然后在java中使用R.id获取TextView对象后,通过getText()获取text属性,通过setText()设置text属性为“xxx”。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
package com.example.view;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends Activity {
    private static final String TAG = "ViewTest";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.text_view);
        String text = textView.getText().toString();
        Log.d(TAG, "MainActivity onCreate xml text:" + text);
        textView.setText("xxx");
        text = textView.getText().toString();
        Log.d(TAG, "MainActivity onCreate set text:" + text);
    }
}

Log输出如下。

2019-01-14 14:16:09.856 10278-10278/com.example.view D/ViewTest: MainActivity onCreate xml text:Hello World!
2019-01-14 14:16:09.857 10278-10278/com.example.view D/ViewTest: MainActivity onCreate set text:xxx

下面我们来分析一下xml与java是如何把TextView的text属性联系在一起的。TextView在xml中可以设置哪些属性,可以在源码frameworks/base/core/res/res/values/attrs.xml中看到,格式固定,内容如下。

    <declare-styleable name="TextView">
        <!-- Text to display. -->
        <attr name="text" format="string" localization="suggested" />
    </declare-styleable>

在java中,通过getText()得知,text属性值保存在一个类型为CharSequence的mText成员变量中,代码如下所示。

    // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
    @ViewDebug.ExportedProperty(category = "text")
    @UnsupportedAppUsage
    private @Nullable CharSequence mText;

    @ViewDebug.CapturedViewProperty
    public CharSequence getText() {
        return mText;
    }

setText()有5个版本,如下所示。

    @android.view.RemotableViewMethod
    public final void setText(CharSequence text) {}
    public void setText(CharSequence text, BufferType type) {}
    public final void setText(char[] text, int start, int len) {}
    @android.view.RemotableViewMethod
    public final void setText(@StringRes int resid) {}
    public final void setText(@StringRes int resid, BufferType type) {}

setText()最终都是通过setTextInternal()实现的,从中可以确定text属性值确实是保存在了mText成员变量中,代码如下所示。

    // Update mText and mPrecomputed
    private void setTextInternal(@Nullable CharSequence text) {
        mText = text;
        mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
        mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
    }

下面来看一下TextView的构造函数中是如何解析xml中设置的text属性的。在构造函数中,通过R.styleable.TextView获取TypedArray类型的AttributeSet,然后通过R.styleable.TextView_text获取text属性值,默认为空字符串,代码如下所示。

    public TextView(Context context) {
        this(context, null);
    }

    public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.textViewStyle);
    }

    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    @SuppressWarnings("deprecation")
    public TextView(
            Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setTextInternal("");
        final Resources.Theme theme = context.getTheme();
        TypedArray a = theme.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case com.android.internal.R.styleable.TextView_text:
                    textIsSetFromXml = true;
                    mTextId = a.getResourceId(attr, ResourceId.ID_NULL);
                    text = a.getText(attr);
                    break;
            }
        }
        a.recycle();
        BufferType bufferType = BufferType.EDITABLE;
        // ...
        setText(text, bufferType);
    }

下面是TextView的构造函数中解析text属性的时序图。

在这里插入图片描述

我们可以根据TextView的text属性的用法,自定义属性,format支持integer、string、boolean、float、fraction、enum、color、dimension、flags、reference十种格式。首先在res/values/attrs.xml中定义属性,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomView">
        <attr name="name" format="string" />
        <attr name="number" format="integer" />
    </declare-styleable>
</resources>

同样在java中解析自定义的属性,代码如下所示。

package com.example.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class CustomView extends View {
    private static final String TAG = "ViewTest";

    public CustomView(Context context) {
        this(context, null);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
        String name = array.getString(R.styleable.CustomView_name);
        int number = array.getInt(R.styleable.CustomView_number, 0);
        array.recycle();
        Log.d(TAG, "CustomView name:" + name);
        Log.d(TAG, "CustomView number:" + number);
    }
}

layout中对于CustomView和自定义属性的用法如下代码所示。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.view.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:name="Tom"
        app:number="18" />

</android.support.constraint.ConstraintLayout>

Log输出如下。

2019-01-14 14:16:09.844 10278-10278/com.example.view D/ViewTest: CustomView name:Tom
2019-01-14 14:16:09.844 10278-10278/com.example.view D/ViewTest: CustomView number:18

在java中获取xml中的一个View对象,通过id获取,id是一个View的身份,用于区分不同的View。除此之外,还有个tag属性,tag用来记载View的额外信息,使用tag的好处是方便存取,而不用将这些信息放在独立于View的其它结构中。

5、focus

focus表示UI交互的元素,可以是Activity、Window、View,拥有focus即表示当前元素可以与UI进行交互,如响应input事件。下图先列出View和ViewGroup中与focus相关的函数,只是列出了函数名和返回值类型,没有列出具体的参数。

在这里插入图片描述

从函数名大致可以明白这个函数的作用。其中,onWindowFocusChanged表示View所在Window的focus发生变化时会调用这个函数,onFocusChanged表示View的focus发生变化时会调用这个函数,还可以通过setOnFocusChangeListener设置View的focus变化的Listener以监听focus变化。另外,isFocused表示View是否拥有focus,isFocusable表示View是否支持focus。

下面分析View中onWindowFocusChanged的源码实现。onWindowFocusChanged是dispatchWindowFocusChanged调用的,在onWindowFocusChanged中,主要作了三件事情:一是在Window没有focus时设置press状态为false,这个很容易理解,同时移除LongPress和Tap的Callback,因为LongPress和Tap属于Gesture,而Gesture依赖于press状态;二是设置InputMethodManager对应的focus状态,设置为focusIn或focusOut;最后通过refreshDrawableState刷新View状态。源码如下所示。

    public void dispatchWindowFocusChanged(boolean hasFocus) {
        onWindowFocusChanged(hasFocus);
    }

    public void onWindowFocusChanged(boolean hasWindowFocus) {
        InputMethodManager imm = InputMethodManager.peekInstance();
        if (!hasWindowFocus) {
            if (isPressed()) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
                imm.focusOut(this);
            }
            removeLongPressCallback();
            removeTapCallback();
            onFocusLost();
        } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
            imm.focusIn(this);
        }

        refreshDrawableState();
    }

View中onFocusChanged的源码实现与onWindowFocusChanged类似,不同的是添加了CallSuper注解,表示View的子类重写onFocusChanged时需要调用父类即View本身的onFocusChanged另外还通过注册的监听focus的Listener通知focus变化。源码片段如下所示。

    @CallSuper
    protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
            @Nullable Rect previouslyFocusedRect) {
        // ...
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnFocusChangeListener != null) {
            li.mOnFocusChangeListener.onFocusChange(this, gainFocus);
        }
        // ...
    }

6、Listener

View中提供了很多Listener,如上面提到的OnFocusChangeListener,用于监听View变化,这些Listener保存在一个ListenerInfo的class中,如下代码所示。

    static class ListenerInfo {
        /**
         * Listener used to dispatch focus change events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        @UnsupportedAppUsage
        protected OnFocusChangeListener mOnFocusChangeListener;

        /**
         * Listeners for layout change events.
         */
        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;

        protected OnScrollChangeListener mOnScrollChangeListener;

        /**
         * Listeners for attach events.
         */
        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;

        /**
         * Listener used to dispatch click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        @UnsupportedAppUsage
        public OnClickListener mOnClickListener;

        /**
         * Listener used to dispatch long click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        @UnsupportedAppUsage
        protected OnLongClickListener mOnLongClickListener;

        /**
         * Listener used to dispatch context click events. This field should be made private, so it
         * is hidden from the SDK.
         * {@hide}
         */
        protected OnContextClickListener mOnContextClickListener;

        /**
         * Listener used to build the context menu.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        @UnsupportedAppUsage
        protected OnCreateContextMenuListener mOnCreateContextMenuListener;

        @UnsupportedAppUsage
        private OnKeyListener mOnKeyListener;

        @UnsupportedAppUsage
        private OnTouchListener mOnTouchListener;

        @UnsupportedAppUsage
        private OnHoverListener mOnHoverListener;

        @UnsupportedAppUsage
        private OnGenericMotionListener mOnGenericMotionListener;

        @UnsupportedAppUsage
        private OnDragListener mOnDragListener;

        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;

        OnCapturedPointerListener mOnCapturedPointerListener;

        private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
    }

	@UnsupportedAppUsage
    ListenerInfo mListenerInfo;

这些Listener一般都有getter/setter函数,如下代码中的OnFocusChangeListener。

    @UnsupportedAppUsage
    ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

    public OnFocusChangeListener getOnFocusChangeListener() {
        ListenerInfo li = mListenerInfo;
        return li != null ? li.mOnFocusChangeListener : null;
    }

    public void setOnFocusChangeListener(OnFocusChangeListener l) {
        getListenerInfo().mOnFocusChangeListener = l;
    }

Listener中的注解UnsupportedAppUsage表示不公开到SDK,通过SDK无法直接使用这些内容。

7、Visibility

View的Visibility有三种状态:visible、invisible和gone。其中,visible表示可见,invisible表示不可见,gone也表示不可见,更深层次的意思是invisible虽然不可见但仍占据layout空间,而gone则是彻底的不可见,不占据layout空间。也就说是,在某些情况下,gone会导致其它View重新布局,如LinearLayout。Visibility操作的函数如下代码所示。

    @ViewDebug.ExportedProperty(mapping = {
        @ViewDebug.IntToString(from = VISIBLE,   to = "VISIBLE"),
        @ViewDebug.IntToString(from = INVISIBLE, to = "INVISIBLE"),
        @ViewDebug.IntToString(from = GONE,      to = "GONE")
    })
    @Visibility
    public int getVisibility() {
        return mViewFlags & VISIBILITY_MASK;
    }

    @RemotableViewMethod
    public void setVisibility(@Visibility int visibility) {
        setFlags(visibility, VISIBILITY_MASK);
    }

在View的属性或状态中,并不是将这些属性或状态直接保存在某个变量中,而是通过一个Flag记录,对这个Flag进行位操作,这是一种好的编程思维。

当View的Visibility发生变化时,会调用onVisibilityChanged,如果是View所在Window的Visibility发生变化时会调用onWindowVisibilityChanged。View在Window中,当View添加到Window中时会调用onAttachedToWindow,当View从Window中移除时会调用onDetachedFromWindow。另外,当从xml中加载layout时,加载完成后,会调用onFinishInflate。

8、Geometry

View是个矩形,包括x和y、width和height两对基本属性,单位为pixel,坐标原点在屏幕左上角,一般是相对坐标,即子View相对于父View的位置。View的这两对Geometry属性又与其它的属性相关,下面逐个介绍。

在这里插入图片描述

  • left:Child View左边距离Parent View左边的大小。

  • right:Child View右边距离Parent View左边的大小。

  • top:Child View上边距离Parent View上边的大小。

  • bottom:Child View下边距离Parent View上边的大小。

  • elevation:Child View在Z轴方向上相对于Parent View的深度。

  • translationX:View移动时相对于left的位置。

  • translationY:View移动时相对于top的位置。

  • translationZ:View移动时相对于elevation的位置

  • x:View在x轴方向上的实际位置,x = left + translationX。

  • y:View在y轴方向上的实际位置,y = top + translationY。

  • z:View在z轴方向上的实际位置,z = elevation + translationZ。

  • width:View的实际宽度,width = right - left。

  • height:View的实际高度,height = bottom - top。

  • measuredWidth:measure阶段的测量值,与width可能不同。

  • measuredHeight:measure阶段的测量值,与height可能不同。

  • paddingLeft:Child View与Parent View在左边保留的最小间歇。

  • paddingRight:Child View与Parent View在右边保留的最小间歇。

  • paddingTop:Child View与Parent View在上边保留的最小间歇。

  • paddingBottom:Child View与Parent View在下边保留的最小间歇。

  • paddingBegin:从左到右布局时,等于paddingLeft,否则等于paddingRight。

  • paddingEnd:同paddingBegin相反。

另外,ViewGoup中添加了margin属性,类似于padding属性,不同的是,padding属性影响的是Child,margin属性影响的是自身。

上面的位置属性都是相对的,有对应的getter/setter函数,如果获取绝对坐标即相对于屏幕原点的大小,可通过下面的三个函数获取。

  • getLocationInWindow
  • getLocationOnScreen
  • getGlobalVisibleRect

另外,对于MotionEvent来说,getX、getY返回相对坐标,getRawX、getRawY返回绝对坐标。

9、Measure、Layout、Draw

Android View的三大流程是measure、layout和draw,对应的都有个Callback即onMeasure、onLayout和onDraw,下图先从时序上大致了解下这三大流程。

在这里插入图片描述

首先来看看measure。measure即测量,目的是测量一个View应该有多大,子View的大小受父View大小的影响,这个影响通过MeasureSpec指定。MeasureSpec有三种模式,其中UNSPECIFIED表示父View对子View没有任何约束,EXACTLY表示父View限制了子View的大小为具体的值,如xml文件中指定的具体的值或match_parent,AT_MOST表示父View指定了子View的最大值,如xml文件中指定的wrap_content。ViewGroup在测量时,通过measureChildren循环遍历所有的子View,当子View的状态不为GONE时,通过measureChild测量这个子View的大小,这时需要通过getChildMeasureSpec获得MeasureSpec,然后调用子View的measure进行测量。View的measure,主要的工作就是调用了onMeasure,onMeasure是个真正测量的地方,我们可以Override这个onMeasure做我们想做的事情,最后测量结果保存在了mMeasuredWidth和mMeasuredHeight。

layout即布局,在measure阶段之后,用于指定View的位置和大小。layout过程中,具体代码实现由setFrame完成,当layout变化时,会调用onSizeChanged以及onLayout和相关的监听Layout变化的Listener。我们可以Override onLayout,如RelativeLayout的onLayout,它会循环遍历所有的子View,调用子View的layout函数。

draw即描画,在ViewGroup中由dispatchDraw发起,然后通过drawChild调到View的draw函数,一个View如何渲染在draw函数指定,依赖于View所属的layer类型和是否硬件加速。draw包括如下七个步骤。

  1. 描画背景,drawBackground。
  2. 如果必要,保存canvas的layout用以fading绘制。
  3. 描画View内容,onDraw。
  4. 描画孩子,dispatchDraw。
  5. 如果必要,绘制fading并恢复之前保存的layer。
  6. 描画装饰,如scrollbar,onDrawForeground。
  7. 描画默认的高亮focus,drawDefaultFocusHighlight。

10、Event

接收事件最简单的方法是通过形如setOnXxxListener的函数设置对应事件的监听者,常用的有如下几个函数。

  • setOnTouchListener:Touch事件,最基本的事件,包括down、up、move、cancel等。
  • setOnClickListener:Click事件,即快速touch down、up。
  • setOnLongClickListener:Long Click事件,即touch down、keep touch、up。
  • setOnDragListener:Drag事件,即touch down、move。
  • setOnKeyListener:Key事件。

此外,还有如下几个设置事件监听者的函数。

  • setOnGenericMotionListener
  • setOnContextClickListener
  • setOnHoverListener
  • setOnCapturedPointerListener

当我们自定义View时,可以Override形如OnXxxEvent的函数,也可以接收对应的事件,有如下几个函数。

  • onTouchEvent
  • onDragEvent
  • onKeyDown
  • onKeyUp
  • onKeyLongPress
  • onKeyMultiple
  • onKeyPreIme
  • onKeyShortcut
  • onTrackballEvent
  • onGenericMotionEvent
  • onHoverEvent
  • onCancelPendingInputEvents
  • onFilterTouchEventForSecurity
  • onCapturedPointerEvent

在ViewGroup中,有如下几个Event相关的函数。

  • onInterceptTouchEvent:拦截Touch事件。
  • onInterceptHoverEvent:拦截Hover事件。
  • setMotionEventSplittingEnabled:设置事件分发策略,是否同时派发到多个子View。

事件如何派发?事件派发的相关函数为dispatchXxxEvent,以dispatchTouchEvent为例,下面的类图列出了相关的函数。

在这里插入图片描述

ViewGroup的dispatchTouchEvent用于事件分发,决定把事件分发给哪个View或ViewGroup,onInterceptTouchEvent表示是否拦截事件,如果拦截事件,事件将不再往ViewGroup的孩子分发。View的dispatchTouchEvent用于事件执行,首先派发给Listener,没有Listener或Listener不消费时,将事件发送到onTouchEvent。所有函数都有个boolean类型的返回值,返回true表示消费事件,返回false表示不消费事件。

下面分析事件分发的流程。

  1. dispatchTouchEvent是从ViewRootImpl的InputStage的ViewPostIme阶段调过来的。
    直接调用的是dispatchPointerEvent,然后根据Event类型决定调用dispatchTouchEvent还是dispatchGenericMotionEvent。源码如下所示。
	@UnsupportedAppUsage
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }
  1. 接着是ViewGroup的dispatchTouchEvent。在dispatchTouchEvent的开始,首先会通过InputEventConsistencyVerifier进行事件一致性验证,函数结束时如果事件还没有被消费,也会通知InputEventConsistencyVerifier,代码如下所示。
	public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        // ...
        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }
  1. 接着,所有的代码处理都在onFilterTouchEventForSecurity返回true的情况下执行,这是一种安全策略,当Window处于Obscured状态时可以屏蔽事件,代码如下。
	/**
     * Filter the touch event to apply security policies.
     *
     * @param event The motion event to be filtered.
     * @return True if the event should be dispatched, false if the event should be dropped.
     *
     * @see #getFilterTouchesWhenObscured
     */
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }
  1. 接着,对touch down事件进行特殊处理,因为down是一切事件的开始,所以down事件到来时,要清空原有的状态,代码如下。

    	// Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Throw away all previous state when starting a new touch gesture.
            // The framework may have dropped the up or cancel event for the previous gesture
            // due to an app switch, ANR, or some other state change.
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
    
  2. 接着,判断是否拦截事件,当事件为touch down或者有FirstTouchTarget时,在允许拦截事件的情况下就会调用onInterceptTouchEvent,进而判断是否拦截了事件;当事件非touch down且有FirstTouchDown时,必然要拦截这个事件,因为事件要发给touch down事件的接收者。然后,判断事件是否cancel,是否可以分发事件到各个孩子。代码如下所示。

			// Check for interception.
            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;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
  1. 接着是事件真正分发的逻辑,前提是事件没有cancel也没有被拦截。如果事件被拦截,则通过super.dispatchTouchEvent进行处理。可以分发的事件有三种类型,一是touch down事件,二是hover事件,三是多个手指操作且允许事件分发到不同孩子的情况。事件分发时,循环遍历所有的孩子,通过private函数dispatchTransformedTouchEvent真正实现事件分发。在dispatchTransformedTouchEvent中,其实就是调用了super.dispatchTouchEvent或child.dispatchTouchEvent。前面分析了ViewGroup的dispatchTouchEvent,下面分析View的dispatchTouchEvent。

  2. View的dispatchTouchEvent中,其实就是把事件发送给Listener或onTouchEvent,代码如下。

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

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
  1. Listener是使用者自己注册实现的,下面看看View的onTouchEvent做了哪些事情。如果View注册了Touch Delegate,事件首先会发给Delegate,代码如下所示。
	public boolean onTouchEvent(MotionEvent event) {
        // ...
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        // ...
    }

	/**
     * Sets the TouchDelegate for this View.
     */
    public void setTouchDelegate(TouchDelegate delegate) {
        mTouchDelegate = delegate;
    }

然后,是对touch down、up、move、cancel事件的处理。至此,事件处理结束。下面是事件分发的流程图。

在这里插入图片描述

下面再简单介绍下Android中相关的Gesture。Gesture有两种用法,一种是GestureDector,用以识别MotionEvent为哪些Gesture,支持的Gesture如下。

public class GestureDetector {
    public interface OnGestureListener {
        boolean onDown(MotionEvent e);
        void onShowPress(MotionEvent e);
        boolean onSingleTapUp(MotionEvent e);
        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
        void onLongPress(MotionEvent e);
        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
    }
    public interface OnDoubleTapListener {
        boolean onSingleTapConfirmed(MotionEvent e);
        boolean onDoubleTap(MotionEvent e);
        boolean onDoubleTapEvent(MotionEvent e);
    }
    public interface OnContextClickListener {
        boolean onContextClick(MotionEvent e);
    }
}

另一种Gesture是通过GestureLibrary识别的触摸屏上的轨迹线,详见android.gesture.Gesture。

11、Scroll

View的Scroll表示可以移动View,准确的来说是移动View中的内容。例如,向右移动View 100个pixel,相当于向左移动View的内容100个pixel。下面列出View类中与Scroll相关主要的几个变量和函数。

在这里插入图片描述

Scroll的x和y分别保存在变量mScrollX和mScrollY中。setScrollX、setScrollY和scrollBy都通过scrollTo实现,源码如下所示。

	public void setScrollX(int value) {
        scrollTo(value, mScrollY);
    }

	public void setScrollY(int value) {
        scrollTo(mScrollX, value);
    }

	public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

当View被Scroll时,会调用onScrollChanged函数,如果通过setOnScrollChangeListener设置监听Scroll变化的Listener,还会通知这个Listener,通知是在onScrollChanged函数中发出的,而onScrollChanged函数是在scrollTo中调用的,下面是scrollTo的源码实现。

	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();
            }
        }
    }

View在Scroll时,实际上是作了一个动画。真正的动作发生在postInvalidateOnAnimation,然后通过ViewRootImpl、 Choreographer、DisplayEventReceiver、native完成,时序图如下。

在这里插入图片描述

12、Animation

View提供了播放动画的接口,下面列出函数名。

  • startAnimation:播放动画。
  • setAnimation:设置动画。
  • clearAnimation:取消动画。
  • getAnimation:获取动画。
  • postInvaidateOnAnimation:用于Display下一帧时的动画。
  • postOnAnimation:指定下一个动画帧时需要做的事情。
  • postOnAnimationDelayed:指定下一个动画帧时需要做的事情,有延迟。
  • onAnimationStart:动画开始时调用。
  • onAnimationEnd:动画结束时调用。

下面是Animation相关的类图。

在这里插入图片描述

可以看出Animation支持的动画种类有限,包括alpha、平移、缩放和旋转,以及组合动画AnimationSet,是Android早期的产品,后期又退出了更强大的Animator动画框架。两者相比,从版本兼容性来说,Animation更好;从效率来说,Animator使用了反射,效率稍差;从应用角度来说,Animator可以用于任意对象,应用范围更广;从动画效果来看,Animator会更新View的实际位置,而Animation只是提供了一种动画效果,实际位置保持不变。

下面是Animator相关的类图。

在这里插入图片描述

13、布局

Android有各种各样的布局,这些布局都继承自ViewGroup类,下面是几种主要布局的类图。

在这里插入图片描述

  • LinearLayout:线性布局,包括水平方向和垂直方向。
  • GridLayout:网格布局。
  • AbsoluteLayout:绝对布局。
  • RelativeLayout:相对布局。
  • FrameLayout:帧布局,在z轴方向上的布局,上面的会覆盖下面的。

上面的这些布局都继承自ViewGroup,必定会包含一些Child,对这些Child的管理通过ViewManager和ViewParent完成,文章开头列出了相关的类图。

猜你喜欢

转载自blog.csdn.net/iEearth/article/details/86169732