Activity
public View findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
从这个方法我们可以看到Activity中的findViewById方法是通过getWindow()方法返回的Window对象调用了其findViewById方法而得来的。
Window
public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
Window类中的findViewById方法返回的是getDecorView()的findViewById,前面说Window是个抽象类,不能具体实现管理ActivityUI组件的方法,那么这个getDecorView()就是返回的是一个具体的View对象,来管理Activity的UI组件。
View
public final View findViewById(@IdRes int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}
protected View findViewTraversal(@IdRes int id) {
if (id == mID) {
return this;
}
return null;
}
从这里看,View类的findViewById方法是直接调用findViewTraversal(),用mID跟传入的id对比,如果存在就返回当前view,不存在就返回null。findViewTraversal从字面上理解,这个方法的意思是反复寻找view的ID。但是findViewTraversal方法中似乎并没有发现“反复”或“循环”的意思。凭感觉,查找mID应该是不断+1比较,如果是ViewGroup(容器组件),里面还有View的话,还应该是一个递归的过程,从这里看不出来。所以我们还需要找mID是如何设置的。
public void setId(@IdRes int id) {
mID = id;
if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
mID = generateViewId();
}
}
private int mLabelForId = View.NO_ID;
先把id给了mID,如果mID为空(就是当前ID没有被加过),而且不是作为标签,那么就给他加一个ID,动态生成控件的ID。
public static int generateViewId() {
for (;;) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
一个固定地址开始,一个一个找ID,设置的ID值似乎连续的。然后在后面的查找中,找递归,找循环。。没有找到类似的代码。
在ViewGroup中,已经对View的findViewTraversal()方法进行重写了!
protected View findViewTraversal(@IdRes int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}
这个是重写的方法:可以非常清楚的看到,findViewById的原理,是从头开始找,遇到有子控件的,就递归接着找。先不考虑具体细节,到这里,开始的猜想得到了证实。其中v.findViewById又调用View中的方法,而View的findViewById调用的findViewTraversal()。这样实现了一个递归。
注:在找findViewById的过程中,如果忽略子类ViewGroup对View方法的重写,就会导致在View.class万行代码中陷入死循环,找不到想看到的方法。
现在为止,已经把findViewById的源码分析了一遍,我们再来总结一下吧。组件ID值(句柄)的设定,是通过View类实现的。在查找指定ID的时候,如果是容器组件的话,是通过被View的子类ViewGroup重写的findViewTraversal()方法实现的。查找ID的过程类似于数据结构中讲解的深度优先搜索的概念。Activity通过把ID值传递给Window类,Window类的方法通过返回的View对象,来管理Activity的UI组件。传递的ID值与提前设定好的ID进行比较,并且是按照View结构的DOM树进行深度优先搜索,从而获取指定的组件对象。
给定 2 个 View 视图引用,您将如何查找它们的祖先视图?
采用递归先序遍历的方法,分别从左右子树中找目标节点,如果左子树中找到了二者,则返回左子树,如果右子树找到了二者则返回右子树;左右子树各找到一个,则返回当前节点。
public class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left != null && right != null) return root;
return left != null ? left : right;
}
}