前言
最近的一系列源碼分析,都是基于一個(gè)錯(cuò)誤昧港,逐步深入源碼。這樣更有目的性的看源碼支子,思路會(huì)更清楚一點(diǎn)创肥。
網(wǎng)絡(luò)上有文章給出了有針對性的解決方案。我通過源碼給出更普通的解決思路值朋,這個(gè)問題叹侄,沒有特定的解決方案,所以只能領(lǐng)會(huì)精髓后昨登,隨機(jī)應(yīng)變趾代。
下面通過我遇到的具體問題,展開源碼的分析丰辣,所以不必太在意業(yè)務(wù)場景的相似撒强,重在領(lǐng)會(huì)精髓
報(bào)錯(cuò)
我的具體場景是禽捆,在從某一個(gè)界面跳轉(zhuǎn)到登錄界面時(shí),點(diǎn)擊輸入框EditText 時(shí)飘哨,出現(xiàn)的崩潰胚想。
java.lang.IllegalArgumentException: parameter must be a descendant of this view
at android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:6078)
at android.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.java:6007)
at android.view.FocusFinder.findNextFocusInAbsoluteDirection(FocusFinder.java:365)
at android.view.FocusFinder.findNextFocus(FocusFinder.java:268)
at android.view.FocusFinder.findNextFocus(FocusFinder.java:110)
at android.view.FocusFinder.findNextFocus(FocusFinder.java:80)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1027)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.ViewGroup.focusSearch(ViewGroup.java:1029)
at android.view.View.focusSearch(View.java:10843)
at android.widget.TextView.onCreateInputConnection(TextView.java:7862)
at androidx.appcompat.widget.AppCompatEditText.onCreateInputConnection(AppCompatEditText.java:186)
at android.view.inputmethod.InputMethodManager.startInputInner(InputMethodManager.java:1290)
at android.view.inputmethod.InputMethodManager.checkFocus(InputMethodManager.java:1485)
at android.view.inputmethod.InputMethodManager.viewClicked(InputMethodManager.java:1667)
at android.widget.TextView.viewClicked(TextView.java:12009)
at android.widget.TextView.onTouchEvent(TextView.java:10109)
at android.view.View.dispatchTouchEvent(View.java:12513)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:440)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)
at android.app.Activity.dispatchTouchEvent(Activity.java:3400)
源碼分析 Android 獲取View焦點(diǎn)的流程
深入剖析崩潰的原因,涉及到Android其他方面的知識(shí)芽隆,所以這里只分析到引出這個(gè)異常的地方
下面是跳轉(zhuǎn)到登錄界面后浊服,點(diǎn)擊輸入框EditText 時(shí),點(diǎn)擊事件層層分發(fā)胚吁,到focusSearch
在指定方向上牙躺,搜索下一個(gè)可以獲取焦點(diǎn)的View
mParent.focusSearch(this, direction)
有兩處實(shí)現(xiàn),分別是:RecyclerView 囤采、ViewGroup
下面分析ViewGroup中的focusSearch(this, direction)
代碼段1
/**
* Find the nearest view in the specified direction that wants to take
* focus.
*
* @param focused The view that currently has focus
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
* FOCUS_RIGHT, or 0 for not applicable.
*/
@Override
public View focusSearch(View focused, int direction) {
if (isRootNamespace()) {
//如果是根布局也就是 DecorView
// root namespace means we should consider ourselves the top of the
// tree for focus searching; otherwise we could be focus searching
// into other tabs. see LocalActivityManager and TabHost for more info.
return FocusFinder.getInstance().findNextFocus(this, focused, direction);
} else if (mParent != null) {
//不斷調(diào)用父視圖的focusSearch
return mParent.focusSearch(focused, direction);
}
return null;
}
接著調(diào)用了FocusFinder 里面的函數(shù) findNextFocus(ViewGroup root, View focused, int direction)
代碼段2
/**
* Find the next view to take focus in root's descendants, starting from the view
* that currently is focused.
* @param root Contains focused. Cannot be null.
* @param focused Has focus now.
* @param direction Direction to look.
* @return The next focusable view, or null if none exists.
*/
public final View findNextFocus(ViewGroup root, View focused, int direction) {
return findNextFocus(root, focused, null, direction);
}
代碼段3
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
if (focused != null) {
//查找下一個(gè)由用戶指定的可獲取焦點(diǎn)的View
//那么如何指定下一個(gè)獲取焦點(diǎn)的view呢述呐?通過View 中的一系列函數(shù) setNextFocusLeftId setNextFocusRightId setNextFocusUpId setNextFocusDownId setNextFocusForwardId 來設(shè)置
next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
}
if (next != null) {
//如果找到符合條件的view,則返回
return next;
}
// 如果沒有找到蕉毯,則通過遍歷root(也就是DecorView)下所有的可獲取焦點(diǎn)的非touch_mode的 view
ArrayList<View> focusables = mTempList;
try {
focusables.clear();
//遍歷是從這個(gè)函數(shù)開始的乓搬,所有符合條件的view被添加到focusables
effectiveRoot.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
//在focusables 中查詢,下一個(gè)可以獲取焦點(diǎn)的view
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
下面詳細(xì)的分析一下 effectiveRoot.addFocusables(focusables, direction);
:
代碼段4
/**
* Add any focusable views that are descendants of this view (possibly
* including this view if it is focusable itself) to views. If we are in touch mode,
* only add views that are also focusable in touch mode.
*
* @param views Focusable views found so far
* @param direction The direction of the focus
*/
public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
//這個(gè)方法有5個(gè)地方實(shí)現(xiàn)了它:DrawerLayout代虾、RecyclerView进肯、View、ViewGroup棉磨、ViewPage
addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
}
下面主要對ViewGroup和View 中的addFocusables
進(jìn)行分析
View 中的addFocusables
函數(shù)
代碼段5
public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
@FocusableMode int focusableMode) {
if (views == null) {
return;
}
//如果不能獲取焦點(diǎn)江掩,就不添加到views中,直接返回
if (!canTakeFocus()) {
return;
}
//如果是觸摸模式乘瓤,并且在觸摸模式下不能獲取焦點(diǎn)环形,直接返回
//也就是說,如果不是觸摸模式或者觸摸模式下可獲取焦點(diǎn)衙傀,就添加到views
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
&& !isFocusableInTouchMode()) {
return;
}
views.add(this);
}
關(guān)于TOUCH_MODE更詳細(xì)的說明抬吟,參考官方博客
ViewGroup 中的addFocusables
函數(shù)
代碼段6
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size();
//自身ViewGroup與它后代view的關(guān)系,是在后代view之前统抬、之后獲取焦點(diǎn)火本,或者不讓后臺(tái)view 獲取焦點(diǎn)
final int descendantFocusability = getDescendantFocusability();
final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
//isFocusableInTouchMode() 在touchMode下 是否可以獲取或保持焦點(diǎn)
final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);
//后代view不能獲取焦點(diǎn)
if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
//自己可以獲取焦點(diǎn)
if (focusSelf) {
//調(diào)用view 中的addFocusables,把當(dāng)前布局添加到views
super.addFocusables(views, direction, focusableMode);
}
return;
}
if (blockFocusForTouchscreen) {
focusableMode |= FOCUSABLES_TOUCH_MODE;
}
//在后代view之前獲取焦點(diǎn)聪建,并且自己可以獲取焦點(diǎn)
if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
//調(diào)用view 中的addFocusables钙畔,把當(dāng)前布局添加到views
super.addFocusables(views, direction, focusableMode);
}
int count = 0;
final View[] children = new View[mChildrenCount];
for (int i = 0; i < mChildrenCount; ++i) {
View child = mChildren[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
//獲取當(dāng)前view 下可見的子view
children[count++] = child;
}
}
FocusFinder.sort(children, 0, count, this, isLayoutRtl());
for (int i = 0; i < count; ++i) {
//遍歷子view,如果是view就添加到 views金麸,如果是viewGroup就再次調(diào)用addFocusables進(jìn)行判斷
children[i].addFocusables(views, direction, focusableMode);
}
// When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
// there aren't any focusable descendants. this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
//在后代view之后獲取焦點(diǎn)擎析,并且自己可以獲取焦點(diǎn) 并且僅在沒有可聚焦后代(views的數(shù)量沒有變)的情況下添加自己
if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
&& focusableCount == views.size()) {
super.addFocusables(views, direction, focusableMode);
}
}
這篇文章所給出的解決方法,就是
代碼段3 中effectiveRoot.addFocusables(focusables, direction);
調(diào)用后 挥下,focusables 中是所有可獲取焦點(diǎn)的View叔锐,在非空的情況下調(diào)用如下代碼findNextFocus
函數(shù)
代碼段7
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList<View> focusables) {
if (focused != null) {
//本篇文章分析的流程挪鹏,focused 不為空
if (focusedRect == null) {
focusedRect = mFocusedRect;
}
// fill in interesting rect from focused
//獲取focused所在的矩形區(qū)域到mOtherRect中
focused.getFocusedRect(focusedRect);
//把focused的坐標(biāo)置侍,轉(zhuǎn)換為相對于root的坐標(biāo)
root.offsetDescendantRectToMyCoords(focused, focusedRect);
} else {
//如果focused 為空惭缰,就在root布局的指定方向添加一個(gè)focusedRect
if (focusedRect == null) {
focusedRect = mFocusedRect;
// make up a rect at top left or bottom right of root
switch (direction) {
case View.FOCUS_RIGHT:
case View.FOCUS_DOWN:
setFocusTopLeft(root, focusedRect);
break;
case View.FOCUS_FORWARD:
if (root.isLayoutRtl()) {
setFocusBottomRight(root, focusedRect);
} else {
setFocusTopLeft(root, focusedRect);
}
break;
case View.FOCUS_LEFT:
case View.FOCUS_UP:
setFocusBottomRight(root, focusedRect);
break;
case View.FOCUS_BACKWARD:
if (root.isLayoutRtl()) {
setFocusTopLeft(root, focusedRect);
} else {
setFocusBottomRight(root, focusedRect);
break;
}
}
}
}
switch (direction) {
case View.FOCUS_FORWARD:
case View.FOCUS_BACKWARD:
return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
direction);
case View.FOCUS_UP:
case View.FOCUS_DOWN:
case View.FOCUS_LEFT:
case View.FOCUS_RIGHT:
return findNextFocusInAbsoluteDirection(focusables, root, focused,
focusedRect, direction);
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
}
}
- 如果focused不是null,說明當(dāng)前獲取到焦點(diǎn)的View存在愈案,則獲得繪制焦點(diǎn)的Rect到focusedRect步责,然后根據(jù)rootView遍歷所有ParentView從子View糾正坐標(biāo)到根View坐標(biāo)返顺。
- 如果focused是null,則說明當(dāng)前沒有View獲取到焦點(diǎn)蔓肯,則把focusedRect根據(jù)不同的direction重置為“一點(diǎn)”遂鹊。
根據(jù)direction調(diào)用FocusFinder::findNextFocusInAbsoluteDirection方法進(jìn)行對比查找“下一個(gè)”View。
代碼段8
View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
Rect focusedRect, int direction) {
// initialize the best candidate to something impossible
// (so the first plausible view will become the best choice)
//先設(shè)置focusedRect 為最佳的候選矩陣
mBestCandidateRect.set(focusedRect);
//根據(jù)不同的方向蔗包,偏移一個(gè)像素秉扑,為了方便比較?
switch(direction) {
case View.FOCUS_LEFT:
mBestCandidateRect.offset(focusedRect.width() + 1, 0);
break;
case View.FOCUS_RIGHT:
mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
break;
case View.FOCUS_UP:
mBestCandidateRect.offset(0, focusedRect.height() + 1);
break;
case View.FOCUS_DOWN:
mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
}
View closest = null;
int numFocusables = focusables.size();
for (int i = 0; i < numFocusables; i++) {
View focusable = focusables.get(i);
// only interested in other non-root views
if (focusable == focused || focusable == root) continue;
// get focus bounds of other view in same coordinate system
//獲取focusable所在的矩形區(qū)域到mOtherRect中调限,這個(gè)focusable是之前獲取的可聚焦的views
focusable.getFocusedRect(mOtherRect);
//把focusable的坐標(biāo)(矩陣)舟陆,轉(zhuǎn)換為相對于root的坐標(biāo)(矩陣)
root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
//mOtherRect 是否比mBestCandidateRect 更優(yōu)
if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
//如果mOtherRect 更優(yōu),則mBestCandidateRect設(shè)置為mOtherRect 耻矮,for循環(huán)結(jié)束后秦躯,得到最優(yōu)的
mBestCandidateRect.set(mOtherRect);
closest = focusable;
}
}
//返回最優(yōu)的view
return closest;
}
代碼段9
/**
* Offset a rectangle that is in a descendant's coordinate
* space into our coordinate space.
* @param descendant A descendant of this view
* @param rect A rectangle defined in descendant's coordinate space.
*/
public final void offsetDescendantRectToMyCoords(View descendant, Rect rect) {
offsetRectBetweenParentAndChild(descendant, rect, true, false);
}
代碼段10
/**
* Helper method that offsets a rect either from parent to descendant or
* descendant to parent.
*/
void offsetRectBetweenParentAndChild(View descendant, Rect rect,
boolean offsetFromChildToParent, boolean clipToBounds) {
// already in the same coord system :)
if (descendant == this) {
return;
}
ViewParent theParent = descendant.mParent;
// search and offset up to the parent
//通過不斷的循環(huán),把descendant的坐標(biāo)裆装,也就是矩陣rect踱承,轉(zhuǎn)換為相對于當(dāng)前view(因?yàn)閛ffsetRectBetweenParentAndChild是view的方法)的坐標(biāo)(矩陣)
while ((theParent != null)
&& (theParent instanceof View)
&& (theParent != this)) {
if (offsetFromChildToParent) {
//偏移矩陣,例如:布局viewGoupA 里面有viewGoupB 里面有 view C
//把view C 相對于父布局viewGoupB的坐標(biāo)轉(zhuǎn)換為相對于viewGoupA的坐標(biāo)
//這里mLeft 相對于父布局的x坐標(biāo)哨免,-x方向滾動(dòng)的距離mScrollX茎活,才是原來真是的位置
rect.offset(descendant.mLeft - descendant.mScrollX,
descendant.mTop - descendant.mScrollY);
if (clipToBounds) {
//修剪矩陣
View p = (View) theParent;
//intersect 壓緊到公共區(qū)域
boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
p.mBottom - p.mTop);
if (!intersected) {
rect.setEmpty();
}
}
} else {
//上面的反向操作,布局viewGoupA 里面有viewGoupB 里面有 view C
//view C 已經(jīng)轉(zhuǎn)為相對viewGoupA的坐標(biāo)了琢唾,下面的操作就是轉(zhuǎn)為相對于viewGoupB的坐標(biāo)
if (clipToBounds) {
View p = (View) theParent;
boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
p.mBottom - p.mTop);
if (!intersected) {
rect.setEmpty();
}
}
//偏移矩陣载荔,從父view 到子view
rect.offset(descendant.mScrollX - descendant.mLeft,
descendant.mScrollY - descendant.mTop);
}
descendant = (View) theParent;
theParent = descendant.mParent;
}
// now that we are up to this view, need to offset one more time
// to get into our coordinate space
if (theParent == this) {
if (offsetFromChildToParent) {
rect.offset(descendant.mLeft - descendant.mScrollX,
descendant.mTop - descendant.mScrollY);
} else {
rect.offset(descendant.mScrollX - descendant.mLeft,
descendant.mScrollY - descendant.mTop);
}
} else {
//經(jīng)過我的分析,這個(gè)錯(cuò)誤只有兩種情況
throw new IllegalArgumentException("parameter must be a descendant of this view");
}
}
<font color =blue>經(jīng)過我的分析慧耍,這個(gè)錯(cuò)誤只有兩種情況:
- descendant 不是當(dāng)前view 的后代 ,(theParent instanceof View) == false 退出循環(huán) ,descendant 為ViewRootImpl (就是這個(gè)解決方法[Another java.lang.IllegalArgumentException: parameter must be a descendant of this view](https://stackoverflow.com/questions/30585561/another-java-lang-illegalargumentexception-parameter-must -be-a-descendant-of-th))
-
descendant 的mParent為空 (theParent != null) == false 退出循環(huán)丐谋。我遇到的就是這個(gè)問題
下面繼續(xù)回到代碼段8芍碧,來看看函數(shù)isBetterCandidate
是怎么比較出更優(yōu)的rect
代碼段11
/**
* Is rect1 a better candidate than rect2 for a focus search in a particular
* direction from a source rect? This is the core routine that determines
* the order of focus searching.
* @param direction the direction (up, down, left, right)
* @param source The source we are searching from
* @param rect1 The candidate rectangle
* @param rect2 The current best candidate.
* @return Whether the candidate is the new best.
*/
boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
// to be a better candidate, need to at least be a candidate in the first
// place :)
//rect1是否在source的指定方向direction的下一個(gè)可獲得焦點(diǎn)的矩陣
if (!isCandidate(source, rect1, direction)) {
//rect1不是候選的,說明rect1 沒有rect2 更優(yōu)号俐,返回false
return false;
}
// we know that rect1 is a candidate.. if rect2 is not a candidate,
// rect1 is better
if (!isCandidate(source, rect2, direction)) {
//rect2不是候選的泌豆,說明rect1 比rect2 更優(yōu),返回true
return true;
}
//如果都是候選的吏饿,比較rect1 和rect2 哪個(gè)更優(yōu)踪危,比較的方法大概是:兩個(gè)候選rect分表與source比較蔬浙,是否重疊,是否在希望的方向上等
// if rect1 is better by beam, it wins
if (beamBeats(direction, source, rect1, rect2)) {
return true;
}
// if rect2 is better, then rect1 cant' be :)
if (beamBeats(direction, source, rect2, rect1)) {
return false;
}
// otherwise, do fudge-tastic comparison of the major and minor axis
return (getWeightedDistanceFor(
majorAxisDistance(direction, source, rect1),
minorAxisDistance(direction, source, rect1))
< getWeightedDistanceFor(
majorAxisDistance(direction, source, rect2),
minorAxisDistance(direction, source, rect2)));
}
解決方法:
根據(jù)上面的原因贞远,對應(yīng)兩種解決方法:
1畴博、就是可獲取焦點(diǎn)的view是在報(bào)錯(cuò)View的后代
2、保證可獲取焦點(diǎn)view的mParent 不為null
這篇文章蓝仲,與我分析的第二種解決方案一樣俱病,他給出的解決方案更為具體,可以參考
【原創(chuàng)】【ViewFlow+GridView】Parameter must be a descendant of this view問題分析
這篇文章袱结,分析了本篇文章中亮隙,未涉及到的其他的幾處代碼實(shí)現(xiàn)
Android焦點(diǎn)流程代碼分析