精华内容
下载资源
问答
  • ❤很多时候我们需要在控件失去焦点之前,再去判断其内容,比如说:想限制TextBox控件中内容为0-10的数字(你也可以用键盘Key事件,这个另说),想把ComboBox新输入的内容加入到列表选项中,Validate事件就是很不错...

    Validate事件和CauseValidation属性

    前言
    ❤很多时候我们需要在控件失去焦点之前,再去判断其内容,比如说:想限制TextBox控件中内容为0-10的数字(你也可以用键盘Key事件,这个另说),想把ComboBox新输入的内容加入到列表选项中时,Validate事件就是很不错的选择。

    内容:
    ❤Validate事件:前一个控件的焦点转移到后一个控件上前,验证前一个控件的有效性,触发此事件,其实就可以理解为失去焦点事件。
    ❤CauseValidation属性:Validate事件触发是有条件的,就是前后两个控件的CauseValidation属性必须都为True,有一个为False,则不触发该事件。

    注意:很多控件都有Validate事件和CauseValidation属性,但是Label和Image是没有的,CommandButton有CauseValidation属性但是没有Validate事件。

    展开全文
  • onBlur:当失去输入焦点后产生该事件 onFocus:当输入获得焦点后,产生该文件 Onchange:当文字值改变,产生该事件 Onselect:当文字加亮后,产生该文件 onpropertychange 当属性改变发生该事件 无论粘贴 keyup ...
  • C#控件 失去Focus焦点事件与获得焦点事件 文本框失去焦点事件 LostFocus GotFocus事件 供大家学习,减为1分了。。。。
  • MSDN上说是按照这个顺序来进行的 Leave--...可是跟踪代码发现顺序怎么是LostFocus--Leave--Validating--Validated呢?后来发现原来还和离开的方法有关系。当使用鼠标或直接设置焦点离开的时候顺序应当为LostFo...
           MSDN上说是按照这个顺序来进行的 Leave--Validating--Validated--LostFocus(不特殊说明时CausesValidation为true)。可是在跟踪代码时发现顺序怎么是LostFocus--Leave--Validating--Validated呢?后来发现原来还和离开的方法有关系。当使用鼠标或直接设置焦点离开的时候顺序应当为LostFocus--Leave--Validating--Validated,而MSDN上说的那个顺序是以TAB键移动焦点时的顺序。仔细看了一下MSDN中好像并没有对此有明确的说明,而且LostFocus在视图设计器中都没有,只能通过代码来添加,是否是意味着这个事件不是很推荐使用呢?

    离开方式

    使用Tab键或set Focus

    使用鼠标点击

    触发顺序

    Leave--Validating--Validated--LostFocus

    LostFocus--Leave--Validating--Validated


         另外若将要移动到的下一个控件的CausesValidation为false时,则原控件的Validating和Validated事件并不会触发。它们将会发生在下一次焦点移动到CausesValidation为true的控件的GotFocus事件之前。而且若这个CausesValidation为true的控件为自身,也不会引发这两个事件,在MSDN中好像也没有非常明确的说明。

         A,C的CausesValidation为true,B的CausesValidation为false  用鼠标点击移动

    移动顺序

    A--B--C

    A--B--A

    触发顺序

    A.LostFocus--A.Leave--B.GotFocus--B.LostFocus--B.Leave--A.Validating--A.Validated--C.GotFocus

    A.LostFocus--A.Leave--B.GotFocus--B.LostFocus--B.Leave--A.GotFocus
        
         第一组中A的Validating和Validated事件发生在C的GotFoucus之前,而第二组中A的第一次Validating和Validated事件就不会发生了

    转载于:https://www.cnblogs.com/aiyagaze/archive/2006/09/09/499626.html

    展开全文
  • onblur事件只有表单元素具有这个事件!比如说一个输入用户名的登陆框,你把鼠标点上去,光标变一闪一闪的提示你输入信息,这个时候...也就是说一个从来没得到过焦点的表单元素是不可能会失去焦点的!也就不会触发onblur事件!
    onblur事件只有表单元素具有这个事件!比如说一个输入用户名的登陆框,你把鼠标点上去,光标变一闪一闪的提示你输入信息,这个时候就是得到焦点,就会触发onfocus事件,当你把鼠标点击到输入框外,这个时候就是失去焦点,就会触发onblur事件,一个表单元素的onblur事件是必须在它得到焦点过后才会触发的!也就是说一个从来没得到过焦点的表单元素是不可能会失去焦点的!也就不会触发onblur事件!
    展开全文
  • Android 控件获取焦点

    千次阅读 2021-10-29 22:23:45
    请求焦点   方法requestFocus()代码如下: public final boolean requestFocus() { return requestFocus(View.FOCUS_DOWN); } public final boolean requestFocus(int direction) { return requestFocus...

    控件请求焦点

      方法requestFocus()代码如下:

        public final boolean requestFocus() {
            return requestFocus(View.FOCUS_DOWN);
        }
    
        public final boolean requestFocus(int direction) {
            return requestFocus(direction, null);
        }
    
        public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
            return requestFocusNoSearch(direction, previouslyFocusedRect);
        }
    
        private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
            // need to be focusable
            if (!canTakeFocus()) {
                return false;
            }
    
            // need to be focusable in touch mode if in touch mode
            if (isInTouchMode() &&
                (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
                   return false;
            }
    
            // need to not have any parents blocking us
            if (hasAncestorThatBlocksDescendantFocus()) {
                return false;
            }
    
            if (!isLayoutValid()) {
                mPrivateFlags |= PFLAG_WANTS_FOCUS;
            } else {
                clearParentsWantFocus();
            }
    
            handleFocusGainInternal(direction, previouslyFocusedRect);
            return true;
        }
    

      最终代码走到方法requestFocusNoSearch()方法中,canTakeFocus()方法代表控件是可以获取焦点,看下代码看看什么情况下可以获取焦点:

        private boolean canTakeFocus() {
            return ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
                    && ((mViewFlags & FOCUSABLE) == FOCUSABLE)
                    && ((mViewFlags & ENABLED_MASK) == ENABLED)
                    && (sCanFocusZeroSized || !isLayoutValid() || hasSize());
        }
    

      前面三个标识,都好理解,可见,可获取焦点,可使用状态。最后一个条件是一个||组成的逻辑表达式。静态变量sCanFocusZeroSized代表的是0大小的控件能否可以获取焦点,默认为false,根据项目的targetSdkVersion设置来确定该值。如果targetSdkVersion小于28,则该值为true。isLayoutValid()代码如下:

        /**
         * Returns true if this view has been through at least one layout since it
         * was last attached to or detached from a window.
         */
        public boolean isLaidOut() {
            return (mPrivateFlags3 & PFLAG3_IS_LAID_OUT) == PFLAG3_IS_LAID_OUT;
        }
    
        /**
         * @return {@code true} if laid-out and not about to do another layout.
         */
        boolean isLayoutValid() {
            return isLaidOut() && ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == 0);
        }
    

      isLaidOut()方法是检查PFLAG3_IS_LAID_OUT标识,代表控件上次绑定窗口之后至少执行过一次布局计算(layout()方法)。所以isLayoutValid()就是控件上次绑定窗口之后至少执行过一次布局计算并且不需要另一次布局。PFLAG_FORCE_LAYOUT代表需要执行另一次布局操作(measure()、layout())。所以isLayoutValid()方法的求反,就是控件还未设置PFLAG3_IS_LAID_OUT标识或者即将进行一次布局操作计算大小。还未设置PFLAG3_IS_LAID_OUT标识,代表控件还未绑定窗口或者绑定窗口还没有做第一次布局操作。
      第三个条件是hasSize(),它的意思就是控件的大小面积大于0。
      canTakeFocus()的最后一个条件意思就是控件的大小是大于0,或者绑定窗口还没有做第一次布局操作或即将去执行一次布局操作,或者设置目标targetSdkVersion小于28。
      requestFocusNoSearch在canTakeFocus()方法返回false的时候,就是不能获取到焦点的情况,也会返回false结束函数。代码继续向下,判断当前是触摸模式,并且在触摸模式下是否可以获取焦点。如果这个条件不满足,也会返回false结束函数。触摸模式是通过isInTouchMode()方法来判断的,既然有触摸模式,那有没有非触摸模式?下面解释一下触摸模式

    触摸模式

      当用户通过一个方向键(例如D-pad),操纵用户的意图,可执行的控件获取焦点是必要的,这样用户就能看到要操作的控件是哪个。如果设备有触摸能力,当用户通过触摸它交互,这个时候就没有必要高亮显示控件或者让控件获取焦点了,这激活一个交互模式,就是触摸模式。
      用户一旦触摸了显示屏幕,设备就会进入触摸模式。从这时起,isFocusableInTouchMode()返回true的控件,在触摸的时候才能获取焦点。这点在前面ACTION_UP类型事件处理的时候讲过了。满足isFocusableInTouchMode()返回true的控件如可编辑文本的控件。但是像Button是不满足的,所以Button直接表现点击事件。
      用户如果又点击了方向键(例如D-pad),设备即将退出触摸模式,用户又能找到一个获取焦点的控件操作。
      接着调用hasAncestorThatBlocksDescendantFocus()方法判断是否存在祖先控件会阻止其获取焦点,如果存在,也会返回false。看一下该方法:

        private boolean hasAncestorThatBlocksDescendantFocus() {
            final boolean focusableInTouchMode = isFocusableInTouchMode();
            ViewParent ancestor = mParent;
            while (ancestor instanceof ViewGroup) {
                final ViewGroup vgAncestor = (ViewGroup) ancestor;
                if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS
                        || (!focusableInTouchMode && vgAncestor.shouldBlockFocusForTouchscreen())) {
                    return true;
                } else {
                    ancestor = vgAncestor.getParent();
                }
            }
            return false;
        }
    

      如果父控件的getDescendantFocusability()方法得到的标识为ViewGroup.FOCUS_BLOCK_DESCENDANTS,或者父控件在触摸模式下不能获取焦点并且父控件的shouldBlockFocusForTouchscreen()方法的值为true,就会返回true。并且该方法会通过父控件向上传递,只要有一个祖先控件满足上面的条件,就会返回true,也就是阻止当前控件获取焦点。
      下面看一下ViewGroup类的shouldBlockFocusForTouchscreen()方法:

        @ViewDebug.ExportedProperty(category = "focus")
        @InspectableProperty
        public boolean getTouchscreenBlocksFocus() {
            return (mGroupFlags & FLAG_TOUCHSCREEN_BLOCKS_FOCUS) != 0;
        }
        boolean shouldBlockFocusForTouchscreen() {
            // There is a special case for keyboard-navigation clusters. We allow cluster navigation
            // to jump into blockFocusForTouchscreen ViewGroups which are clusters. Once in the
            // cluster, focus is free to move around within it.
            return getTouchscreenBlocksFocus() &&
                    mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
                    && !(isKeyboardNavigationCluster()
                            && (hasFocus() || (findKeyboardNavigationCluster() != this)));
        }
    

      getTouchscreenBlocksFocus()方法就是检测FLAG_TOUCHSCREEN_BLOCKS_FOCUS标识存在,该标识代表容器控件应该忽略它本身及子控件的焦点请求。mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)代表设备有一个触摸屏。第三个条件不好理解,用求反的符号,就是要排除掉这种情况,isKeyboardNavigationCluster()
    && (hasFocus() || (findKeyboardNavigationCluster() != this))。

    键盘导航键区

      这个先说下键盘导航键区的概念,应用的某个操作组件使用一种复杂的视图层次结构,可考虑将多组界面元素组成一个键区,简化键盘导航这些元素的操作。用户可以在 Chromebook 设备上按 Meta+Tab 或 Search+Tab,在不同键区之间导航。键区的一些范例包括:侧面板、导航栏、主内容区域和可能包含多个子元素的元素。要将一个 View 或 ViewGroup 元素设置为一个键区,请在元素的布局 XML 文件中将 android:keyboardNavigationCluster 属性设置为 true,或者将 true 传递至应用界面逻辑中的 setKeyboardNavigationCluster()。注意:键区不能嵌套,不过,非嵌套键区可以显示在层次结构的不同层级。如果您尝试嵌套键区,框架仅会将最顶层的 ViewGroup 元素视为键区。
      接着看上面的第三个条件,isKeyboardNavigationCluster()

        public final boolean isKeyboardNavigationCluster() {
            return (mPrivateFlags3 & PFLAG3_CLUSTER) != 0;
        }
    

    这个方法就是判断PFLAG3_CLUSTER标识,如果存在该标识,这个控件就是一个键盘导航键区的根源。接着看hasFocus()

        @Override
        public boolean hasFocus() {
            return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
        }
    

    这个是ViewGroup类型的hasFocus(),返回true,就是代表控件有焦点或者包含焦点。mFocused是它的一个子控件,获得焦点或者它是获得焦点的子控件的祖先。通过mFocused 可以层层传递到获得焦点的子控件。再看findKeyboardNavigationCluster() != this,

        View findKeyboardNavigationCluster() {
            if (mParent instanceof View) {
                View cluster = ((View) mParent).findKeyboardNavigationCluster();
                if (cluster != null) {
                    return cluster;
                } else if (isKeyboardNavigationCluster()) {
                    return this;
                }
            }
            return null;
        }
    

    该方法是通过递归父控件一直到ViewRootImpl类实例,ViewRootImpl类实例开始,从上到下寻找首先设置PFLAG3_CLUSTER标识的父控件返回。
      分析完上面的方法就能解释下isKeyboardNavigationCluster()
    && (hasFocus() || (findKeyboardNavigationCluster() != this))这个条件,控件是键盘导航键区的根并且包含焦点,或者当前控件是键盘导航键区的根并且嵌套在另外的键盘导航键区内,这两种情况即使设置了FLAG_TOUCHSCREEN_BLOCKS_FOCUS标识,也不阻止获取焦点。

      接着返回到requestFocusNoSearch()方法中,继续向下看代码,!isLayoutValid()这个条件前面说过,控件还未设置PFLAG3_IS_LAID_OUT标识或者或者即将进行一次布局操作计算大小,这个时候设置PFLAG_WANTS_FOCUS标识。如果isLayoutValid()为true,就是控件上次绑定窗口之后至少执行过一次布局计算并且不需要另一次布局,这个情况下调用clearParentsWantFocus(),用来清除当前控件的父控件的PFLAG_WANTS_FOCUS。设置PFLAG_WANTS_FOCUS标识,是为了在布局阶段如果控件没有获取焦点,会请求获取控件焦点。
      再向下,调用handleFocusGainInternal(direction, previouslyFocusedRect)去处理请求获取焦点。最后就返回true。

    handleFocusGainInternal(direction, previouslyFocusedRect)

      看下handleFocusGainInternal(direction, previouslyFocusedRect)方法代码:

        void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
            if (DBG) {
                System.out.println(this + " requestFocus()");
            }
    
            if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
                mPrivateFlags |= PFLAG_FOCUSED;
    
                View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
    
                if (mParent != null) {
                    mParent.requestChildFocus(this, this);
                    updateFocusedInCluster(oldFocus, direction);
                }
    
                if (mAttachInfo != null) {
                    mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
                }
    
                onFocusChanged(true, direction, previouslyFocusedRect);
                refreshDrawableState();
            }
        }
    

      handleFocusGainInternal的操作的条件是控件没有PFLAG_FOCUSED标识,主要做了如下:
      1、给当前控件设置PFLAG_FOCUSED,这个标识就代表控件获取了焦点。
      2、调用getRootView().findFocus()获取原来的获取焦点的控件。
      3、如果当前父控件不为null,调用父控件的requestChildFocus(this, this)方法。该步骤主要是取消原来获取焦点控件的PFLAG_FOCUSED标识,并且更新控件焦点链上的mFocused。
      4、如果当前父控件不为null,更新键盘导航键区的信息。
      5、如果控件已经绑定到窗口,则派发全局焦点改变事件。
      6、调用onFocusChanged(true, direction, previouslyFocusedRect),这个是当前控件获取焦点执行焦点改变事件。具体见下面
      7、最后调用refreshDrawableState(),该方法会通过控件的状态来设置各个Drawable的状态。该方法在Android触摸事件派发(二) View的onTouchEvent()中有讲到
    看getRootView()方法:

        public View getRootView() {
            if (mAttachInfo != null) {
                final View v = mAttachInfo.mRootView;
                if (v != null) {
                    return v;
                }
            }
    
            View parent = this;
    
            while (parent.mParent != null && parent.mParent instanceof View) {
                parent = (View) parent.mParent;
            }
    
            return parent;
        }
    

    如果mAttachInfo不为null,则代表当前控件已经绑定了窗口。通过mAttachInfo.mRootView得到当前窗口最顶级控件,这个控件就是DecorView类型的控件,如果该值不为null,则返回该值。如果当前控件没有绑定窗口,则通过mParent属性进行往上寻找,将最终找到的控件返回。在这儿,返回的就是DecorView类型的控件。也就是调用DecorView类的findFocus()方法。这个方法在ViewGroup类里,如下:

        @Override
        public View findFocus() {
            if (DBG) {
                System.out.println("Find focus in " + this + ": flags="
                        + isFocused() + ", child=" + mFocused);
            }
    
            if (isFocused()) {
                return this;
            }
    
            if (mFocused != null) {
                return mFocused.findFocus();
            }
            return null;
        }
    

    isFocused()方法,就是检查当前控件是否具有焦点。

        @InspectableProperty(hasAttributeId = false)
        public boolean isFocused() {
            return (mPrivateFlags & PFLAG_FOCUSED) != 0;
        }
    

    isFocused()方法返回true,代表找到了焦点控件。就返回当前控件。如果当前控件不是获取焦点的控件,就通过成员变量mFocused的findFocus()方法来找。容器如果具有焦点,mFocused有可能就是获取焦点的子控件,也可能是包含焦点控件的子控件。所以这里也会递归调用findFocus()方法来找到获取焦点的控件。
      找到原来的获取焦点的控件,返回handleFocusGainInternal(direction, previouslyFocusedRect)方法,继续执行,如果父控件不为null,则调用mParent.requestChildFocus(this, this)方法。

        @Override
        public void requestChildFocus(View child, View focused) {
            if (DBG) {
                System.out.println(this + " requestChildFocus()");
            }
            if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
                return;
            }
    
            // Unfocus us, if necessary
            super.unFocus(focused);
    
            // We had a previous notion of who had focus. Clear it.
            if (mFocused != child) {
                if (mFocused != null) {
                    mFocused.unFocus(focused);
                }
    
                mFocused = child;
            }
            if (mParent != null) {
                mParent.requestChildFocus(this, focused);
            }
        }
    

    requestChildFocus()方法也是一个通过mParent向它的父控件传递requestChildFocus()方法,其中参数focused是获取焦点的控件,而child则可以是获取焦点的控件或者包含焦点的控件。首先检查控件本身的FOCUS_BLOCK_DESCENDANTS标识是否存在,如果存在,就返回,不继续执行了,这个标识就代表阻止子孙控件获取焦点的意思。接着调用父类View的unFocus(focused)方法,如果mFocused和参数child不一致,则需要调用mFocused的unFocus(focused),同时将mFocused = child。最后就通过mParent,调用父控件的requestChildFocus(this, focused)方法。
      从handleFocusGainInternal()方法可以知道,请求焦点的控件的PFLAG_FOCUSED已经设置了。那么原来已经设置了PFLAG_FOCUSED标识的控件应该取消掉该标识,并且祖先控件的mFocused如果发生变化,应该重新设置。所以handleFocusGainInternal()方法里面调用mParent.requestChildFocus(this, this)就是为了做这些的,并且通过mParent进行传递,一直传递到所有的祖先控件。
      从requestChildFocus()方法里面看到,如果mFocused 发生变化,会重新设置mFocused 的值。那么取消之前的获取焦点的控件的PFLAG_FOCUSED标识的工作,应该由unFocus()来完成了,所以看下unFocus()方法,先看下View类的unFocus(focused),再接着看下ViewGroup类的unFocus(focused)方法。

    View类的unFocus(focused)

    View类的unFocus(focused):

        void unFocus(View focused) {
            if (DBG) {
                System.out.println(this + " unFocus()");
            }
    
            clearFocusInternal(focused, false, false);
        }
        ………………
        void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
            if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
                mPrivateFlags &= ~PFLAG_FOCUSED;
                clearParentsWantFocus();
    
                if (propagate && mParent != null) {
                    mParent.clearChildFocus(this);
                }
    
                onFocusChanged(false, 0, null);
                refreshDrawableState();
    
                if (propagate && (!refocus || !rootViewRequestFocus())) {
                    notifyGlobalFocusCleared(this);
                }
            }
        }    
    

    unFocus(focused)方法调用clearFocusInternal(focused, false, false)方法,该方法是为了让原来占用焦点的控件取消焦点。对已经获得焦点的控件执行如下:
      1、取消PFLAG_FOCUSED标识。
      2、调用clearParentsWantFocus(),清除祖先控件的PFLAG_WANTS_FOCUS标识。
      3、当前的参数propagate为false,这个参数代表是否向上祖先控件传递,假如该参数为true,并且父控件不为null,则调用父控件的clearChildFocus(this)方法。这个方法是将父控件的mFocused置为null,并且继续去执行祖先控件的clearChildFocus(this)方法。
      4、调用onFocusChanged(false, 0, null)方法,通知焦点改变了。
      5、调用refreshDrawableState()改变Drawable的显示。
      6、在参数propagate 为true,refocus 为false的情况下,会调用notifyGlobalFocusCleared(this)方法发送全局焦点改变通知。如果propagate 为true,refocus 为true,rootViewRequestFocus()的方法返回false,也会发送全局焦点改变通知。rootViewRequestFocus()方法是根控件DecorView去请求焦点,返回false也就是请求焦点失败。
      从传递过来的参数propagate值为false,refocus值为false,可知第3、6步不会执行。
      看下第4步骤的onFocusChanged(false, 0, null)方法:

       @CallSuper
        protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
                @Nullable Rect previouslyFocusedRect) {
            if (gainFocus) {
                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
            } else {
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
            }
    
            // Here we check whether we still need the default focus highlight, and switch it on/off.
            switchDefaultFocusHighlight();
    
            if (!gainFocus) {
                if (isPressed()) {
                    setPressed(false);
                }
                if (hasWindowFocus()) {
                    notifyFocusChangeToImeFocusController(false /* hasFocus */);
                }
                onFocusLost();
            } else if (hasWindowFocus()) {
                notifyFocusChangeToImeFocusController(true /* hasFocus */);
            }
    
            invalidate(true);
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnFocusChangeListener != null) {
                li.mOnFocusChangeListener.onFocusChange(this, gainFocus);
            }
    
            if (mAttachInfo != null) {
                mAttachInfo.mKeyDispatchState.reset(this);
            }
    
            if (mParent != null) {
                mParent.onDescendantUnbufferedRequested();
            }
    
            notifyEnterOrExitForAutoFillIfNeeded(gainFocus);
        }
    

      该方法组主要做如下:
      ① 根据是获取焦点还是失去焦点,处理辅助功能相关事件
      ② 打开或者关闭默认焦点高亮,对应调用switchDefaultFocusHighlight()
      ③ 如果失去焦点,如果是按压状态,取消按压状态,如果目前窗口获取窗口焦点,向输入法焦点控制器通知该控件丢失焦点。如果控件失去焦点还会调用onFocusLost(),取消控件的按压状态,并且如果现在存在长按事件消息,会取消。
      ④ 如果获得焦点,并且目前窗口获取窗口焦点,向输入法焦点控制器通知该控件获得焦点。
      ⑤ 刷新界面
      ⑥ 如果控件当前注册了焦点改变接口,现在会执行该接口回调函数onFocusChange(this, gainFocus)
      ⑦ 停止追踪和这个控件相关。这个追踪是用来KeyEvent事件的长按事件的,如果实现长按事件,在KeyEvent的ACTION_DOWN事件,记录按键的code和目标。现在如果控件的焦点改变的时候,并且记录的目标是这个控件的话,就将记录code和目标清除掉。
      ⑧调用父类控件,检查是否设置不缓存派发。这个不缓存派发,只在触摸事件中使用,如果设置了,输入系统则不会批次派发触摸事件,有了就会派发。
      ⑨ 通知自动填充服务该控件获取或失去焦点。
      clearFocusInternal(focused, false, false)方法调用的onFocusChanged()方法,参数gainFocus为false,所以是执行的失去焦点。
      先看看②步骤中的switchDefaultFocusHighlight()方法:

        private void switchDefaultFocusHighlight() {
            if (isFocused()) {
                final boolean needed = isDefaultFocusHighlightNeeded(mBackground,
                        mForegroundInfo == null ? null : mForegroundInfo.mDrawable);
                final boolean active = mDefaultFocusHighlight != null;
                if (needed && !active) {
                    setDefaultFocusHighlight(getDefaultFocusHighlightDrawable());
                } else if (!needed && active) {
                    // The highlight is no longer needed, so tear it down.
                    setDefaultFocusHighlight(null);
                }
            }
        }
    

      该方法的首要条件是在控件目前是获取焦点的状态,检查是否需要默认高亮,并且默认高亮的Drawable不能为null,就是是否是active,如果需要默认高亮,但是还没有设置默认高亮的Drawable,那么就调用setDefaultFocusHighlight(getDefaultFocusHighlightDrawable())进行设置默认高亮的Drawable,这就相当于打开了默认焦点高亮,后面就可以显示了;如果不需要默认高亮,不过现在存在默认高亮的Drawable,现在调用setDefaultFocusHighlight(null)将其设置为null,这就相当于将默认高亮关闭。接着看isDefaultFocusHighlightNeeded()方法,看下什么情况下,需要默认高亮,

        @TestApi
        public boolean isDefaultFocusHighlightNeeded(Drawable background, Drawable foreground) {
            final boolean lackFocusState = (background == null || !background.isStateful()
                    || !background.hasFocusStateSpecified())
                    && (foreground == null || !foreground.isStateful()
                    || !foreground.hasFocusStateSpecified());
            return !isInTouchMode() && getDefaultFocusHighlightEnabled() && lackFocusState
                    && isAttachedToWindow() && sUseDefaultFocusHighlight;
        }
    

      参数background是背景Drawable,foreground是前景Drawable。Drawable类的isStateful()方法表示它的外观会随着它的状态改变,取反就是不会随着状态改变。background.hasFocusStateSpecified()表示background设置了属性state_focused,取反就是没有设置该属性。所以lackFocusState为true,表示背景Drawable不会随着状态改变或者背景Drawable没有设置state_focused属性,并且前景Drawable不会随着状态改变或者前景Drawable没有设置state_focused属性。
      getDefaultFocusHighlightEnabled()代表控件默认焦点高亮启用,它可以通过setDefaultFocusHighlightEnabled(boolean defaultFocusHighlightEnabled)方法或者xml布局文件中控件的defaultFocusHighlightEnabled属性进行设置。
      !isInTouchMode()代表不是在触摸模式下,前面解释过了。sUseDefaultFocusHighlight是系统的资源配置文件里的config_useDefaultFocusHighlight属性值。isAttachedToWindow()代表控件是绑定到窗口上了。
      上面所有方法的并集就是需要默认焦点高亮的。
      接着看看setDefaultFocusHighlight(Drawable highlight)方法:

        private void setDefaultFocusHighlight(Drawable highlight) {
            mDefaultFocusHighlight = highlight;
            mDefaultFocusHighlightSizeChanged = true;
            if (highlight != null) {
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                }
                highlight.setLayoutDirection(getLayoutDirection());
                if (highlight.isStateful()) {
                    highlight.setState(getDrawableState());
                }
                if (isAttachedToWindow()) {
                    highlight.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
                }
                // Set callback last, since the view may still be initializing.
                highlight.setCallback(this);
            } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null
                    && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
                mPrivateFlags |= PFLAG_SKIP_DRAW;
            }
            invalidate();
        }
    

    该方法先将mDefaultFocusHighlight设置为参数highlight,将mDefaultFocusHighlightSizeChanged设为true,代表默认焦点高亮Drawable的面积发生了变化。
      如果参数highlight 不为null,即为设置了新的Drawable。如果之前设置了PFLAG_SKIP_DRAW标识,现在需要将它去除,因为这个标识是为了跳过该控件的绘制。下面再解释这个标识,先往下看,根据控件的布局方法来设置highlight的布局方向,highlight如果也是根据状态变化,这个时候也设置它的状态。如果控件已经绑定到窗口,根据窗口和控件和祖先控件的可见性,来设置highlight的可见性状态。再设置highlight的Callback接口。
      如果参数highlight 为null,并且背景mBackground为null,mForegroundInfo.mDrawable为null,及默认高亮、背景、前景都不需要绘制,并且这个时候也设置了WILL_NOT_DRAW标识,这个时候,就设置PFLAG_SKIP_DRAW标识。WILL_NOT_DRAW标识基本上来设置ViewGroup类型的控件,因为它没有内容,并且这个时候,也不用绘制默认高亮、背景、前景,所以绘制的时候就可以跳过该控件的绘制,直接绘制子控件。PFLAG_SKIP_DRAW标识就是为了这个目的。
      最后这两种情况都会调用invalidate(),刷新界面。
      该方法的isShown()是该控件和祖先控件中只要有一个不可见,就会返回false。

    ViewGroup类的unFocus(focused)

        @Override
        void unFocus(View focused) {
            if (DBG) {
                System.out.println(this + " unFocus()");
            }
            if (mFocused == null) {
                super.unFocus(focused);
            } else {
                mFocused.unFocus(focused);
                mFocused = null;
            }
        }
    

      在容器控件的mFocused 为null的时候,会调用对应的View类的unFocus(View focused)方法,上面已经讲述;如果不为null,则会调用mFocused.unFocus(focused),这又会根据mFocused是ViewGroup类型还是View类型,继续执行,最后会将mFocused 设置为null。

      handleFocusGainInternal()方法执行完了mParent.requestChildFocus(this, this),需要执行updateFocusedInCluster(oldFocus, direction)方法,如下:

        private void updateFocusedInCluster(View oldFocus, @FocusDirection int direction) {
            if (oldFocus != null) {
                View oldCluster = oldFocus.findKeyboardNavigationCluster();
                View cluster = findKeyboardNavigationCluster();
                if (oldCluster != cluster) {
                    // Going from one cluster to another, so save last-focused.
                    // This covers cluster jumps because they are always FOCUS_DOWN
                    oldFocus.setFocusedInCluster(oldCluster);
                    if (!(oldFocus.mParent instanceof ViewGroup)) {
                        return;
                    }
                    if (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) {
                        // This is a result of ordered navigation so consider navigation through
                        // the previous cluster "complete" and clear its last-focused memory.
                        ((ViewGroup) oldFocus.mParent).clearFocusedInCluster(oldFocus);
                    } else if (oldFocus instanceof ViewGroup
                            && ((ViewGroup) oldFocus).getDescendantFocusability()
                                    == ViewGroup.FOCUS_AFTER_DESCENDANTS
                            && ViewRootImpl.isViewDescendantOf(this, oldFocus)) {
                        // This means oldFocus is not focusable since it obviously has a focusable
                        // child (this). Don't restore focus to it in the future.
                        ((ViewGroup) oldFocus.mParent).clearFocusedInCluster(oldFocus);
                    }
                }
            }
        }
    

    这个方法在原来获取焦点和现在获取焦点的控件的键盘导航键区如果不同,之前键区会记录它的最后的获取焦点的控件,调用oldFocus.setFocusedInCluster(oldCluster)实现。接着在direction等于FOCUS_FORWARD或者FOCUS_BACKWARD,会执行清除旧键区记录的焦点控件。还有一种情况,也会执行清除旧键区记录的焦点控件,就是旧焦点控件是容器类型,并且该容器与子孙控件获取焦点的关系是ViewGroup.FOCUS_AFTER_DESCENDANTS(这个就是先让子孙控件获取焦点),并且切换之前的容器控件还是目前获取焦点控件的祖先控件的情况下。但是这种情况下,他们两个的键区怎么会不同呢,没想明白。
      继续看下View类的setFocusedInCluster(View cluster)和ViewGroup类的clearFocusedInCluster(View child),View类的setFocusedInCluster(View cluster)代码如下:

        private void setFocusedInCluster(View cluster) {
            if (this instanceof ViewGroup) {
                ((ViewGroup) this).mFocusedInCluster = null;
            }
            if (cluster == this) {
                return;
            }
            ViewParent parent = mParent;
            View child = this;
            while (parent instanceof ViewGroup) {
                ((ViewGroup) parent).mFocusedInCluster = child;
                if (parent == cluster) {
                    break;
                }
                child = (View) parent;
                parent = parent.getParent();
            }
        }
    

    如果当前请求控件是容器类型,则将它的mFocusedInCluster置为null,就是清除了。如果参数cluster和当前请求控件是相同对象,则退出,不向下执行。接着就通过while循环,向上寻找父控件,一直找到参数cluster为止。对于找到的父控件,设置它的成员变量mFocusedInCluster为child,这样就通过键区的根容器控件的mFocusedInCluster维持了一个链条,一直到当前控件。
      接着看ViewGroup类的clearFocusedInCluster(View child),如下:

        void clearFocusedInCluster(View child) {
            if (mFocusedInCluster != child) {
                return;
            }
            clearFocusedInCluster();
        }
    
        /**
         * Removes the focusedInCluster chain from this up to the cluster containing it.
         */
        void clearFocusedInCluster() {
            View top = findKeyboardNavigationCluster();
            ViewParent parent = this;
            do {
                ((ViewGroup) parent).mFocusedInCluster = null;
                if (parent == top) {
                    break;
                }
                parent = parent.getParent();
            } while (parent instanceof ViewGroup);
        }
    

    可以看到这个的操作是和上面的setFocusedInCluster(View cluster)的操作是相反的,就是取消键区的根容器控件的mFocusedInCluster维持了一个链条,将成员变量mFocusedInCluster都设置为null。
      以上就是控件获取焦点的主要步骤和方法,里面的细节还是挺多的,需要仔细理解、加深记忆。

    展开全文
  • 两个文本框 一个事用户名 一个是密码 里面有初始内容 得到焦点就把内容清空 失去焦点就判断输入的内容是否大于6 如果大于则作出相应的提示
  • C#控件怎样获取,和失去焦点的处理

    千次阅读 2013-07-16 08:51:00
    publicForm1() { InitializeComponent(); textBox1.Enter+=newEventHandler...//获得焦点事件 textBox1.Leave+=newEventHandler(textBox1_Leave);//失去焦点事件。 } void textBox1_Enter(object sen...
  • 今天这里,我想与大家一起分享如何处理的ASP.net TextBox控件失去焦点后触发其它事件的问题,此做个小结,以供参考。有兴趣的同学,可以一同探讨与学习一下,否则就略过吧。   1.首先弄清楚问题,asp.net...
  • -- TextView的点击事件如何去除- ...TextView.setEnabled(true); TextView.setEnabled(true); TextView.setClickable(true); TextView.setClickable(true); -- Android View获取焦点...
  • 具体需求是这样的,XtraTabControl界面,一个Page有GridControl,另外一个Page是其他编辑控件,当GridView选中了内容后,切换Page,点其他的控件,这个时候GridControl就会失去焦点,之前的选中的文本也会消失,所以...
  • 方法一: 1、设置TreeView的DrawMode为.OwnerDrawText 2、绑定DrawNode事件为上面的事件函数 private void init() { this.treeView1.HideSelection = false;//失去焦点后不隐藏选中节点 //自已绘制 ...
  • 需求文档: 字典类型表单验证。(以下描述你们将“详细设计文档”中看到。...获得焦点之后,自动清除错误信息,并且文本框中的内容不合法的时候,文本框的内容自动清空。 用户最终保存按钮点击之后,必须保证所有...
  • 项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,adapter中按照需求进行编写,问题就出现了,可能会发生点击每一个item的时候没有反应,无法获取的焦点。...
  • ListView子控件获取焦点问题

    千次阅读 2014-07-26 23:18:19
    项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,adapter中按照需求进行编写,问题就出现了,可能会发生点击每一个item的时候没有反应,无法获取的焦点。...
  • 项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,adapter中按照需求进行编写,问题就出现了,可能会发生点击每一个item的时候没有反应,无法获取的焦点。...
  • 1、Windows自带控件,Button、Label等控件的派生 构造函数中的 ControlStyles.Selectable属性设置为false (不可以被选中)。 System.Reflection.BindingFlags bindingAttr = System.Reflection.BindingFlags....
  • 控件焦点问题

    2016-06-24 15:47:41
    项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,adapter中按照需求进行编写,问题就出现了,可能会发生点击每一个item的时候没有反应,无法获取的焦点。...
  • 网上找的一个例子,稍做了点修改并加上了失去焦点的事件 Ext.onReady(function(){ Ext.QuickTips.init(); Ext.form.Field.prototype.msgTarget = side;//提示显示方式 E
  • 5.处理自定义控件焦点

    千次阅读 2014-03-31 01:26:48
    其实要说啥是焦点 从口头上来说 我也不知道应该怎么描述 如果非要我描述一下的话 我也只能说:获得焦点控件会接受用户的键盘事件 比如说 窗体上有两个文本框 我上面一个文本框打字的时候 不会打到下面的一个...
  • input标签内容改变或失去焦点的触发事件
  • 2、树控件失去焦点时,被选中的节点,仍然高亮显示。 先看效果: 图1 失去焦点时仍然高亮显示(颜色可以自己设置) 图2 只会选中的节点右下方弹出菜单 实现方法详解(有时间再写): 实现的代码: ...
  • 控件与子控件焦点问题

    千次阅读 2012-10-26 18:38:44
    项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,adapter中按照需求进行编写,问题就出现了,可能会发生点击每一个item的时候没有反应,无法获取的焦点。...
  • 最近项目中需要用到RecyclerView嵌套RecyclerView实现复杂界面的需求,测试中... 通过查阅资料发现,当RecyclerView嵌套RecyclerView的时候,子RecyclerView会优先父RecyclerView而获得焦点,也即是说,内部R...
  • 我们按tab键那些控件就会依次获得焦点,获得了焦点的控件就会呈现出一种特殊的外观,默认为我们看到的虚线,也可以修改成其他的外观,背景色,前景色,边框等属性都可以在获得焦点发生改变,失去焦点后这些变化就...
  • 元素被鼠标点击后,会自动获得焦点,此时...因此,如果要避免鼠标单击获得焦点的情况,要click事件回调里设置失去焦点行为,这样就不会出现按下enter触发click的情况了。表单元素很可能都有这类特性,鼠标单击获...
  • 项目中的lListview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,adapter中按照需求进行编写,问题就出现了,可能会发生点击每一个item的时候没有反应,无法获取的焦点。...
  • blur事件: 当元素失去焦点时发生 blur 事件。 focus事件:focus() 方法用于赋予文本域焦点(也值让某些元素得到焦点事件)。 tabIndex属性:tabIndex 属性可设置或返回按钮的 tab 键控制次序。 我们都知道blur只是针对...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,940
精华内容 8,776
关键字:

发生在控件失去焦点时的