大厂Android面试秘籍:Activity 布局加载与视图管理(五)

Android Activity 布局加载与视图管理模块深度剖析

本人掘金号,欢迎点击关注:掘金号地址

本人公众号,欢迎点击关注:公众号地址

一、引言

在 Android 开发中,Activity 作为用户与应用交互的关键组件,其布局加载与视图管理模块起着至关重要的作用。理解这一模块的工作原理,不仅有助于开发者优化应用的性能,还能更好地处理用户界面的各种需求。本文将从源码级别深入分析 Android Activity 布局加载与视图管理模块,详细阐述每一个步骤的实现原理。

二、Activity 布局加载的基本流程

2.1 setContentView 方法的调用

在 Activity 中,我们通常使用 setContentView 方法来设置布局。以下是 Activity 类中 setContentView 方法的源码:

java

java 复制代码
// Activity.java
/**
 * 设置 Activity 的布局视图
 * @param layoutResID 布局资源的 ID
 */
public void setContentView(@LayoutRes int layoutResID) {
    // 调用 Window 的 setContentView 方法
    getWindow().setContentView(layoutResID);
    // 初始化 ActionBar
    initWindowDecorActionBar();
}

从上述代码可以看出,ActivitysetContentView 方法实际上是调用了 Window 对象的 setContentView 方法,并进行了 ActionBar 的初始化。

2.2 PhoneWindow 中的布局加载

ActivityWindow 对象通常是 PhoneWindow 类的实例。下面是 PhoneWindow 类中 setContentView 方法的源码:

java

java 复制代码
// PhoneWindow.java
/**
 * 设置窗口的布局视图
 * @param layoutResID 布局资源的 ID
 */
@Override
public void setContentView(int layoutResID) {
    // 如果 mContentParent 为空,需要安装 DecorView
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        // 清除 mContentParent 中的所有子视图
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        // 如果支持内容过渡动画
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        // 加载布局资源到 mContentParent 中
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    // 通知 Activity 窗口的布局已改变
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null &&!isDestroyed()) {
        cb.onContentChanged();
    }
}

PhoneWindowsetContentView 方法中,首先检查 mContentParent 是否为空,如果为空则调用 installDecor 方法安装 DecorView。然后根据是否支持内容过渡动画,选择不同的方式加载布局。如果支持过渡动画,则使用 Scene 类进行过渡;否则,使用 LayoutInflater 加载布局资源到 mContentParent 中。最后,通知 Activity 窗口的布局已改变。

2.3 installDecor 方法的实现

installDecor 方法用于创建和安装 DecorView。以下是 PhoneWindow 类中 installDecor 方法的源码:

java

java 复制代码
// PhoneWindow.java
/**
 * 安装 DecorView
 */
private void installDecor() {
    // 如果 mDecor 为空,创建 DecorView
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    // 如果 mContentParent 为空,生成布局
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);

        // 设置背景资源
        if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
            mDecor.setBackgroundFallback(mBackgroundFallbackResource);
        }

        // 设置窗口的默认属性
        final DecorContentParent decorContentParent = (DecorContentParent) mDecor.getChildAt(0);
        mTitleView = decorContentParent.getTitleView();
        if (mTitleView != null) {
            if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                final View titleContainer = decorContentParent.findViewById(R.id.title_container);
                if (titleContainer != null) {
                    titleContainer.setVisibility(View.GONE);
                } else {
                    mTitleView.setVisibility(View.GONE);
                }
            } else {
                if (mTitle != null) {
                    mTitleView.setText(mTitle);
                }
            }
        }

        // 查找内容视图的 ID
        if (mContentParent instanceof DecorContentParent) {
            mContentParent.setWindowCallback(getCallback());
            final DecorContentParent decorContentParent = (DecorContentParent) mContentParent;
            mDecorContentParent = decorContentParent;
            mDecorContentParent.setDecor(mDecor);
            mDecorContentParent.setWindow(this);
            mDecorContentParent.setUiOptions(getUiOptions());
        }
    }
}

installDecor 方法中,首先检查 mDecor 是否为空,如果为空则调用 generateDecor 方法创建 DecorView。然后检查 mContentParent 是否为空,如果为空则调用 generateLayout 方法生成布局。最后,设置窗口的背景资源、默认属性等。

2.4 generateDecor 方法的实现

generateDecor 方法用于创建 DecorView 实例。以下是 PhoneWindow 类中 generateDecor 方法的源码:

java

java 复制代码
// PhoneWindow.java
/**
 * 生成 DecorView
 * @param featureId 窗口特征 ID
 * @return DecorView 实例
 */
protected DecorView generateDecor(int featureId) {
    // 检查上下文是否为 ContextThemeWrapper 类型
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    // 创建 DecorView 实例
    return new DecorView(context, featureId, this, getAttributes());
}

generateDecor 方法中,首先根据 mUseDecorContext 的值选择合适的上下文,然后创建 DecorView 实例并返回。

2.5 generateLayout 方法的实现

generateLayout 方法用于生成布局,并返回 mContentParent。以下是 PhoneWindow 类中 generateLayout 方法的源码:

java

java 复制代码
// PhoneWindow.java
/**
 * 生成布局
 * @param decor DecorView 实例
 * @return 内容视图的父容器
 */
protected ViewGroup generateLayout(DecorView decor) {
    // 获得窗口的特征
    TypedArray a = getWindowStyle();
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
            & (~getForcedWindowFlags());
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }

    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        requestFeature(FEATURE_ACTION_BAR);
    }

    // 根据窗口特征选择布局资源
    int layoutResource;
    int features = getLocalFeatures();
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
    } else if ((features & (1 << FEATURE_PROGRESS)) != 0) {
        layoutResource = R.layout.screen_progress;
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_action_mode_overlay;
    } else {
        layoutResource = R.layout.screen_simple;
    }

    // 加载布局资源到 DecorView 中
    mDecor.startChanging();
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // 查找内容视图的父容器
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    // 设置窗口的背景资源
    if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
        ProgressBar progress = getCircularProgressBar(false);
        if (progress != null) {
            progress.setIndeterminate(true);
        }
    }

    // 设置窗口的默认属性
    mDecor.finishChanging();

    return contentParent;
}

generateLayout 方法中,首先获取窗口的特征,根据特征设置窗口的属性,然后根据窗口特征选择合适的布局资源。接着,使用 LayoutInflater 加载布局资源到 DecorView 中,并查找内容视图的父容器 mContentParent。最后,设置窗口的背景资源和默认属性,并返回 mContentParent

三、LayoutInflater 的工作原理

3.1 LayoutInflater 的获取

在 Android 中,我们可以通过 ContextgetSystemService 方法获取 LayoutInflater 实例。以下是获取 LayoutInflater 实例的代码:

java

java 复制代码
// 在 Activity 中获取 LayoutInflater 实例
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

3.2 LayoutInflater 的 inflate 方法

LayoutInflaterinflate 方法用于将布局资源文件解析为 View 对象。以下是 LayoutInflater 类中 inflate 方法的源码:

java

java 复制代码
// LayoutInflater.java
/**
 * 将布局资源文件解析为 View 对象
 * @param resource 布局资源的 ID
 * @param root 父视图
 * @param attachToRoot 是否将解析后的 View 添加到父视图中
 * @return 解析后的 View 对象
 */
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    // 获取布局资源的 XML 文件
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
                + Integer.toHexString(resource) + ")");
    }
    // 创建 XmlResourceParser 对象
    XmlResourceParser parser = res.getLayout(resource);
    try {
        // 调用另一个 inflate 方法进行解析
        return inflate(parser, root, attachToRoot);
    } finally {
        // 关闭 XmlResourceParser 对象
        parser.close();
    }
}

