记录使用StateListDrawable相关

本文深入探讨了StateListDrawable的工作原理,特别是如何与ImageView配合使用,以及在设置不同状态时遇到的具体问题。通过源码分析揭示了为何特定状态下某些操作无法生效。

1、配合ImageView使用

  1. 比如向StateListDrawable对象中添加选中状态,以及选中状态所显示的Drawable
		//定义常规和选中两种状态
        int[] normal = StateSet.WILD_CARD;
        int[] selected = new int[]{android.R.attr.state_selected};
        //设置两种状态分别显示的Drawable
        StateListDrawable stateListDrawable1 = new StateListDrawable();
        stateListDrawable1.addState(selected,getResources().getDrawable(R.drawable.ic_launcher_foreground));
        stateListDrawable1.addState(normal,getResources().getDrawable(R.drawable.ic_launcher_background));
  1. 给ImageView设置使用StateListDrawable
        binding.iv1.setImageDrawable(stateListDrawable1);
  1. 设置ImageView为选中状态
        binding.iv1.setSelected(true);

2、使用中遇到的问题

  1. 空状态(正常状态)必须在最后一个添加,不然设置选中会不生效
  2. StateListDrawable有个setState()方法,如果单独设置它的状态为选中状态,理论上就不用设置ImageView为选中状态了,但是实际效果却不生效

我主要好奇第二点为啥不生效,随后便开始了代码翻阅之旅,然后中途恰好翻到了第1点的原因

2.1 跟踪StateListDrawable的setState()源码

  1. 点击setState()方法进入到Drawable类中,判断当前是否是该状态,不是的话重新设置状态
	//Drawable.java
    public boolean setState(@NonNull final int[] stateSet) {
        if (!Arrays.equals(mStateSet, stateSet)) {
            mStateSet = stateSet;
            return onStateChange(stateSet);
        }
        return false;
    }

	protected boolean onStateChange(int[] state) {
        return false;
    }
  1. 查看子类的实现,因为StateListDrawable是DrawableContainer的子类,DrawableContainer直接继承的Drawable,直接进入DrawableContainer.java
    @Override
    //DrawableContainer.java
    protected boolean onStateChange(int[] state) {
    	//大概翻了下,和显隐动画相关的
        if (mLastDrawable != null) {
            return mLastDrawable.setState(state);
        }
        //设置当前显示的Drawable状态
        if (mCurrDrawable != null) {
            return mCurrDrawable.setState(state);
        }
        return false;
    }

