我这不起作用的原因是由于对View设置了onTouchListener,且在ontouch方法中返回了true(具体原因后面会讲,此处我用的ImageView,在属性中设置src不起作用了)。
注:没有设置OnTouchListener的原因可能是要设置xml中的属性clickble为true。
注:MotinoEvent中的这些事件可以在View中具体看它们的解释在这就不多说了。
首先初始化:
一、在xml中设置了background/foreground后在构造方法中会初始化selector及shape等,
1.首先看下View设置selector或者shape之后是怎么初始化的:
可以看到View中有三个构造方法(主要看下一个参数跟四个参数的构造方法,其他构造方法最终会调用四个构造方法的):
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.
*
* If the View has a StateListAnimator, it will also be called to run necessary state
* change animations.
*
* 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.
*
* 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.
*
* @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的状态。