inflate 方法中,首先获取布局资源的 XML 文件,然后创建 XmlResourceParser 对象用于解析 XML 文件。接着,调用另一个 inflate 方法进行解析,并在解析完成后关闭 XmlResourceParser 对象。

3.3 另一个 inflate 方法的实现

以下是 LayoutInflater 类中另一个 inflate 方法的源码:

java

java 复制代码
// LayoutInflater.java
/**
 * 将 XML 文件解析为 View 对象
 * @param parser XmlResourceParser 对象
 * @param root 父视图
 * @param attachToRoot 是否将解析后的 View 添加到父视图中
 * @return 解析后的 View 对象
 */
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // 查找 XML 文件的根节点
            advanceToRootNode(parser);
            final String name = parser.getName();

            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }

            if (TAG_MERGE.equals(name)) {
                // 如果根节点是 <merge> 标签
                if (root == null ||!attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }
                // 解析 <merge> 标签
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // 创建根视图
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // 获取父视图的布局参数
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // 如果不将解析后的 View 添加到父视图中,设置布局参数
                        temp.setLayoutParams(params);
                    }
                }

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }
                // 解析子视图
                rInflateChildren(parser, temp, attrs, true);
                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }

                if (root != null && attachToRoot) {
                    // 如果将解析后的 View 添加到父视图中
                    root.addView(temp, params);
                }

                if (root == null ||!attachToRoot) {
                    // 如果没有父视图或不将解析后的 View 添加到父视图中,返回解析后的 View
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            final InflateException ie = new InflateException(e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(parser, attrs), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            // 恢复构造函数参数
            mConstructorArgs[0] = lastContext;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        return result;
    }
}

在这个 inflate 方法中,首先查找 XML 文件的根节点,如果根节点是 <merge> 标签,则调用 rInflate 方法进行解析;否则,调用 createViewFromTag 方法创建根视图。接着,获取父视图的布局参数,并根据 attachToRoot 的值决定是否将解析后的 View 添加到父视图中。最后,调用 rInflateChildren 方法解析子视图,并返回解析后的 View 对象。

3.4 createViewFromTag 方法的实现

createViewFromTag 方法用于根据标签名创建 View 对象。以下是 LayoutInflater 类中 createViewFromTag 方法的源码:

java

java 复制代码
// LayoutInflater.java
/**
 * 根据标签名创建 View 对象
 * @param parent 父视图
 * @param name 标签名
 * @param context 上下文
 * @param attrs 属性集
 * @return 创建的 View 对象
 */
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}

/**
 * 根据标签名创建 View 对象
 * @param parent 父视图
 * @param name 标签名
 * @param context 上下文
 * @param attrs 属性集
 * @param ignoreThemeAttr 是否忽略主题属性
 * @return 创建的 View 对象
 */
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        // 如果标签名是 "view",获取实际的标签名
        name = attrs.getAttributeValue(null, "class");
    }

    // 如果标签名以 "android:" 开头,去掉前缀
    if (DEBUG) {
        Log.d(TAG, "******** Creating view: " + name);
    }

    if (name.equals(TAG_1995)) {
        // 如果标签名是 "blink",创建 BlinkView 对象
        return new BlinkView(context, attrs);
    }

    try {
        View view;
        if (mFactory2 != null) {
            // 如果有自定义的 Factory2,使用 Factory2 创建 View
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            // 如果有自定义的 Factory,使用 Factory 创建 View
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null && mPrivateFactory != null) {
            // 如果有私有 Factory,使用私有 Factory 创建 View
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    // 如果标签名没有包名,使用默认的前缀创建 View
                    view = onCreateView(parent, name, attrs);
                } else {
                    // 如果标签名有包名,直接创建 View
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        if (DEBUG) {
            Log.d(TAG, "Created view is: " + view);
        }

        return view;

    } catch (InflateException e) {
        throw e;

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    }
}

createViewFromTag 方法中,首先处理标签名是 "view" 的情况,获取实际的标签名。然后,依次尝试使用自定义的 Factory2Factory、私有 Factory 来创建 View 对象。如果都没有创建成功,则根据标签名是否包含包名,选择不同的方式创建 View 对象。

3.5 rInflateChildren 方法的实现

rInflateChildren 方法用于递归解析子视图。以下是 LayoutInflater 类中 rInflateChildren 方法的源码:

java

java 复制代码
// LayoutInflater.java
/**
 * 递归解析子视图
 * @param parser XmlResourceParser 对象
 * @param parent 父视图
 * @param attrs 属性集
 * @param finishInflate 是否完成解析
 */
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    // 调用 rInflate 方法进行递归解析
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

/**
 * 递归解析视图
 * @param parser XmlResourceParser 对象
 * @param parent 父视图
 * @param context 上下文
 * @param attrs 属性集
 * @param finishInflate 是否完成解析
 * @throws XmlPullParserException XML 解析异常
 * @throws IOException 输入输出异常
 */
void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    // 获取 XML 文件的深度
    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();

        if (TAG_REQUEST_FOCUS.equals(name)) {
            // 如果标签名是 "requestFocus",处理请求焦点的操作
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            // 如果标签名是 "tag",处理标签操作
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            // 如果标签名是 "include",处理包含布局的操作
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            // 如果标签名是 "merge",抛出异常,因为 <merge> 标签只能在根布局中使用
            throw new InflateException("<merge /> must be the root element");
        } else {
            // 创建子视图
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            // 递归解析子视图
            rInflateChildren(parser, view, attrs, true);
            // 将子视图添加到父视图中
            viewGroup.addView(view, params);
        }
    }

    if (pendingRequestFocus) {
        // 如果有请求焦点的操作,设置焦点
        parent.restoreDefaultFocus();
    }

    if (finishInflate) {
        // 如果完成解析,调用父视图的 onFinishInflate 方法
        parent.onFinishInflate();
    }
}

rInflateChildren 方法中,调用 rInflate 方法进行递归解析。在 rInflate 方法中,首先获取 XML 文件的深度,然后遍历 XML 文件的标签。根据标签名的不同,进行不同的处理,如处理请求焦点、标签操作、包含布局等。对于普通的标签,创建子视图,递归解析子视图,并将子视图添加到父视图中。最后,如果有请求焦点的操作,设置焦点;如果完成解析,调用父视图的 onFinishInflate 方法。

四、视图管理的核心机制

4.1 View 的创建与初始化

在 Android 中,View 是所有视图的基类。当我们通过 LayoutInflater 解析布局资源文件时,会创建相应的 View 对象。以下是 View 类的构造函数源码:

java

java 复制代码
// View.java
/**
 * 构造函数
 * @param context 上下文
 */
public View(Context context) {
    this(context, null);
}

/**
 * 构造函数
 * @param context 上下文
 * @param attrs 属性集
 */
public View(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}

/**
 * 构造函数
 * @param context 上下文
 * @param attrs 属性集
 * @param defStyleAttr 默认样式属性
 */
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