发现StateListDrawable也有实现,点进去查看代码,思路就是先根据设置的状态找到对应要显示的Drawable,然后设置一下

    @Override
    //StateListDrawable.java
    protected boolean onStateChange(int[] stateSet) {
        final boolean changed = super.onStateChange(stateSet);
        //查找要设置状态的index
        int idx = mStateListState.indexOfStateSet(stateSet);
        if (DEBUG) {
            android.util.Log.i(TAG, "onStateChange " + this + " states "
                    + Arrays.toString(stateSet) + " found " + idx);
        }
        if (idx < 0) {
            idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
        }
        //设置当前的drawable
        return selectDrawable(idx) || changed;
    }
  1. 进入selectDrawable(int index)方法,核心代码就是绘制自身invalidateSelf()
	//DrawableContanier.java
    boolean selectDrawable(int index) {
        if (index == mCurIndex) {
            return false;
        }
        final long now = SystemClock.uptimeMillis();
        if (DEBUG) {
            android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + index
                    + ": exit=" + mDrawableContainerState.mExitFadeDuration
                    + " enter=" + mDrawableContainerState.mEnterFadeDuration);
        }
        if (mDrawableContainerState.mExitFadeDuration > 0) {
            if (mLastDrawable != null) {
                mLastDrawable.setVisible(false, false);
            }
            if (mCurrDrawable != null) {
                mLastDrawable = mCurrDrawable;
                mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
            } else {
                mLastDrawable = null;
                mExitAnimationEnd = 0;
            }
        } else if (mCurrDrawable != null) {
            mCurrDrawable.setVisible(false, false);
        }
        if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
            final Drawable d = mDrawableContainerState.getChild(index);
            mCurrDrawable = d;
            mCurIndex = index;
            if (d != null) {
                if (mDrawableContainerState.mEnterFadeDuration > 0) {
                    mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
                }
                initializeDrawableForDisplay(d);
            }
        } else {
            mCurrDrawable = null;
            mCurIndex = -1;
        }
        if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
            if (mAnimationRunnable == null) {
                mAnimationRunnable = new Runnable() {
                    @Override
                    public void run() {
                        animate(true);
                        invalidateSelf();
                    }
                };
            } else {
                unscheduleSelf(mAnimationRunnable);
            }
            // Compute first frame and schedule next animation.
            animate(true);
        }
        invalidateSelf();
        return true;
    }
  1. 进入invalidateSelf,发现是获取callBack,然后调用callBack的invalidateDrawable()方法,这个callBack在ImageView里面,是在IamgeView.setImageDrawable()方法中调用了updateDrawable()设置的
	// Drawable.java
    public void invalidateSelf() {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.invalidateDrawable(this);
        }
    }
	//ImageView.java
    public void setImageDrawable(@Nullable Drawable drawable) {
        if (mDrawable != drawable) {
            mResource = 0;
            mUri = null;

            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;

            updateDrawable(drawable);

            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            invalidate();
        }
    }

    private void updateDrawable(Drawable d) {
        if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
            mRecycleableBitmapDrawable.setBitmap(null);
        }

        boolean sameDrawable = false;

        if (mDrawable != null) {
            sameDrawable = mDrawable == d;
            mDrawable.setCallback(null);
            unscheduleDrawable(mDrawable);
            if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
                mDrawable.setVisible(false, false);
            }
        }

        mDrawable = d;

        if (d != null) {
        	//就是这句设置的callback
            d.setCallback(this);
            d.setLayoutDirection(getLayoutDirection());
            if (d.isStateful()) {
                d.setState(getDrawableState());
            }
            if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
                final boolean visible = sCompatDrawableVisibilityDispatch
                        ? getVisibility() == VISIBLE
                        : isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
                d.setVisible(visible, true);
            }
            d.setLevel(mLevel);
            mDrawableWidth = d.getIntrinsicWidth();
            mDrawableHeight = d.getIntrinsicHeight();
            applyImageTint();
            applyColorFilter();
            applyAlpha();
            applyXfermode();

            configureBounds();
        } else {
            mDrawableWidth = mDrawableHeight = -1;
        }
    }
  1. 查看ImageView的invalidateDrawable()方法,发现就是重绘自身,
    @Override
    //ImageView.java
    public void invalidateDrawable(@NonNull Drawable dr) {
        //走第一个,因为是相同的Drawable
        if (dr == mDrawable) {
            if (dr != null) {
                // update cached drawable dimensions if they've changed
                final int w = dr.getIntrinsicWidth();
                final int h = dr.getIntrinsicHeight();
                if (w != mDrawableWidth || h != mDrawableHeight) {
                    mDrawableWidth = w;
                    mDrawableHeight = h;
                    // updates the matrix, which is dependent on the bounds
                    configureBounds();
                }
            }
            /* we invalidate the whole view in this case because it's very
             * hard to know where the drawable actually is. This is made
             * complicated because of the offsets and transformations that
             * can be applied. In theory we could get the drawable's bounds
             * and run them through the transformation and offsets, but this
             * is probably not worth the effort.
             */
            invalidate();
        } else {
            super.invalidateDrawable(dr);
        }
    }

2.2 跟踪ImageView的setSelected()源码

  1. resizeFromDrawable()就是大小尺寸计算相关,无关,所以追踪父类的setSelected()方法
	//ImageView.java
    @Override
    public void setSelected(boolean selected) {
        super.setSelected(selected);
        resizeFromDrawable();
    }

    private void resizeFromDrawable() {
        final Drawable d = mDrawable;
        if (d != null) {
            int w = d.getIntrinsicWidth();
            if (w < 0) w = mDrawableWidth;
            int h = d.getIntrinsicHeight();
            if (h < 0) h = mDrawableHeight;
            if (w != mDrawableWidth || h != mDrawableHeight) {
                mDrawableWidth = w;
                mDrawableHeight = h;
                requestLayout();
            }
        }
    }
  1. 发现除了重绘本身invalidate(true),就是刷新Drawable的状态refreshDrawableState(),然后分发选中事件dispatchSetSelected(selected)(viewGroup才会分发,所以是个空实现,忽略)
	//View.java
    public void setSelected(boolean selected) {
        //noinspection DoubleNegation
        if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) {
            mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0);
            if (!selected) resetPressedState();
            invalidate(true);
            refreshDrawableState();
            dispatchSetSelected(selected);
            if (selected) {
                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
            } else {
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
            }
        }
    }
  1. 进入refreshDrawableState()方法,点进drawableStateChanged()的方法,都没啥用,不过drawableStateChanged()被ImageView重写了
	//View.java
    public void refreshDrawableState() {
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        drawableStateChanged();

        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);
        }
    }
	//ImageView有这个方法的重写
	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 (!isAggregatedVisible()) {
            // If we're not visible, skip any animated changes
            jumpDrawablesToCurrentState();
        }

        if (changed) {
            invalidate();
        }
    }
  1. 发现最终还是调用了Drawable的setState()方法,以及重新绘制drawable的invalidateDrawable()方法
	//ImageView.java
    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();

        final Drawable drawable = mDrawable;
        if (drawable != null && drawable.isStateful()
                && drawable.setState(getDrawableState())) {
            invalidateDrawable(drawable);
        }
    }

