一、原因:
我這不起作用的原因是由於對View設置了onTouchListener,且在ontouch方法中返回了true(具體原因後面會講,此處我用的ImageView,在屬性中設置src不起作用了)。
注:沒有設置OnTouchListener的原因可能是要設置xml中的屬性clickble爲true。
二、解決方法:
- 在onTouch方法中的MotionEvent.ACTION_DOWN的邏輯塊中調用view.setPressed(true);
- 在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:
- TextView的textColor:
首先可以在draw(Canvas canvas)方法中找到修改顏色的代碼:
可以看到顏色變量是mCurTextColor,而修改mCurTextColor的方法可以找到是updateTextColors():
而可以找到在drawableStateChanged方法中調用了updateTextColors,根據上面的描述可以知道在onTouchEvent中狀態如果變化了會回調drawableStateChanged這個方法的,因此如果設置了不同狀態的textColor則會根據狀態設置text的不同顏色的。