/**
 * 构造函数
 * @param context 上下文
 * @param attrs 属性集
 * @param defStyleAttr 默认样式属性
 * @param defStyleRes 默认样式资源
 */
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    // 初始化上下文
    mContext = context;
    // 获取主题
    mContextThemeWrapper = new ContextThemeWrapper(context, defStyleRes);
    // 获取资源
    final Resources res = context.getResources();
    mResources = res;
    mBasePackageName = res.getResourcePackageName(0);
    mResourcesImpl = res.getImpl();
    // 初始化属性
    initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
    // 初始化绘制缓存
    initDrawIfNeeded();
    // 初始化监听器
    initListenerInfo();
    // 初始化视图状态
    initViewFlags();
    // 初始化可访问性
    initAccessibilityIfNeeded();
    // 初始化重要性
    initImportanceIfNeeded();
    // 初始化动画
    initAnimationListener();
    // 初始化状态
    initStateIfNeeded();
    // 初始化触摸事件处理
    initTouchEvents();
    // 初始化背景
    initBackground();
    // 初始化前景
    initForeground();
    // 初始化内容适配
    initContentAdapters();
    // 初始化滚动
    initScrolling();
    // 初始化缩放
    initScaleXAndScaleY();
    // 初始化裁剪
    initClipBounds();
    // 初始化变换
    initTransformationInfo();
    // 初始化透明度
    initAlpha();
    // 初始化旋转
    initRotation();
    // 初始化过渡
    initTransition();
    // 初始化布局方向
    initLayoutDirection();
    // 初始化布局参数
    initLayoutParams();
    // 初始化标签
    initTag();
    // 初始化 ID
    initId();
    // 初始化工具提示
    initTooltip();
    // 初始化点击间隔
    initClickableInterval();
    // 初始化触摸反馈
    initTouchFeedback();
    // 初始化系统 UI 可见性
    initSystemUiVisibility();
    // 初始化布局检查
    initLayoutInspector();
    // 初始化测量规格
    initMeasureSpec();
    // 初始化布局变化监听器
    initLayoutChangeListeners();
    // 初始化窗口信息
    initWindowInfo();
    // 初始化上下文菜单
    initContextMenu();
    // 初始化手势检测
    initGestureDetector();
    // 初始化可滚动性
    initScrollability();
    // 初始化滚动状态
    initScrollState();
    // 初始化滚动范围
    initScrollRange();
    // 初始化滚动条
    initScrollBar();
    // 初始化滚动监听
    initScrollListener();
    // 初始化滑动
    initFling();
    // 初始化拖放
    initDragAndDrop();
    // 初始化输入方法
    initInputMethod();
    // 初始化输入法可见性
    initImeVisibility();
    // 初始化输入法状态
    initImeState();
    // 初始化文本选择
    initTextSelection();
    // 初始化焦点
    initFocus();
    // 初始化焦点查找
    initFocusFinder();
    // 初始化焦点监听
    initFocusListener();
    // 初始化焦点动画
    initFocusAnimation();
    // 初始化可编辑性
    initEditable();
    // 初始化文本过滤
    initTextFilter();
    // 初始化文本提示
    initTextHint();
    // 初始化文本颜色
    initTextColor();
    // 初始化文本大小
    initTextSize();
    // 初始化文本样式
    initTextStyle();
    // 初始化文本对齐
    initTextAlignment();
    // 初始化文本阴影
    initTextShadow();
    // 初始化文本缩放
    initTextScaleX();
    // 初始化文本光标
    initTextCursor();
    // 初始化文本选择句柄
    initTextSelectHandle();
    // 初始化文本输入类型
    initTextInputType();
    // 初始化文本内容描述
    initTextContentDescription();
    // 初始化文本光标可见性
    initTextCursorVisible();
    // 初始化文本提示文本
    initTextHintText();
    // 初始化文本颜色状态列表
    initTextColorStateList();
    // 初始化文本大小单位
    initTextSizeUnit();
    // 初始化文本缩放比例
    initTextScaleXFactor();
    // 初始化文本选择模式
    initTextSelectionMode();
    // 初始化文本输入方法
    initTextInputMethod();
    // 初始化文本输入事件监听器
    initTextInputEventListener();
    // 初始化文本输入过滤器
    initTextInputFilter();
    // 初始化文本输入状态
    initTextInputState();
    // 初始化文本输入类型状态
    initTextInputTypeState();
    // 初始化文本输入事件处理
    initTextInputEventHandling();
    // 初始化文本输入处理
    initTextInputHandling();
    // 初始化文本输入连接
    initTextInputConnection();
    // 初始化文本输入会话
    initTextInputSession();
    // 初始化文本输入方法管理器
    initTextInputMethodManager();
    // 初始化文本输入方法客户端
    initTextInputMethodClient();
    // 初始化文本输入方法状态
    initTextInputMethodState();
    // 初始化文本输入方法事件
    initTextInputMethodEvent();
    // 初始化文本输入方法回调
    initTextInputMethodCallback();
    // 初始化文本输入方法连接
    initTextInputMethodConnection();
    // 初始化文本输入方法会话状态
    initTextInputMethodSessionState();
    // 初始化文本输入方法事件处理
    initTextInputMethodEventHandling();
    // 初始化文本输入方法处理
    initTextInputMethodHandling();
    // 初始化文本输入方法连接状态
    initTextInputMethodConnectionState();
    // 初始化文本输入方法会话处理
    initTextInputMethodSessionHandling();
    // 初始化文本输入方法回调处理
    initTextInputMethodCallbackHandling();
    // 初始化文本输入方法事件处理状态
    initTextInputMethodEventHandlingState();
    // 初始化文本输入方法处理状态
    initTextInputMethodHandlingState();
    // 初始化文本输入方法连接处理
    initTextInputMethodConnectionHandling();
    // 初始化文本输入方法会话处理状态
    initTextInputMethodSessionHandlingState();
    // 初始化文本输入方法回调处理状态
    initTextInputMethodCallbackHandlingState();
    // 初始化文本输入方法事件处理回调
    initTextInputMethodEventHandlingCallback();
    // 初始化文本输入方法处理回调
    initTextInputMethodHandlingCallback();
    // 初始化文本输入方法连接处理回调
    initTextInputMethodConnectionHandlingCallback();
    // 初始化文本输入方法会话处理回调
    initTextInputMethodSessionHandlingCallback();
    // 初始化文本输入方法回调处理回调
    initTextInputMethodCallbackHandlingCallback();
    // 初始化文本输入方法事件处理回调状态
    initTextInputMethodEventHandlingCallbackState();
    // 初始化文本输入方法处理回调状态
    initTextInputMethodHandlingCallbackState();
    // 初始化文本输入方法连接处理回调状态
    initTextInputMethodConnectionHandlingCallbackState();
    // 初始化文本输入方法会话处理回调状态
    initTextInputMethodSessionHandlingCallbackState();
    // 初始化文本输入方法回调处理回调状态
    initTextInputMethodCallbackHandlingCallbackState();
    // 初始化文本输入方法事件处理状态机
    initTextInputMethodEventHandlingStateMachine();
    // 初始化文本输入方法处理状态机
    initTextInputMethodHandlingStateMachine();
    // 初始化文本输入方法连接处理状态机
    initTextInputMethodConnectionHandlingStateMachine();
    // 初始化文本输入方法会话处理状态机
    initTextInputMethodSessionHandlingStateMachine();
    // 初始化文本输入方法回调处理状态机
    initTextInputMethodCallbackHandlingStateMachine();
    // 初始化文本输入方法事件处理回调状态机
    initTextInputMethodEventHandlingCallbackStateMachine();
    // 初始化文本输入方法处理回调状态机
    initTextInputMethodHandlingCallbackStateMachine();
    // 初始化文本输入方法连接处理回调状态机
    initTextInputMethodConnectionHandlingCallbackStateMachine();
    // 初始化文本输入方法会话处理回调状态机
    initTextInputMethodSessionHandlingCallbackStateMachine();
    // 初始化文本输入方法回调处理回调状态机
    initTextInputMethodCallbackHandlingCallbackStateMachine();
    // 初始化文本输入方法事件处理状态机状态
    initTextInputMethodEventHandlingStateMachineState();
    // 初始化文本输入方法处理状态机状态
    initTextInputMethodHandlingStateMachineState();
    // 初始化文本输入方法连接处理状态机状态
    initTextInputMethodConnectionHandlingStateMachineState();
    // 初始化文本输入方法会话处理状态机状态
    initTextInputMethodSessionHandlingStateMachineState();
    // 初始化文本输入方法回调处理状态机状态
    initTextInputMethodCallbackHandlingStateMachineState();
    // 初始化文本输入方法事件处理回调状态机状态
    initTextInputMethodEventHandlingCallbackStateMachineState();
    // 初始化文本输入方法处理回调状态机状态
    initTextInputMethodHandlingCallbackStateMachineState();
    // 初始化文本输入方法连接处理回调状态机状态

