前言
在写应用时 ,布局中设置了一个button之后点击这个button,button的背景就会变化,我们什么都没干就有了这个效果,最近项目正好涉及到相关的绘制,就去源码里面看一下点击时的实现。关于点击事件的传递我在的android 中事件传递分析 已经学习过了,其中还涉及到view的绘制view绘制流程。
内容
点击某个button时,首先时ViewGroup接收到这个事件然后分发下去dispatchTouchEvent,最后到了你的button的(button继承自view)的onTouchEvent中
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
// final int action = event.getAction();
// if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
// if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// setPressed(false);
// } else if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
// removeTapCallback();
// }
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true);
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN: //这个地方时你按下view的响应
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true);// 这里设置按下为true
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
setPressed(true); 那就走到了setPressed里面
/**
* 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);
}
看看refreshDrawableState 这里是干了什么
/**
* 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);//如果是个父视图的话,子view也相应更新。
}
}
看代码的时候注意看方法上面的注释,对我们理解流程有很大的帮组,上面说一会调drawableStateChanged这个方法,我们一会看看时在那里回调的。下面看看 drawableStateChanged(),
private Drawable mBackground;
/**
* 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>Be sure to call through to the superclass when overriding this
* function.
*
* @see Drawable#setState(int[])
*/
protected void drawableStateChanged() {
Drawable d = mBackground;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
看到了 这个地方给drawable设置了一个新的状态,通过getDrawableState()的值设置下去
/**
* Return an array of resource IDs of the drawable states representing the
* current state of the view.
*
* @return The current drawable state
*
* @see Drawable#setState(int[])
* @see #drawableStateChanged()
* @see #onCreateDrawableState(int)
*/
public final int[] getDrawableState() {
if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
return mDrawableState;
} else {
mDrawableState = onCreateDrawableState(0);
mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}
然后看看上面d.setState()这个方法里面, drawable 是一个抽象类,看看它的setState方法,
/**
* Specify a set of states for the drawable. These are use-case specific,
* so see the relevant documentation. As an example, the background for
* widgets like Button understand the following states:
* [{@link android.R.attr#state_focused},
* {@link android.R.attr#state_pressed}].
*
* <p>If the new state you are supplying causes the appearance of the
* Drawable to change, then it is responsible for calling
* {@link #invalidateSelf} in order to have itself redrawn, <em>and</em>
* true will be returned from this function.
*
* <p>Note: The Drawable holds a reference on to <var>stateSet</var>
* until a new state array is given to it, so you must not modify this
* array during that time.</p>
*
* @param stateSet The new set of states to be displayed.
*
* @return Returns true if this change in state has caused the appearance
* of the Drawable to change (hence requiring an invalidate), otherwise
* returns false.
*/
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
/**
* Override this in your subclass to change appearance if you recognize the
* specified state.
*
* @return Returns true if the state change has caused the appearance of
* the Drawable to change (that is, it needs to be drawn), else false
* if it looks the same and there is no need to redraw it since its
* last state.
*/
protected boolean onStateChange(int[] state) { return false; }
Override this in your subclass ,这个方法会在drawable的子类中实现,那就要找找是哪一个子类实现了这个方法了。找了半天都没有找到哪里有指定drawable的子类,但是功夫不负苦心人,看到了setBackgroundDrawable 这个方法在view当中,
/**
* @deprecated use {@link #setBackground(Drawable)} instead
*/
@Deprecated
public void setBackgroundDrawable(Drawable background) {
computeOpaqueFlags();
if (background == mBackground) {
return;
}
....
mBackground = background; // 你设什么类型下来,我就是怎么类型。button多个状态那肯定就是StateListDrawable了,StateListDrawable又继承自DrawableContainer,DrawableContainer继承自Drawable.
} onStateChange
那就看看StateListDrawable里面的onStateChange方法
@Override
protected boolean onStateChange(int[] 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);
}
if (selectDrawable(idx)) {
return true;
}
return super.onStateChange(stateSet);
}
看到了selectDrawable,亲人啊,selectDrawable是在它的父类DrawableContainer里面实现的,
public boolean selectDrawable(int idx) {
if (idx == mCurIndex) {
return false;
}
final long now = SystemClock.uptimeMillis();
if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx
+ ": 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 (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
final Drawable d = mDrawableContainerState.getChild(idx);
mCurrDrawable = d;
mCurIndex = idx;
if (d != null) {
mInsets = d.getOpticalInsets();
d.mutate();
if (mDrawableContainerState.mEnterFadeDuration > 0) {
mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
} else {
d.setAlpha(mAlpha);
}
d.setVisible(isVisible(), true);
d.setDither(mDrawableContainerState.mDither);
d.setColorFilter(mColorFilter);
d.setState(getState());
d.setLevel(getLevel());
d.setBounds(getBounds());
d.setLayoutDirection(getLayoutDirection());
d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
} else {
mInsets = Insets.NONE;
}
} else {
mCurrDrawable = null;
mInsets = Insets.NONE;
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();// 终于看到了invalidateSelf 方法了之前setState的注释里面有提到这个方法
return true;
}
invalidateSelf方法的实现在drawable里面,这里你需要知道view是有实现Drawable的callback接口的,**public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource **
/**
* Use the current {@link Callback} implementation to have this Drawable
* redrawn. Does nothing if there is no Callback attached to the
* Drawable.
*
* @see Callback#invalidateDrawable
* @see #getCallback()
* @see #setCallback(android.graphics.drawable.Drawable.Callback)
*/
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);//这个时候就回到了view 里面的invalidateDrawable方法里了
}
}
View.java中
/**
* Invalidates the specified Drawable.
*
* @param drawable the drawable to invalidate
*/
public void invalidateDrawable(Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY); // view 的刷新
}
}
invalidate 的刷新是通知了逐层上传,然后最终从父类开始重绘。ok!
关于invalidate的刷新可以看invalidate 分析 的5-3。
网友评论