然后我就纳闷了,ImageView最终还是调用的Drawable的setState()方法,但是Drawable的setState()直接使用却不生效呢,随后我又重新翻阅了两边的逻辑,并没有找到所以然,当天已经凌晨1点,第二天要上班就暂时放弃了,第二天晚上回家又先走了一遍两边的流程,确定自己这边逻辑没有问题,然后便猜测是重绘的时候可能搞了点什么,然后我就在setState()方法里面进行断点调试。

随后对Drawable.setState()进行调试,发现这边一开始是设置的setState()状态,后来又被调用被改掉了,根据断点得到调用栈,发现是在View.dispatchAttachedToWindow()方法中会先将view的state标识为污染状态,让view里面的Drawable的状态和view的本身状态保持一致
在这里插入图片描述

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        //为了矫正Drawable的state,将标志标识为污染状态
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }

        registerPendingFrameMetricsObservers();

        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
            if (isShown()) {
                // Calling onVisibilityAggregated directly here since the subtree will also
                // receive dispatchAttachedToWindow and this same call
                onVisibilityAggregated(vis == VISIBLE);
            }
        }

        // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
        // As all views in the subtree will already receive dispatchAttachedToWindow
        // traversing the subtree again here is not desired.
        onVisibilityChanged(this, visibility);

		//重新设置Drawable的state
        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
            // If nobody has evaluated the drawable state yet, then do it now.
            refreshDrawableState();
        }
        needGlobalAttributesUpdate(false);

        notifyEnterOrExitForAutoFillIfNeeded(true);
        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
    }

2.3 问题1的源码

在StateListDrawable的onStateChange()方法中调用了StateListState的indexOfStateSet()方法,他会遍历StateListState当前所有的状态集合,然后调用了StateSet的stateSetMatches()方法做比较,在该方法里面是遍历StateLIstState的里面取到的一个状态集合,然后在设置的状态中查找有没有是否都有满足该集合的状态,有的话就返回这个index了,所以要把选中的状态先设置,其实这也是我们xml布局文件中selector要把normal状态设置到最后的原因。

	//StateListDrawable.java
    @Override
    protected boolean onStateChange(int[] stateSet) {
        final boolean changed = super.onStateChange(stateSet);
		//这一句
        int idx = mStateListState.indexOfStateSet(stateSet);
        if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states "
                + Arrays.toString(stateSet) + " found " + idx);
        if (idx < 0) {
            idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
        }

        return selectDrawable(idx) || changed;
    }

	/**
	* StateListState.java
	*/
    int indexOfStateSet(int[] stateSet) {
            final int[][] stateSets = mStateSets;
            final int N = getChildCount();
            for (int i = 0; i < N; i++) {
                if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
                    return i;
                }
            }
            return -1;
        }


	/**
	* StateSet.java
	*/
    public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) {
        if (stateSet == null) {
            return (stateSpec == null || isWildCard(stateSpec));
        }
        int stateSpecSize = stateSpec.length;
        int stateSetSize = stateSet.length;
        //遍历的是StateListState集合,然后去查看要设置的状态是否都有这些状态
        for (int i = 0; i < stateSpecSize; i++) {
            int stateSpecState = stateSpec[i];
            if (stateSpecState == 0) {
                // We've reached the end of the cases to match against.
                return true;
            }
            final boolean mustMatch;
            if (stateSpecState > 0) {
                mustMatch = true;
            } else {
                // We use negative values to indicate must-NOT-match states.
                mustMatch = false;
                stateSpecState = -stateSpecState;
            }
            boolean found = false;
            for (int j = 0; j < stateSetSize; j++) {
                final int state = stateSet[j];
                if (state == 0) {
                    // We've reached the end of states to match.
                    if (mustMatch) {
                        // We didn't find this must-match state.
                        return false;
                    } else {
                        // Continue checking other must-not-match states.
                        break;
                    }
                }
                if (state == stateSpecState) {
                    if (mustMatch) {
                        found = true;
                        // Continue checking other other must-match states.
                        break;
                    } else {
                        // Any match of a must-not-match state returns false.
                        return false;
                    }
                }
            }
            if (mustMatch && !found) {
                // We've reached the end of states to match and we didn't
                // find a must-match state.
                return false;
            }
        }
        return true;
    }

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值