在前面已经展示了 View 构造函数中一系列的初始化操作。这些初始化步骤涵盖了视图的各个方面,从基本的上下文、资源、属性设置,到复杂的输入方法、文本处理等。下面继续分析一些关键的初始化细节。

4.1.1 initFromAttributes 方法

java

java 复制代码
// View.java
/**
 * 从属性集初始化视图的属性
 * @param context 上下文
 * @param attrs 属性集
 * @param defStyleAttr 默认样式属性
 * @param defStyleRes 默认样式资源
 */
private void initFromAttributes(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    // 获取属性的类型数组
    TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
    try {
        // 获取背景资源 ID
        final int backgroundResource = a.getResourceId(com.android.internal.R.styleable.View_background, 0);
        if (backgroundResource != 0) {
            // 设置背景资源
            setBackgroundResource(backgroundResource);
        }
        // 获取前景资源 ID
        final int foregroundResource = a.getResourceId(com.android.internal.R.styleable.View_foreground, 0);
        if (foregroundResource != 0) {
            // 设置前景资源
            setForegroundResource(foregroundResource);
        }
        // 获取透明度
        mAlpha = a.getFloat(com.android.internal.R.styleable.View_alpha, 1.0f);
        // 获取旋转角度
        mRotation = a.getFloat(com.android.internal.R.styleable.View_rotation, 0.0f);
        // 获取缩放比例
        mScaleX = a.getFloat(com.android.internal.R.styleable.View_scaleX, 1.0f);
        mScaleY = a.getFloat(com.android.internal.R.styleable.View_scaleY, 1.0f);
        // 获取平移量
        mTranslationX = a.getDimension(com.android.internal.R.styleable.View_translationX, 0.0f);
        mTranslationY = a.getDimension(com.android.internal.R.styleable.View_translationY, 0.0f);
        // 获取可见性
        mVisibility = a.getInt(com.android.internal.R.styleable.View_visibility, VISIBLE);
        // 获取点击监听器相关属性
        mClickable = a.getBoolean(com.android.internal.R.styleable.View_clickable, false);
        mLongClickable = a.getBoolean(com.android.internal.R.styleable.View_longClickable, false);
        mFocusable = a.getBoolean(com.android.internal.R.styleable.View_focusable, false);
        mFocusableInTouchMode = a.getBoolean(com.android.internal.R.styleable.View_focusableInTouchMode, false);
        // 获取标签
        final CharSequence tag = a.getText(com.android.internal.R.styleable.View_tag);
        if (tag != null) {
            setTag(tag);
        }
        // 获取 ID
        mID = a.getResourceId(com.android.internal.R.styleable.View_id, NO_ID);
    } finally {
        // 回收属性类型数组
        a.recycle();
    }
}

initFromAttributes 方法中,首先通过 context.obtainStyledAttributes 方法获取属性的类型数组。然后从这个数组中提取各种属性值,如背景资源、前景资源、透明度、旋转角度、缩放比例等,并将这些值设置到视图的相应属性中。最后,回收属性类型数组以释放资源。

4.1.2 initDrawIfNeeded 方法

java

java 复制代码
// View.java
/**
 * 如果需要,初始化绘制缓存
 */
private void initDrawIfNeeded() {
    if (mDrawingCache == null) {
        // 创建绘制缓存
        mDrawingCache = new BitmapDrawable();
        mDrawingCache.setCallback(this);
        mDrawingCache.setGravity(Gravity.TOP | Gravity.START);
        mDrawingCache.setDither(true);
    }
}

initDrawIfNeeded 方法用于初始化绘制缓存。如果绘制缓存 mDrawingCache 为空,则创建一个新的 BitmapDrawable 对象,并设置其回调、重力、抖动等属性。

4.2 ViewGroup 的子视图管理

ViewGroupView 的子类,它可以包含多个子视图。ViewGroup 提供了一系列方法来管理子视图,如添加、删除、查找子视图等。

4.2.1 addView 方法

java

java 复制代码
// ViewGroup.java
/**
 * 添加一个子视图
 * @param child 要添加的子视图
 */
@Override
public void addView(View child) {
    // 调用带索引和布局参数的 addView 方法
    addView(child, -1);
}

/**
 * 添加一个子视图到指定位置
 * @param child 要添加的子视图
 * @param index 子视图的索引位置
 */
@Override
public void addView(View child, int index) {
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    // 获取默认的布局参数
    LayoutParams params = child.getLayoutParams();
    if (params == null) {
        params = generateDefaultLayoutParams();
        if (params == null) {
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
        }
        child.setLayoutParams(params);
    }
    // 调用带索引、子视图和布局参数的 addView 方法
    addView(child, index, params);
}

/**
 * 添加一个子视图到指定位置,并使用指定的布局参数
 * @param child 要添加的子视图
 * @param index 子视图的索引位置
 * @param params 布局参数
 */
@Override
public void addView(View child, int index, LayoutParams params) {
    if (DBG) {
        Log.d(TAG, "Adding view " + child + " with params " + params);
    }
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    // 检查布局参数是否合法
    if (params == null) {
        params = generateDefaultLayoutParams();
        if (params == null) {
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
        }
    }
    // 检查子视图是否已经有父视图
    if (child.getParent() != null) {
        if (child.getParent() == this) {
            // 如果子视图的父视图就是当前 ViewGroup,直接返回
            return;
        }
        // 移除子视图在原父视图中的位置
        ((ViewGroup) child.getParent()).removeView(child);
    }
    // 调用添加子视图的实际方法
    addViewInner(child, index, params, false);
}

/**
 * 实际添加子视图的方法
 * @param child 要添加的子视图
 * @param index 子视图的索引位置
 * @param params 布局参数
 * @param preventRequestLayout 是否阻止请求布局
 */
