View设置seletor失效问题

一、原因:

我这不起作用的原因是由于对View设置了onTouchListener,且在ontouch方法中返回了true(具体原因后面会讲,此处我用的ImageView,在属性中设置src不起作用了)。

注:没有设置OnTouchListener的原因可能是要设置xml中的属性clickble为true。

二、解决方法:

  1. 在onTouch方法中的MotionEvent.ACTION_DOWN的逻辑块中调用view.setPressed(true);
  2. 在onTouch方法中的MotionEvent.ACTION_UP及ACTION_CANCEL逻辑块中调用view.setPressed(false)即可;

注:MotinoEvent中的这些事件可以在View中具体看它们的解释在这就不多说了。

三、原因:

首先初始化:
一、在xml中设置了background/foreground后在构造方法中会初始化selector及shape等,
1.首先看下View设置selector或者shape之后是怎么初始化的:
可以看到View中有三个构造方法(主要看下一个参数跟四个参数的构造方法,其他构造方法最终会调用四个构造方法的):

  • public View(Context context) //可以看注释为简单调用的构造方法,一般在动态初始化时调用,其他属性也动态设置,如padding,margin等
  • public View(Context context, @Nullable AttributeSet attrs)
  • public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) //一般从xml中初始化时调用的构造方法(当然也可以java代码中动态调用,传context,其他参数为空或者为0即可),里边初始化了padding,margin,background等一些属性,在onDraw绘制时会使用;

2.在几个事件中的状态处理:
在View的源码中可以找到这个方法,根据注释及其中的逻辑可以看到是根据View当前的状态绘制相应的效果的逻辑。根据其中的逻辑可以发现setPress调用了refreshDrawableState,refreshDrawableState调用了drawableStateChanged,在drawableStateChanged的最后状态如果改变了则重绘View:

/**
     * Sets the pressed state for this view and provides a touch coordinate for
     * animation hinting.
     *
     * @param pressed Pass true to set the View's internal state to "pressed",
     *            or false to reverts the View's internal state from a
     *            previously set "pressed" state.
     * @param x The x coordinate of the touch that caused the press
     * @param y The y coordinate of the touch that caused the press
     */
    private void setPressed(boolean pressed, float x, float y) {
        if (pressed) {
            drawableHotspotChanged(x, y);
        }

        setPressed(pressed);
    }
    
    /**
     * Sets the pressed state for this view.
     *
     * @see #isClickable()
     * @see #setClickable(boolean)
     *
     * @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
     *        the View's internal state from a previously set "pressed" state.
     */
    public void setPressed(boolean pressed) {
        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

        if (pressed) {
            mPrivateFlags |= PFLAG_PRESSED;
        } else {
            mPrivateFlags &= ~PFLAG_PRESSED;
        }

        if (needsRefresh) {
            refreshDrawableState();
        }
        dispatchSetPressed(pressed);
    }

/**
     * Call this to force a view to update its drawable state. This will cause
     * drawableStateChanged to be called on this view. Views that are interested
     * in the new state should call getDrawableState.
     *
     * @see #drawableStateChanged
     * @see #getDrawableState
     */
    public void refreshDrawableState() {
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        drawableStateChanged();

        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);
        }
    }

/**
     * This function is called whenever the state of the view changes in such
     * a way that it impacts the state of drawables being shown.
     * <p>
     * If the View has a StateListAnimator, it will also be called to run necessary state
     * change animations.
     * <p>
     * Be sure to call through to the superclass when overriding this function.
     *
     * @see Drawable#setState(int[])
     */
    @CallSuper
    protected void drawableStateChanged() {
        final int[] state = getDrawableState();
        boolean changed = false;

        final Drawable bg = mBackground;
        if (bg != null && bg.isStateful()) {
            changed |= bg.setState(state);
        }

        final Drawable hl = mDefaultFocusHighlight;
        if (hl != null && hl.isStateful()) {
            changed |= hl.setState(state);
        }

        final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (fg != null && fg.isStateful()) {
            changed |= fg.setState(state);
        }

        if (mScrollCache != null) {
            final Drawable scrollBar = mScrollCache.scrollBar;
            if (scrollBar != null && scrollBar.isStateful()) {
                changed |= scrollBar.setState(state)
                        && mScrollCache.state != ScrollabilityCache.OFF;
            }
        }

        if (mStateListAnimator != null) {
            mStateListAnimator.setState(state);
        }

        if (changed) {
            invalidate();
        }
    }

