大厂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 应用的视图交互体验更加流畅和符合用户预期。

相关推荐
张风捷特烈13 分钟前
平面上的三维空间#04 | 万物之母 - 三角形
android·flutter·canvas
恋猫de小郭1 小时前
Android Studio Cloud 正式上线,不只是 Android,随时随地改 bug
android·前端·flutter
匹马夕阳7 小时前
(十八)安卓开发中的后端接口调用详讲解
android
Pigwantofly8 小时前
鸿蒙ArkTS实战:从零打造智能表达式计算器(附状态管理+路由传参核心实现)
android·华为·harmonyos
拉不动的猪9 小时前
设计模式之------单例模式
前端·javascript·面试
xiegwei9 小时前
Kotlin 和 spring-cloud-function 兼容问题
开发语言·kotlin·springcloud
Gracker10 小时前
Android Weekly #202514
android
binderIPC10 小时前
Android之JNI详解
android
林志辉linzh10 小时前
安卓AssetManager【一】- 资源的查找过程
android·resources·assetmanger·安卓资源管理·aapt·androidfw·assetmanger2
互联网老辛10 小时前
【读者求助】如何跨行业进入招聘岗位?
面试·个人思考