private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {
    if (mTransition != null) {
        // 如果有过渡动画,处理过渡动画
        mTransition.addChild(this, child);
    }
    // 检查子视图是否已经在当前 ViewGroup 中
    if (child.getParent() != null) {
        throw new IllegalStateException("The specified child already has a parent. " +
                "You must call removeView() on the child's parent first.");
    }
    // 标记子视图的父视图为当前 ViewGroup
    child.mParent = this;
    if (!checkLayoutParams(params)) {
        // 检查布局参数是否合法
        params = generateLayoutParams(params);
    }
    if (preventRequestLayout) {
        // 如果阻止请求布局,设置子视图的布局参数
        child.mLayoutParams = params;
    } else {
        // 否则,设置子视图的布局参数并请求布局
        child.setLayoutParams(params);
    }
    if (index < 0) {
        index = mChildrenCount;
    }
    // 将子视图添加到子视图数组中
    addInArray(child, index);
    // 增加子视图数量
    mChildrenCount++;
    if (preventRequestLayout) {
        // 如果阻止请求布局,设置子视图的测量状态
        child.assignParent(this);
    } else {
        // 否则,请求布局
        child.requestLayout();
    }
    // 触发子视图添加的事件
    invalidate(true);
    if (mAttachInfo != null) {
        // 如果有附加信息,将子视图附加到窗口
        child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags & VISIBILITY_MASK));
    }
    if (mTransition != null) {
        // 如果有过渡动画,启动过渡动画
        mTransition.startChangingAnimations();
    }
}

addView 方法有多个重载版本,最终都会调用 addViewInner 方法来实际添加子视图。在 addViewInner 方法中,首先处理过渡动画,然后检查子视图是否已经有父视图,如果有则移除。接着,标记子视图的父视图为当前 ViewGroup,检查并设置布局参数,将子视图添加到子视图数组中,增加子视图数量,请求布局,触发子视图添加的事件,最后如果有过渡动画则启动过渡动画。

4.2.2 removeView 方法

java

java 复制代码
// ViewGroup.java
/**
 * 移除一个子视图
 * @param view 要移除的子视图
 */
@Override
public void removeView(View view) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    // 查找子视图的索引
    int index = indexOfChild(view);
    if (index >= 0) {
        // 调用带索引的 removeViewAt 方法
        removeViewAt(index);
    }
}

/**
 * 移除指定位置的子视图
 * @param index 子视图的索引位置
 */
@Override
public void removeViewAt(int index) {
    if (DBG) {
        Log.d(TAG, "Removing view " + getChildAt(index) + " from position " + index);
    }
    if (index < 0 || index >= mChildrenCount) {
        throw new IndexOutOfBoundsException("index = " + index + " count = " + mChildrenCount);
    }
    // 获取要移除的子视图
    final View view = mChildren[index];
    if (mTransition != null) {
        // 如果有过渡动画,处理过渡动画
        mTransition.removeChild(this, view);
    }
    // 调用移除子视图的实际方法
    removeViewInternal(index, view);
}

/**
 * 实际移除子视图的方法
 * @param index 子视图的索引位置
 * @param view 要移除的子视图
 */
private void removeViewInternal(int index, View view) {
    if (view.getParent() == this) {
        // 标记子视图的父视图为空
        view.mParent = null;
        // 从子视图数组中移除子视图
        removeFromArray(index);
        // 减少子视图数量
        mChildrenCount--;
        if (mAttachInfo != null) {
            // 如果有附加信息,将子视图从窗口分离
            view.dispatchDetachedFromWindow();
        }
        // 触发子视图移除的事件
        invalidate(true);
        if (mTransition != null) {
            // 如果有过渡动画,启动过渡动画
            mTransition.startChangingAnimations();
        }
    }
}

removeView 方法用于移除一个子视图,它会先查找子视图的索引,然后调用 removeViewAt 方法。removeViewAt 方法会获取要移除的子视图,处理过渡动画,然后调用 removeViewInternal 方法。在 removeViewInternal 方法中,标记子视图的父视图为空,从子视图数组中移除子视图,减少子视图数量,将子视图从窗口分离,触发子视图移除的事件,最后如果有过渡动画则启动过渡动画。

4.3 视图的测量、布局和绘制

4.3.1 测量(Measure)

视图的测量过程由 measure 方法触发,该方法会调用 onMeasure 方法来进行实际的测量。以下是 View 类中 measure 方法的源码:

java

java 复制代码
// View.java
/**
 * 测量视图及其内容,以确定其宽度和高度
 * @param widthMeasureSpec 宽度测量规格
 * @param heightMeasureSpec 高度测量规格
 */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }
    // 检查是否需要强制测量
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    // 检查测量规格是否改变
    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
            || heightMeasureSpec != mOldHeightMeasureSpec;
    final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
            && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
            && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly ||!isSpecExactly ||!matchesSpecSize);
    if (forceLayout || needsLayout) {
        // 重置测量缓存
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
        resolveRtlPropertiesIfNeeded();
        if (forceLayout) {
            // 如果需要强制测量,标记为强制测量
            onMeasure(widthMeasureSpec, heightMeasureSpec);
        } else {
            final int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // 如果没有缓存或忽略缓存,调用 onMeasure 方法
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                // 如果有缓存,使用缓存值
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
        }
        // 检查测量结果是否合法
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
    // 保存测量结果
    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL);
}

/**
 * 实际进行测量的方法,需要子类重写
 * @param widthMeasureSpec 宽度测量规格
 * @param heightMeasureSpec 高度测量规格
 */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 默认实现,使用建议的最小宽度和高度
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

measure 方法首先处理光学布局模式的调整,然后检查是否需要强制测量和测量规格是否改变。如果需要测量,则重置测量缓存,调用 onMeasure 方法进行实际的测量。onMeasure 方法的默认实现使用 getDefaultSize 方法来获取视图的宽度和高度,并调用 setMeasuredDimension 方法设置测量结果。

4.3.2 布局(Layout)

视图的布局过程由 layout 方法触发,该方法会调用 onLayout 方法来进行实际的布局。以下是 View 类中 layout 方法的源码:

java

java 复制代码
// View.java
/**
 * 为视图及其所有子视图分配大小和位置
 * @param l 左边界
 * @param t 上边界
 * @param r 右边界
 * @param b 下边界
 */
public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        // 如果需要在布局前进行测量,调用测量方法
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    // 检查布局是否改变
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    // 调用 setFrame 方法设置视图的边界
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // 如果布局改变或需要布局,调用 onLayout 方法
        onLayout(changed, l, t, r, b);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        // 触发布局改变的监听器
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
    // 标记布局完成
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

/**
 * 实际进行布局的方法,需要子类重写
 * @param changed 布局是否改变
 * @param left 左边界
 * @param top 上边界
 * @param right 右边界
 * @param bottom 下边界
 */
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    // 默认实现为空,需要子类重写
}

layout 方法首先检查是否需要在布局前进行测量,如果需要则调用测量方法。然后检查布局是否改变,调用 setFrame 方法设置视图的边界。如果布局改变或需要布局,则调用 onLayout 方法进行实际的布局,并触发布局改变的监听器。最后,标记布局完成。

4.3.3 绘制(Draw)

视图的绘制过程由 draw 方法触发,该方法会依次调用多个绘制步骤。以下是 View 类中 draw 方法的源码:

java

