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的不同顏色的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章