而调用setpressed的方法可以看到是在View的onTouchEvent中相应的地方调用了这个方法(在ACTION_DOWN时调用了setPressed(true),在ACTION_UP及ACTION_CANCEL时调用了setPressed(false)),而setPressed根据上面的描述可以知道是根据View的状态绘制背景或前景的,因此可以知道selector失效的原因是setPressed没有被调用即onTochEvent没有被调用

注:源码中可以看到只有clickable时才会走到里边的Action分发的逻辑块,因此如果只给ImageView,TextView设置了selector没有设置clickable为true则也不触发相应的状态改变。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
而调用onTouchEvent的方法是dispatchTouchEvent(事件分发的方法中调用的),可以看到如果onTouchListener不为空且可点击并且在onTouch方法中返回true的话则在11770行就直接返回true了并不会执行onTouchEvent方法,则不会调用setPressed修改View的一些状态:

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

3.绘制时的状态处理:
draw(Canvas canvas)方法中调用了drawBackground(canvas)与onDrawForeground(canvas)其中一个是绘制background的状态的一个是绘制foreground的状态的:

            /**
     * Draws the background onto the specified canvas.
     *
     * @param canvas Canvas on which to draw the background
     */
    private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }

        setBackgroundBounds();

        // Attempt to use a display list if requested.
        if (canvas.isHardwareAccelerated() && mAttachInfo != null
                && mAttachInfo.mThreadedRenderer != null) {
            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

            final RenderNode renderNode = mBackgroundRenderNode;
            if (renderNode != null && renderNode.isValid()) {
                setBackgroundRenderNodeProperties(renderNode);
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                return;
            }
        }

        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
/**
     * Draw any foreground content for this view.
     *
     * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground}
     * drawable or other view-specific decorations. The foreground is drawn on top of the
     * primary view content.</p>
     *
     * @param canvas canvas to draw into
     */
    public void onDrawForeground(Canvas canvas) {
        onDrawScrollIndicators(canvas);
        onDrawScrollBars(canvas);

        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (foreground != null) {
            if (mForegroundInfo.mBoundsChanged) {
                mForegroundInfo.mBoundsChanged = false;
                final Rect selfBounds = mForegroundInfo.mSelfBounds;
                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

                if (mForegroundInfo.mInsidePadding) {
                    selfBounds.set(0, 0, getWidth(), getHeight());
                } else {
                    selfBounds.set(getPaddingLeft(), getPaddingTop(),
                            getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
                }

                final int ld = getLayoutDirection();
                Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                        foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
                foreground.setBounds(overlayBounds);
            }

            foreground.draw(canvas);
        }
    }

然后在设备上就显示了相应的backGround及foreGround的状态。

四、TextView的textColor:

  1. TextView的textColor:
    首先可以在draw(Canvas canvas)方法中找到修改颜色的代码:
    在这里插入图片描述
    可以看到颜色变量是mCurTextColor,而修改mCurTextColor的方法可以找到是updateTextColors():
    在这里插入图片描述
    而可以找到在drawableStateChanged方法中调用了updateTextColors,根据上面的描述可以知道在onTouchEvent中状态如果变化了会回调drawableStateChanged这个方法的,因此如果设置了不同状态的textColor则会根据状态设置text的不同颜色的。
发布了46 篇原创文章 · 获赞 23 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/divaid/article/details/96365426