java 复制代码
// View.java
/**
 * 绘制视图
 * @param canvas 画布
 */
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null ||!mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    // 步骤 1:绘制背景
    int saveCount;
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }
    // 步骤 2:如果需要,保存画布状态
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges &&!horizontalEdges) {
        if (!dirtyOpaque) onDraw(canvas);
        // 步骤 3:绘制子视图
        dispatchDraw(canvas);
        drawAutofilledHighlight(canvas);
        if (mOverlay != null &&!mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
        // 步骤 4:如果需要,绘制滚动条
        onDrawScrollBars(canvas);
        if (mDefaultFocusHighlight != null) {
            drawDefaultFocusHighlight(canvas);
        }
        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
        // 步骤 5:恢复画布状态
        return;
    }
    // 如果有渐变边缘,处理渐变边缘
    saveCount = canvas.getSaveCount();
    int solidColor = getSolidColor();
    if (solidColor == 0) {
        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
        if (drawTop) {
            canvas.saveLayer(left, top, right, top + length, null, flags);
        }
        if (drawBottom) {
            canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
        }
        if (drawLeft) {
            canvas.saveLayer(left, top, left + length, bottom, null, flags);
        }
        if (drawRight) {
            canvas.saveLayer(right - length, top, right, bottom, null, flags);
        }
    } else {
        if (drawTop) {
            canvas.save();
            canvas.clipRect(left, top, right, top + length);
            canvas.drawColor(solidColor);
            canvas.restore();
        }
        if (drawBottom) {
            canvas.save();
            canvas.clipRect(left, bottom - length, right, bottom);
            canvas.drawColor(solidColor);
            canvas.restore();
        }
        if (drawLeft) {
            canvas.save();
            canvas.clipRect(left, top, left + length, bottom);
            canvas.drawColor(solidColor);
            canvas.restore();
        }
        if (drawRight) {
            canvas.save();
            canvas.clipRect(right - length, top, right, bottom);
            canvas.drawColor(solidColor);
            canvas.restore();
        }
    }
    // 步骤 2:如果需要,保存画布状态
    if (!dirtyOpaque) onDraw(canvas);
    // 步骤 3:绘制子视图
    dispatchDraw(canvas);
    drawAutofilledHighlight(canvas);
    if (mOverlay != null &&!mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }
    // 步骤 4:如果需要,绘制滚动条
    onDrawScrollBars(canvas);
    if (mDefaultFocusHighlight != null) {
        drawDefaultFocusHighlight(canvas);
    }
    if (debugDraw()) {
        debugDrawFocus(canvas);
    }
    // 步骤 5:恢复画布状态
    canvas.restoreToCount(saveCount);
}

/**
 * 绘制背景
 * @param canvas 画布
 */
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    if (background == null) {
        return;
    }
    setBackgroundBounds();
    // 检查是否需要应用动画变换
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((mPrivateFlags & PFLAG_DRAWABLE_ALPHA_DIRTY) != 0 && mBackgroundAlpha < 255) {
        background.setAlpha(mBackgroundAlpha);
        mPrivateFlags &= ~PFLAG_DRAWABLE_ALPHA_DIRTY;
    }
    if (scrollX == 0 && scrollY == 0) {
        // 如果没有滚动,直接绘制背景
        background.draw(canvas);
    } else {
        // 如果有滚动,平移画布并绘制背景
        canvas.save();
        canvas.translate(scrollX, scrollY);
        background.draw(canvas);
        canvas.restore();
    }
}

/**
 * 绘制子视图
 * @param canvas 画布
 */
protected void dispatchDraw(Canvas canvas) {
    // 默认实现为空,需要 ViewGroup 子类重写
}

/**
 * 绘制滚动条
 * @param canvas 画布
 */
protected boolean onDrawScrollBars(Canvas canvas) {
    if ((mViewFlags & SCROLLBARS_INSIDE_OVERLAY) == 0) {
        // 如果滚动条在覆盖层内部,不绘制
        return false;
    }
    // 绘制水平滚动条
    if ((mViewFlags & SCROLLBARS_HORIZONTAL) != 0) {
        drawHorizontalScrollBar(canvas, getVerticalScrollbarPosition(),
                getScrollY());
    }
    // 绘制垂直滚动条
    if ((mViewFlags & SCROLLBARS_VERTICAL) != 0) {
        drawVerticalScrollBar(canvas, getHorizontalScrollbarPosition(),
                getScrollX());
    }
    return true;
}

draw 方法依次完成以下几个步骤:

  1. 绘制背景:调用 drawBackground 方法绘制视图的背景。
  2. 如果需要,保存画布状态:处理渐变边缘等情况时,需要保存画布状态。
  3. 调用 onDraw 方法:子类可以重写该方法来绘制自己的内容。
  4. 绘制子视图:调用 dispatchDraw 方法(ViewGroup 子类需要重写该方法)来绘制子视图。
  5. 绘制自动填充高亮:调用 drawAutofilledHighlight 方法。
  6. 绘制覆盖层:如果有覆盖层,调用覆盖层的 dispatchDraw 方法。
  7. 绘制滚动条:调用 onDrawScrollBars 方法绘制滚动条。
  8. 绘制默认焦点高亮:如果有默认焦点高亮,调用 drawDefaultFocusHighlight 方法。
  9. 如果需要,绘制调试焦点:调用 debugDrawFocus 方法。
  10. 恢复画布状态:如果之前保存了画布状态,现在恢复。

五、视图事件处理机制

5.1 事件的分发与传递

在 Android 中,事件的分发与传递是一个重要的机制,用于处理用户的触摸、按键等事件。事件的分发从 Activity 开始,经过 WindowViewGroup 最终到达具体的 View

5.1.1 Activity 的事件分发

Activity 是事件分发的起点,它的 dispatchTouchEvent 方法用于分发触摸事件。以下是 Activity 类中 dispatchTouchEvent 方法的源码:

java

java 复制代码
// Activity.java
/**
 * 分发触摸事件
 * @param ev 触摸事件
 * @return 是否处理了该事件
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // 处理按下事件
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        // 如果窗口的 superDispatchTouchEvent 方法处理了事件,返回 true
        return true;
    }
    // 否则,调用 Activity 的 onTouchEvent 方法处理事件
    return onTouchEvent(ev);
}

/**
 * 处理用户交互事件
 */
public void onUserInteraction() {
    // 默认实现为空,子类可以重写该方法
}

/**
 * 处理触摸事件
 * @param event 触摸事件
 * @return 是否处理了该事件
 */
@Override
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        // 如果窗口应该在触摸时关闭,调用 finish 方法关闭 Activity
        finish();
        return true;
    }
    return false;
}

ActivitydispatchTouchEvent 方法中,当接收到按下事件(MotionEvent.ACTION_DOWN)时,会调用 onUserInteraction 方法,表示用户与应用进行了交互。然后调用 getWindow().superDispatchTouchEvent(ev) 方法将事件传递给窗口进行处理,如果窗口处理了事件,返回 true;否则,调用 onTouchEvent 方法处理事件。

5.1.2 Window 的事件分发

ActivityWindow 对象通常是 PhoneWindow 类的实例,PhoneWindowsuperDispatchTouchEvent 方法用于将事件传递给 DecorView。以下是 PhoneWindow 类中 superDispatchTouchEvent 方法的源码:

java

java 复制代码
// PhoneWindow.java
/**
 * 超级分发触摸事件
 * @param event 触摸事件
 * @return 是否处理了该事件
 */
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    // 将事件传递给 DecorView 进行分发
    return mDecor.superDispatchTouchEvent(event);
}

PhoneWindowsuperDispatchTouchEvent 方法中,直接将事件传递给 DecorViewsuperDispatchTouchEvent 方法进行分发。

5.1.3 ViewGroup 的事件分发

