parameter must be a descendant of this view 报错解决方案及Android 获取View焦点源码分析

Posted 薛瑄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了parameter must be a descendant of this view 报错解决方案及Android 获取View焦点源码分析相关的知识,希望对你有一定的参考价值。

前言

最近的一系列源码分析,都是基于一个错误,逐步深入源码。这样更有目的性的看源码,思路会更清楚一点。
网络上有文章给出了有针对性的解决方案。我通过源码给出更普通的解决思路,这个问题,没有特定的解决方案,所以只能领会精髓后,随机应变。

下面通过我遇到的具体问题,展开源码的分析,所以不必太在意业务场景的相似,重在领会精髓

报错

我的具体场景是,在从某一个界面跳转到登录界面时,点击输入框EditText 时,出现的崩溃。

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焦点的流程

深入剖析崩溃的原因,涉及到Android其他方面的知识,所以这里只分析到引出这个异常的地方

下面是跳转到登录界面后,点击输入框EditText 时,点击事件层层分发,到focusSearch 在指定方向上,搜索下一个可以获取焦点的View
mParent.focusSearch(this, direction)有两处实现,分别是: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) 
            //不断调用父视图的focusSearch
            return mParent.focusSearch(focused, direction);
        
        return null;
    

接着调用了FocusFinder 里面的函数 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) 
        	//查找下一个由用户指定的可获取焦点的View
        	//那么如何指定下一个获取焦点的view呢?通过View 中的一系列函数 setNextFocusLeftId setNextFocusRightId setNextFocusUpId setNextFocusDownId setNextFocusForwardId 来设置
            next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
        
        if (next != null) 
            //如果找到符合条件的view,则返回
            return next;
        

        // 如果没有找到,则通过遍历root(也就是DecorView)下所有的可获取焦点的非touch_mode的 view 
        ArrayList<View> focusables = mTempList;
        try 
            focusables.clear();
            //遍历是从这个函数开始的,所有符合条件的view被添加到focusables
            effectiveRoot.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) 
               //在focusables 中查询,下一个可以获取焦点的view
                next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
            
         finally 
            focusables.clear();
        
        return next;
    

下面详细的分析一下 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) 

        //这个方法有5个地方实现了它:DrawerLayout、RecyclerView、View、ViewGroup、ViewPage
        addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
    

下面主要对ViewGroup和View 中的addFocusables 进行分析

View 中的addFocusables 函数

代码段5
    public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
            @FocusableMode int focusableMode) 
        if (views == null) 
            return;
        
        //如果不能获取焦点,就不添加到views中,直接返回
        if (!canTakeFocus()) 
            return;
        
        //如果是触摸模式,并且在触摸模式下不能获取焦点,直接返回
        //也就是说,如果不是触摸模式或者触摸模式下可获取焦点,就添加到views
        if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
                && !isFocusableInTouchMode()) 
            return;
        
        views.add(this);
    

关于TOUCH_MODE更详细的说明,参考官方博客

ViewGroup 中的addFocusables 函数

代码段6
    @Override
    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) 
        final int focusableCount = views.size();
		//自身ViewGroup与它后代view的关系,是在后代view之前、之后获取焦点,或者不让后台view 获取焦点
        final int descendantFocusability = getDescendantFocusability();
        final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
         //isFocusableInTouchMode() 在touchMode下 是否可以获取或保持焦点
        final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);

		//后代view不能获取焦点
        if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) 
            //自己可以获取焦点
            if (focusSelf) 
                //调用view 中的addFocusables,把当前布局添加到views
                super.addFocusables(views, direction, focusableMode);
            
            return;
        

        if (blockFocusForTouchscreen) 
            focusableMode |= FOCUSABLES_TOUCH_MODE;
        
		//在后代view之前获取焦点,并且自己可以获取焦点
        if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) 
            //调用view 中的addFocusables,把当前布局添加到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) 
               //获取当前view 下可见的子view
                children[count++] = child;
            
        
        FocusFinder.sort(children, 0, count, this, isLayoutRtl());
        for (int i = 0; i < count; ++i) 
            //遍历子view,如果是view就添加到 views,如果是viewGroup就再次调用addFocusables进行判断
            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之后获取焦点,并且自己可以获取焦点 并且仅在没有可聚焦后代(views的数量没有变)的情况下添加自己
        if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
                && focusableCount == views.size()) 
            super.addFocusables(views, direction, focusableMode);
        
    

这篇文章所给出的解决方法,就是

代码段3 中effectiveRoot.addFocusables(focusables, direction);调用后 ,focusables 中是所有可获取焦点的View,在非空的情况下调用如下代码findNextFocus函数

代码段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所在的矩形区域到mOtherRect中
            focused.getFocusedRect(focusedRect);
            //把focused的坐标,转换为相对于root的坐标
            root.offsetDescendantRectToMyCoords(focused, focusedRect);
         else 
           //如果focused  为空,就在root布局的指定方向添加一个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,说明当前获取到焦点的View存在,则获得绘制焦点的Rect到focusedRect,然后根据rootView遍历所有ParentView从子View纠正坐标到根View坐标。
  • 如果focused是null,则说明当前没有View获取到焦点,则把focusedRect根据不同的direction重置为“一点”。

根据direction调用FocusFinder::findNextFocusInAbsoluteDirection方法进行对比查找“下一个”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)
        //先设置focusedRect 为最佳的候选矩阵
        mBestCandidateRect.set(focusedRect);
        //根据不同的方向,偏移一个像素,为了方便比较?
        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以上是关于parameter must be a descendant of this view 报错解决方案及Android 获取View焦点源码分析的主要内容,如果未能解决你的问题,请参考以下文章

ValueError: For multi-metric scoring, the parameter refit must be set to a scorer key or a callable(

spring-cloud-feign 使用@RequetParam报错QueryMap parameter must be a Map: class java.lang.String

mysql 执行 sql 语句提示Parameter '@XXX' must be defined

parameter must be a descendant of this view 报错解决方案及Android 获取View焦点源码分析

Oracle存储过程报错ORA-02069: global_names parameter must be set to TRUE for this operation

java.io.IOException: Target host must not be null, or set in parameters. scheme=null, host=null, pat