1 java.lang.IllegalArgumentException: parameter must be a descendant of this view 2 at android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:4295) 3 at android.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.java:4232) 4 at android.view.ViewRootImpl.scrollToRectOrFocus(ViewRootImpl.java:2440) 5 at android.view.ViewRootImpl.draw(ViewRootImpl.java:2096) 6 at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2045) 7 at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1854) 8 at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:989) 9 at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4351) 10 at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749) 11 at android.view.Choreographer.doCallbacks(Choreographer.java:562) 12 at android.view.Choreographer.doFrame(Choreographer.java:532) 13 at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735) 14 at android.os.Handler.handleCallback(Handler.java:725) 15 at android.os.Handler.dispatchMessage(Handler.java:92) 16 at android.os.Looper.loop(Looper.java:137) 17 at android.app.ActivityThread.main(ActivityThread.java:5041) 18 at java.lang.reflect.Method.invokeNative(Native Method) 19 at java.lang.reflect.Method.invoke(Method.java:511) 20 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793) 21 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560) 22 at dalvik.system.NativeStart.main(Native Method)
1 /** 2 * Helper method that offsets a rect either from parent to descendant or 3 * descendant to parent. 4 */ 5 void offsetRectBetweenParentAndChild(View descendant, Rect rect, 6 boolean offsetFromChildToParent, boolean clipToBounds) { 7 8 // already in the same coord system :) 9 if (descendant == this) { 10 return; 11 } 12 13 ViewParent theParent = descendant.mParent; 14 15 // search and offset up to the parent 16 while ((theParent != null) 17 && (theParent instanceof View) 18 && (theParent != this)) { 19 20 if (offsetFromChildToParent) { 21 rect.offset(descendant.mLeft - descendant.mScrollX, 22 descendant.mTop - descendant.mScrollY); 23 if (clipToBounds) { 24 View p = (View) theParent; 25 rect.intersect(0, 0, p.mRight - p.mLeft, p.mBottom - p.mTop); 26 } 27 } else { 28 if (clipToBounds) { 29 View p = (View) theParent; 30 rect.intersect(0, 0, p.mRight - p.mLeft, p.mBottom - p.mTop); 31 } 32 rect.offset(descendant.mScrollX - descendant.mLeft, 33 descendant.mScrollY - descendant.mTop); 34 } 35 36 descendant = (View) theParent; 37 theParent = descendant.mParent; 38 } 39 40 // now that we are up to this view, need to offset one more time 41 // to get into our coordinate space 42 if (theParent == this) { 43 if (offsetFromChildToParent) { 44 rect.offset(descendant.mLeft - descendant.mScrollX, 45 descendant.mTop - descendant.mScrollY); 46 } else { 47 rect.offset(descendant.mScrollX - descendant.mLeft, 48 descendant.mScrollY - descendant.mTop); 49 } 50 } else { 51 throw new IllegalArgumentException("parameter must be a descendant of this view"); 52 } 53 }
在方法最后可以看到该异常。那么该异常到底表示什么意思呢?若想知道答案,我们需要从该方法的实现入手。
1 ViewParent theParent = descendant.mParent; 2 3 // search and offset up to the parent 4 while ((theParent != null) 5 && (theParent instanceof View) 6 && (theParent != this)) {
当Descendant View的Parent为null、非View实例、当前View时,会跳出循环进入最后的判断。排除当前View,就只剩下两个原因:null和非View实例。
1 /** 2 * The parent this view is attached to. 3 * {@hide} 4 * 5 * @see #getParent() 6 */ 7 protected ViewParent mParent;
赋值:
1 /* 2 * Caller is responsible for calling requestLayout if necessary. 3 * (This allows addViewInLayout to not request a new layout.) 4 */ 5 void assignParent(ViewParent parent) { 6 if (mParent == null) { 7 mParent = parent; 8 } else if (parent == null) { 9 mParent = null; 10 } else { 11 throw new RuntimeException("view " + this + " being added, but" 12 + " it already has a parent"); 13 } 14 }
透过上述代码,我们可以猜测mParent的赋值方式有两种:直接赋值和调用assignParent方法赋值。
1 protected void recycleView(View v) { 2 if (v == null) 3 return ; 4 5 mRecycledViews.add(v); 6 detachViewFromParent(v); 7 }
该方法是把ViewFlow的Child移除,并回收到循环利用列表。注意最后一行,调用了detachViewFromParent(View v)方法,代码如下:
1 /** 2 * Detaches a view from its parent. Detaching a view should be temporary and followed 3 * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} 4 * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, 5 * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. 6 * 7 * @param child the child to detach 8 * 9 * @see #detachViewFromParent(int) 10 * @see #detachViewsFromParent(int, int) 11 * @see #detachAllViewsFromParent() 12 * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams) 13 * @see #removeDetachedView(View, boolean) 14 */ 15 protected void detachViewFromParent(View child) { 16 removeFromArray(indexOfChild(child)); 17 }
很明显,直接调用了removeFromArray(int index)方法,正是在4.2.4节中指出的第一个方法,而该方法已经在本节开头被确定为真凶!
1 protected void recycleView(View v) { 2 if (v == null) 3 return; 4 5 // 方法一:普通方案,已验证可行 6 // 如果被移除的View恰好是ViewFlow内当前焦点所在View 7 // 则清除焦点(clearChildFocus方法在清除焦点的同时 8 // 也把ViewGroup内保存的Focused View引用清除) 9 if (v == findFocus()) { 10 clearChildFocus(v); 11 } 12 13 // 方法二:文艺方案,请自行验证! 14 // 下面这个方法也是把View的焦点清除,但是其是否起作用 15 // 这里不讲,请读者自行验证、比较。 16 // v.clearFocus(); 17 18 mRecycledViews.add(v); 19 detachViewFromParent(v); 20 }
注意代码内的注释。
1 /** 2 * {@inheritDoc} 3 */ 4 public void clearChildFocus(View child) { 5 if (DBG) { 6 System.out.println(this + " clearChildFocus()"); 7 } 8 9 mFocused = null; 10 if (mParent != null) { 11 mParent.clearChildFocus(this); 12 } 13 }
View.clearFocus():
1 /** 2 * Called when this view wants to give up focus. This will cause 3 * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called. 4 */ 5 public void clearFocus() { 6 if (DBG) { 7 System.out.println(this + " clearFocus()"); 8 } 9 10 if ((mPrivateFlags & FOCUSED) != 0) { 11 mPrivateFlags &= ~FOCUSED; 12 13 if (mParent != null) { 14 mParent.clearChildFocus(this); 15 } 16 17 onFocusChanged(false, 0, null); 18 refreshDrawableState(); 19 } 20 }
当然,解决问题方法不止一种!
1 /** 2 * {@inheritDoc} 3 */ 4 public void requestChildFocus(View child, View focused) { 5 if (DBG) { 6 System.out.println(this + " requestChildFocus()"); 7 } 8 if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) { 9 return; 10 } 11 12 // Unfocus us, if necessary 13 super.unFocus(); 14 15 // We had a previous notion of who had focus. Clear it. 16 if (mFocused != child) { 17 if (mFocused != null) { 18 mFocused.unFocus(); 19 } 20 21 mFocused = child; 22 } 23 if (mParent != null) { 24 mParent.requestChildFocus(this, focused); 25 } 26 }
注意第二个判断条件:如果ViewGroup当前的焦点传递策略是不向下传递,则不指定Focused View。
So,下面该如何做,你懂的!整个世界清静了~