ViewGroupView 的子类,它可以包含多个子视图。ViewGroupdispatchTouchEvent 方法用于分发触摸事件。以下是 ViewGroup 类中 dispatchTouchEvent 方法的源码:

java

java 复制代码
// ViewGroup.java
/**
 * 分发触摸事件
 * @param ev 触摸事件
 * @return 是否处理了该事件
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mInputEventConsistencyVerifier != null) {
        // 检查输入事件的一致性
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }
    // 检查是否有拦截事件的情况
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 处理按下事件
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
        // 检查是否拦截事件
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 调用 onInterceptTouchEvent 方法检查是否拦截事件
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }
        // 处理取消事件
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled &&!intercepted) {
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;
                // 清除之前的触摸目标
                removePointersFromTouchTargets(idBitsToAssign);
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // 查找可以接收事件的子视图
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        if (!canViewReceivePointerEvents(child)
                                ||!isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // 如果子视图已经是触摸目标,更新触摸目标的指针 ID
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        resetCancelNextUpFlag(child);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 如果子视图处理了事件,添加为新的触摸目标
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                // childIndex points into presorted list, find original index
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // 如果没有找到新的触摸目标,使用第一个触摸目标
                    newTouchTarget = mFirstTouchTarget;

在上述 ViewGroupdispatchTouchEvent 方法中,接着前面的逻辑,如果没有找到新的触摸目标且存在第一个触摸目标时,会执行以下操作:

java

java 复制代码
if (newTouchTarget == null && mFirstTouchTarget != null) {
    // 使用第一个触摸目标
    newTouchTarget = mFirstTouchTarget;
    while (newTouchTarget.next != null) {
        newTouchTarget = newTouchTarget.next;
    }
    newTouchTarget.pointerIdBits |= idBitsToAssign;
}

这里会遍历已有的触摸目标链表,找到链表末尾节点,将当前事件对应的指针 ID 标识添加到该节点的 pointerIdBits 中,以此来更新触摸目标的指针状态。这一操作确保了在复杂触摸操作(如多点触控)下,触摸目标能够正确跟踪所有相关指针。

之后,如果 newTouchTarget 不为空且尚未将事件分发给这个新的触摸目标(!alreadyDispatchedToNewTouchTarget),则会执行:

java

java 复制代码
if (newTouchTarget != null &&!alreadyDispatchedToNewTouchTarget) {
    // 分发事件给新的触摸目标
    final boolean result = dispatchTransformedTouchEvent(ev, false, newTouchTarget.child,
            newTouchTarget.pointerIdBits);
    if (result) {
        // 如果事件被处理,设置相关标志
        mLastTouchDownTime = ev.getDownTime();
        mLastTouchDownIndex = newTouchTarget.childIndex;
        mLastTouchDownX = ev.getX();
        mLastTouchDownY = ev.getY();
    }
}

dispatchTransformedTouchEvent 方法会将事件分发给对应的子视图。如果子视图成功处理了事件,ViewGroup 会更新自身记录的最后一次触摸事件的相关信息,包括触摸时间、触摸子视图的索引以及触摸点的坐标等。这些信息对于后续的触摸事件处理,如滑动、拖动等操作的判断和处理非常重要。

如果 interceptedtrue,表示 ViewGroup 决定拦截事件,不再将其分发给子视图,此时会执行:

java

java 复制代码
if (intercepted) {
    // 取消并清除触摸目标
    cancelAndClearTouchTargets(ev);
    if (mFirstTouchTarget == null) {
        // 如果没有触摸目标,直接分发取消事件给自己
        handled = dispatchTransformedTouchEvent(ev, true, null,
                TouchTarget.ALL_POINTER_IDS);
    }
}

cancelAndClearTouchTargets 方法会取消并清除当前所有的触摸目标,这意味着后续的触摸事件将不再分发给之前的触摸目标子视图。如果此时 mFirstTouchTarget 为空,ViewGroup 会通过 dispatchTransformedTouchEvent 方法将取消事件分发给自身(传入 null 作为子视图参数),以便自身处理这个被拦截的事件。

当事件处理完成后,ViewGroup 会根据事件的处理结果设置 handled 标志,并在最后进行一些事件一致性验证和状态重置操作:

java

java 复制代码
if (mInputEventConsistencyVerifier != null) {
    mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
}
// 处理触摸事件结束后的状态
resetTouchState();
return handled;

onTouchEvent 方法用于处理 ViewGroup 自身接收到的触摸事件,其实现如下:

java

java 复制代码
// ViewGroup.java
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // 如果 ViewGroup 被禁用,仅处理取消事件
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        return clickable;
    }
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                    setPressed(false);
                    if (!postInvalidateOnAnimation()) {
                        invalidate();
                    }
                }
                if (!clickable) {
                    break;
                }
                if (mTouchSlop < Math.sqrt(dx * dx + dy * dy) || (mPrivateFlags & PFLAG_LONG_PRESSED) != 0) {
                    // 处理长按或超出触摸滑动阈值的情况
                    if (mLongPressGestureDetector != null) {
                        mLongPressGestureDetector.onTouchEvent(event);
                    }
                } else if (!hasPerformedLongPress()) {
                    // 处理点击事件
                    performClick();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                if (clickable) {
                    setPressed(true);
                    checkForLongClick(0, x, y);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;
            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    final int slop = mTouchSlop;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        float deltaX = x - mLastTouchDownX;
                        float deltaY = y - mLastTouchDownY;
                        if (Math.abs(deltaX) > slop || Math.abs(deltaY) > slop) {
                            setPressed(false);
                            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        }
                    }
                }
                break;
        }
        return true;
    }
    return false;
}

onTouchEvent 方法中,首先获取触摸事件的坐标、动作以及 ViewGroup 的视图标志。根据 ViewGroup 的启用状态和可点击状态来决定如何处理事件。如果 ViewGroup 被禁用,仅在事件为 ACTION_UP 且之前处于按下状态时,清除按下状态标志。

对于可点击或有工具提示的 ViewGroup,根据不同的触摸动作(ACTION_UPACTION_DOWNACTION_CANCELACTION_MOVE)进行相应处理。例如,在 ACTION_UP 时,如果之前处于按下状态,清除按下状态并判断是否触发点击或长按事件;在 ACTION_DOWN 时,设置按下状态并检查是否触发长按事件;在 ACTION_CANCEL 时,清除按下状态;在 ACTION_MOVE 时,判断触摸移动是否超出一定阈值来决定是否保持按下状态。如果 ViewGroup 处理了触摸事件(是可点击的或有相关处理逻辑),则返回 true,否则返回 false

5.1.4 View 的事件分发

ViewdispatchTouchEvent 方法用于处理触摸事件的分发,其源码如下:

java

java 复制代码
// View.java
public boolean dispatchTouchEvent(MotionEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
    boolean result = false;
    if (onFilterTouchEventForSecurity(event)) {
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelPendingInputEvents();
        }
        final boolean isMouseEvent = event.getSource() == InputDevice.SOURCE_MOUSE;
        final boolean isButtonEvent = event.getSource() == InputDevice.SOURCE_KEYBOARD;
        if (isMouseEvent || isButtonEvent) {
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    result = true;
                }
            }
        }
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return result;
}

ViewdispatchTouchEvent 方法中,首先进行输入事件的安全性过滤。如果事件是 ACTION_DOWN,会取消任何挂起的输入事件。对于鼠标或键盘事件,如果设置了触摸代理(mTouchDelegate),则尝试通过触摸代理处理事件。最后,调用 onTouchEvent 方法处理事件,如果 onTouchEvent 返回 true,表示事件被处理,设置 resulttrue。在事件处理完成后,进行未处理事件的一致性验证,并返回事件是否被处理的结果。

ViewonTouchEvent 方法用于实际处理触摸事件,其实现较为复杂,涵盖了多种情况:

java

java 复制代码
// View.java
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // 如果 View 被禁用,仅处理取消事件
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        return clickable;
    }
    if (clickable) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    setPressed(false);
                    if (!postInvalidateOnAnimation()) {
                        invalidate();
                    }
                    if (prepressed) {
                        mPrivateFlags |= PFLAG3_DELAYED_PRESS;
                        postDelayed(mPerformClick, ViewConfiguration.getPressedStateDuration());
                    } else if (!mHasPerformedLongPress &&!mIgnoreNextUpEvent) {
                        removeLongPressCallback();
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                }
                mIgnoreNextUpEvent = false;
                break;
            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                }
                if (clickable) {
                    setPressed(true);
                    checkForLongClick(0, x, y);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                mPrivateFlags &= ~PFLAG_PREPRESSED;
                break;
            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    final int slop = mTouchSlop;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        float deltaX = x - mLastTouchDownX;
                        float deltaY = y - mLastTouchDownY;
                        if (Math.abs(deltaX) > slop || Math.abs(deltaY) > slop) {
                            setPressed(false);
                            mPrivateFlags &= ~PFLAG_PREPRESSED;
                        }
                    }
                }
                break;
        }
        return true;
    }
    return false;
}

onTouchEvent 方法中,同样先获取触摸事件的相关信息以及 View 的视图标志。如果 View 被禁用,仅在事件为 ACTION_UP 且之前处于按下状态时,清除按下状态标志,并根据 View 的可点击状态返回结果。对于可点击的 View,根据不同的触摸动作进行处理。在 ACTION_UP 时,处理按下状态的清除、可能的延迟点击以及点击事件的触发;在 ACTION_DOWN 时,设置按下状态并检查是否触发长按事件;在 ACTION_CANCEL 时,清除按下状态;在 ACTION_MOVE 时,判断触摸移动是否超出阈值来决定是否保持按下状态。如果 View 处理了触摸事件(是可点击的或有相关处理逻辑),则返回 true,否则返回 false

5.2 事件处理的优先级与冲突解决

在 Android 视图体系中,事件处理的优先级和冲突解决是确保用户交互流畅和准确的关键。不同的视图组件可能对同一事件有不同的处理需求,这就需要一套规则来确定事件的处理顺序和解决冲突。

5.2.1 事件处理优先级

一般来说,事件处理的优先级遵循以下规则:

  1. 最内层的视图优先 :当触摸事件发生时,系统会首先尝试将事件分发给最内层的视图(即离触摸点最近的视图)。如果该视图处理了事件(onTouchEvent 返回 true),则事件处理流程结束,不再向上层视图传递。例如,在一个包含多个嵌套 ViewGroup 的布局中,如果最内层的 Button 视图处理了触摸事件,那么外层的 LinearLayoutRelativeLayoutViewGroup 将不会再收到该事件。
  2. onTouch 监听器优先于 onClick 监听器 :如果为一个视图同时设置了 OnTouchListenerOnClickListener,当触摸事件发生时,OnTouchListeneronTouch 方法会首先被调用。只有当 onTouch 方法返回 false 时,才会触发 OnClickListeneronClick 方法。这是因为 OnTouchListener 可以更细粒度地控制触摸事件的处理,例如在触摸按下、移动、抬起等不同阶段进行不同的操作。
  3. ViewGroup 的拦截机制影响优先级ViewGroup 可以通过重写 onInterceptTouchEvent 方法来决定是否拦截事件。如果 ViewGroup 拦截了事件(onInterceptTouchEvent 返回 true),则事件不会再分发给子视图,而是由 ViewGroup 自身处理。这种机制使得 ViewGroup 能够在必要时优先处理事件,例如在实现滑动菜单等功能时,ViewGroup 可以拦截触摸事件来处理滑动操作,而不会将事件传递给菜单中的子视图。
5.2.2 事件冲突解决

当多个视图对同一事件都有处理需求时,可能会出现事件冲突。常见的事件冲突场景包括:

  1. 滑动冲突 :例如,在一个包含可滑动列表(如 RecyclerView)和可滑动的 ViewGroup(如实现侧滑菜单的 DrawerLayout)的布局中,如果用户在列表区域进行滑动操作,可能会与 DrawerLayout 的滑动操作产生冲突。解决滑动冲突的方法通常有以下几种:

    • 根据滑动方向判断 :在 onInterceptTouchEvent 方法中,通过计算触摸事件的滑动方向来决定是否拦截事件。例如,如果检测到用户是水平滑动,且当前视图是 DrawerLayout,则拦截事件以处理侧滑菜单的展开和关闭;如果是垂直滑动,则不拦截,让 RecyclerView 处理列表的滚动。
    • 根据触摸位置判断 :根据触摸点的位置来决定事件的处理。例如,在 DrawerLayout 中,可以设置一个边缘区域(如左侧或右侧的一定宽度范围),当触摸点在这个区域内时,DrawerLayout 拦截事件处理侧滑;当触摸点在其他区域时,将事件传递给子视图。
  2. 点击冲突 :当多个可点击的视图重叠时,可能会出现点击冲突。例如,一个 Button 视图覆盖在另一个 ImageView 视图之上,且两个视图都设置了点击监听器。解决点击冲突的方法可以是:

    • 调整视图的层级关系 :通过设置视图的 z 轴坐标(在 Android 5.0 及以上版本支持)或在布局文件中调整视图的顺序,确保期望首先响应点击事件的视图处于上层。

    • 在事件处理方法中进行判断 :在视图的 onTouchEvent 方法中,根据触摸点的位置和视图的边界等信息,判断当前触摸事件是否应该由该视图处理。例如,可以计算触摸点是否在 Button 的边界范围内,如果是,则由 Button 处理事件;否则,将事件传递给下层的 ImageView

通过合理地利用事件处理的优先级规则和采用有效的冲突解决方法,开发者可以确保 Android 应用的视图交互体验更加流畅和符合用户预期。

相关推荐
UGOTNOSHOT7 分钟前
7.4项目一问题准备
面试
福柯柯17 分钟前
Android ContentProvider的使用
android·contenprovider
不想迷路的小男孩18 分钟前
Android Studio 中Palette跟Component Tree面板消失怎么恢复正常
android·ide·android studio
餐桌上的王子19 分钟前
Android 构建可管理生命周期的应用(一)
android
菠萝加点糖23 分钟前
Android Camera2 + OpenGL离屏渲染示例
android·opengl·camera
用户20187928316733 分钟前
🌟 童话:四大Context徽章诞生记
android
yzpyzp42 分钟前
Android studio在点击运行按钮时执行过程中输出的compileDebugKotlin 这个任务是由gradle执行的吗
android·gradle·android studio
aningxiaoxixi1 小时前
安卓之service
android
TeleostNaCl2 小时前
Android 应用开发 | 一种限制拷贝速率解决因 IO 过高导致系统卡顿的方法
android·经验分享
用户2018792831672 小时前
📜 童话:FileProvider之魔法快递公司的秘密
android