安卓使用ripple实现点击时的涟漪效果 - 简书

https://www.jb51.net/article/145309.htm

Android:RippleDrawable 水波纹/涟漪效果 - 简书

Android5.0 水波控件RippleDrawable简析_Jeepend的专栏-CSDN博客

RippleDrawable水波绘制分析 - 掘金

自定义Drawable实现点击水波纹涟漪效果_Eshel的博客-CSDN博客

自定义一个水波纹 xml:

<ripple xmlns:android="http://schemas.android.com/apk/res/android"android:color="@color/colorPrimary"><itemandroid:id="@android:id/mask"android:drawable="@android:color/white" /><item android:drawable="@color/cccccc" /></ripple>

在view 中使用:

我们再来梳理下绘制流程:

  • RippleDrawableinflate过程初始化了一层层的layer,添加到LayerState里面,初始化mask部分的drawable,放到了mMask全局drawable里面,初始化了ripple标签里面的color属性。
  • 在RippleDrawable静态绘制部分先是绘制了非id=mask的item
  • mask部分color属性值alpha=255是不会绘制的,因此颜色值的alpha值需要在[0,255)这个区间,mask绘制是在rippleForeground和RippleBackground的绘制下层。
  • 接着绘制RippleBackground部分,如果RippleBackground.isVisible才绘制。
  • 接着绘制每次exit未完成的RippleForeground部分,注意这里是个集合遍历绘制RippleForeground
  • 接着才是绘制当前次的RippleForeground
  • 在动画部分,先是触发了RippleDrawableonStateChange方法,接着创建了RippleForeground,调用了RippleForegroundentersetup``方法,在enter里面创建了softWare动画,其中hardWare动画是要开启了硬件加速功能才能创建,所以默认不会创建softWare`动画。
  • RippleForeground中的softWare创建的动画有三个,一个是半径、圆心、透明度变化的三个动画,在enter的时候RippleForegroundRippleDrawable.isBounded的时候不创建动画;在exit的时候不会限制创建动画,这个是在android-27下面的源码。在android-28的手机上面我看下了效果是在enter的时候有水波动画,exit的时候没有动画,大家可以用android-28的手机尝试下。
  • RippleBackground中就一个动画,改变画笔的透明底,enter情况下画笔从0到1的过程;在exit的时候画笔的透明度先是从1到0,然后又从0到1的过程。
  • 上面提到的enterexit中的动画,都是不断地调用到RippleDrawableinvalidateSelf方法,而invalidateSelf会触发viewdraw方法,最后触发了RippleDrawabledraw方法,最终会触发到RippleForegrounddrawSoftwareRippleBackgrounddrawSoftware
  • RippleDrawable中动画销毁是在view#dispatchdetachedFromWindowRippleDrawablejumpToCurrentState方法。

其中:RippleForground 与 RippleBackground 的区别

RippleForground负责水波的绘制,RippleBackground负责绘制透明度渐变的动画

1) RippleDrawable的绘制

关于drawable的绘制,直接看RippleDrawable的draw方法:

    @Overridepublic void draw(@NonNull Canvas canvas) {if (mState.mRippleStyle == STYLE_SOLID) {drawSolid(canvas);} else {drawPatterned(canvas);}}
---------------private void drawSolid(Canvas canvas) {pruneRipples();// Clip to the dirty bounds, which will be the drawable bounds if we// have a mask or content and the ripple bounds if we're projecting.final Rect bounds = getDirtyBounds();
//先保存canvas的状态final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);if (isBounded()) {
//裁剪drawable的区域canvas.clipRect(bounds);}
//绘制content部分drawContent(canvas);
//绘制波纹部分drawBackgroundAndRipples(canvas);
// 还原canvas的状态canvas.restoreToCount(saveCount);}

直接绘制item的id不是mask的drawable。在开篇的事例中,不带id=mask的drawable="#cccccc",此处是一个colorDrawable。

private void drawContent(Canvas canvas) {// Draw everything except the mask.final ChildDrawable[] array = mLayerState.mChildren;final int count = mLayerState.mNumChildren;for (int i = 0; i < count; i++) {if (array[i].mId != R.id.mask) {array[i].mDrawable.draw(canvas);}}
}

这部分是波纹效果的关键,看下drawBackgroundAndRipples方法:

    private void drawBackgroundAndRipples(Canvas canvas) {
//绘制水波的动画类final RippleForeground active = mRipple;
//绘制背景的动画类final RippleBackground background = mBackground;
//抬起的次数final int count = mExitingRipplesCount;if (active == null && count <= 0 && (background == null || !background.isVisible())) {// Move along, nothing to draw here.return;}
//获取到点击时的坐标final float x = mHotspotBounds.exactCenterX();final float y = mHotspotBounds.exactCenterY();
//将画布偏移到点击的坐标位置canvas.translate(x, y);final Paint p = getRipplePaint();//如果background不为空,并且isVisible才去绘制backgroundif (background != null && background.isVisible()) {background.draw(canvas, p);}//将每一次exit的ripple依次绘制出来,可以看出来该处是绘制波纹效果的关键if (count > 0) {final RippleForeground[] ripples = mExitingRipples;for (int i = 0; i < count; i++) {ripples[i].draw(canvas, p);}}//当前次的rippleForeground绘制if (active != null) {active.draw(canvas, p);}canvas.translate(-x, -y);}

2) 取消动画流程:

  • frameworks/base/core/java/android/view/View.java
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)void dispatchDetachedFromWindow() {AttachInfo info = mAttachInfo;if (info != null) {int vis = info.mWindowVisibility;if (vis != GONE) {onWindowVisibilityChanged(GONE);if (isShown()) {// Invoking onVisibilityAggregated directly here since the subtree// will also receive detached from windowonVisibilityAggregated(false);}}}//一般自定义view的时候重写该方法,比如释放动画等等onDetachedFromWindow();//销毁drawable的地方onDetachedFromWindowInternal();------------@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)protected void onDetachedFromWindowInternal() {mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;removeUnsetPressCallback();removeLongPressCallback();removePerformClickCallback();clearAccessibilityThrottles();stopNestedScroll();// Anything that started animating right before detach should already// be in its final state when re-attached.jumpDrawablesToCurrentState();-------------
public void jumpDrawablesToCurrentState() {if (mBackground != null) {mBackground.jumpToCurrentState();}if (mStateListAnimator != null) {mStateListAnimator.jumpToCurrentState();}if (mDefaultFocusHighlight != null) {mDefaultFocusHighlight.jumpToCurrentState();}if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {mForegroundInfo.mDrawable.jumpToCurrentState();}
}

都是调用了drawable的jumpToCurrentState方法,直接来到RippleDrawable下面的该方法:

  • frameworks/base/graphics/java/android/graphics/drawable/RippleDrawable.java
    @Overridepublic void jumpToCurrentState() {super.jumpToCurrentState();if (mRipple != null) {mRipple.end();}if (mBackground != null) {mBackground.jumpToFinal();}cancelExitingRipples();endPatternedAnimations();}------------------private void cancelExitingRipples() {final int count = mExitingRipplesCount;final RippleForeground[] ripples = mExitingRipples;for (int i = 0; i < count; i++) {ripples[i].end();}if (ripples != null) {Arrays.fill(ripples, 0, count, null);}mExitingRipplesCount = 0;// Always draw an additional "clean" frame after canceling animations.invalidateSelf(false);}

调用了RippleForegroundendRippleBackgroundend以及在cancelExitingRipples方法里面调用了每次exit未完成的RippleForeground的end方法

  • frameworks/base/graphics/java/android/graphics/drawable/RippleForeground.java
     * Ends all animations, jumping values to the end state.*/public void end() {for (int i = 0; i < mRunningSwAnimators.size(); i++) {mRunningSwAnimators.get(i).end();}mRunningSwAnimators.clear();for (int i = 0; i < mRunningHwAnimators.size(); i++) {mRunningHwAnimators.get(i).end();}mRunningHwAnimators.clear();}

3)触发 RippleForeground 的动画

由下面代码分析进行如下炒作:

        mRipple.setup(mState.mMaxRadius, mDensity);mRipple.enter();

setup 时调用父类方法

  • frameworks/base/graphics/java/android/graphics/drawable/RippleComponent.java
    public final void setup(float maxRadius, int densityDpi) {if (maxRadius >= 0) {mHasMaxRadius = true;mTargetRadius = maxRadius;} else {mTargetRadius = getTargetRadius(mBounds);}mDensityScale = densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;onTargetRadiusChanged(mTargetRadius);}// getTargetRadius里面通过勾股定理得到view大小的对角线的一半。最后调用了onTargetRadiusChanged方法,该方法是个空方法,可以想到是交给子类自己去处理mTargetRadius的问题private static float getTargetRadius(Rect bounds) {final float halfWidth = bounds.width() / 2.0f;final float halfHeight = bounds.height() / 2.0f;return (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);}

enter 方法,绘制动画:

     * Starts a ripple enter animation.*/public final void enter() {mEnterStartedAtMillis = AnimationUtils.currentAnimationTimeMillis();
// 软件加速绘制startSoftwareEnter();
// 硬件加速绘制startHardwareEnter();}
------------private void startSoftwareEnter() {for (int i = 0; i < mRunningSwAnimators.size(); i++) {mRunningSwAnimators.get(i).cancel();}mRunningSwAnimators.clear();//radius动画final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);tweenRadius.setDuration(RIPPLE_ENTER_DURATION);tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);tweenRadius.start();mRunningSwAnimators.add(tweenRadius);//水波画圆的时候圆心动画,从点击的点到rippleDrawable中心位置一直到点击的点到rippleDrawable中心位置的0.7的圆心渐变动画final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);tweenOrigin.setDuration(RIPPLE_ORIGIN_DURATION);tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);tweenOrigin.start();mRunningSwAnimators.add(tweenOrigin);//透明度的动画final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);opacity.setDuration(OPACITY_ENTER_DURATION);opacity.setInterpolator(LINEAR_INTERPOLATOR);opacity.start();mRunningSwAnimators.add(opacity);}
  • tweenRadius定义水波画圆的时候半径的动画
  • tweenOrigin定义水波画圆的时候圆心的动画
  • opacity定义水波透明度的动画、 上面三个动画都用到了动画的Property形式实现当前类值的改变,都是从0到1的过程,在tweenRadius动画中不断改变RippleForeground中的mTweenRadius变量,在tweenOrigin动画中不断改变mTweenXmTweenX全局变量,opacity动画中不断改变mOpacity全局变量。并且在动画的setValue方法中都会调用invalidateSelf方法,最终会重新调用到rippleDrawable的invalidateSelf方法,在第一节中简单提过invalidateSelf方法,最终会触发drawable的draw方法,因此可以想到实际上rippleForeground中的动画会不断draw方法:
  • frameworks/base/graphics/java/android/graphics/drawable/RippleForeground.java
    public void draw(Canvas c, Paint p) {final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof RecordingCanvas;pruneSwFinished();if (hasDisplayListCanvas) {final RecordingCanvas hw = (RecordingCanvas) c;drawHardware(hw, p);} else {
// 如果没开启硬件加速,hardWare动画是没有打开的,因此直接看drawSoftware部分drawSoftware(c, p);}}-----------------private void drawSoftware(Canvas c, Paint p) {
//获取到画笔最开始的透明度,透明度是ripple标签color颜色值透明度的一半final int origAlpha = p.getAlpha();final int alpha = (int) (origAlpha * mOpacity + 0.5f);
//获取到当前的圆的半径final float radius = getCurrentRadius();if (alpha > 0 && radius > 0) {final float x = getCurrentX();final float y = getCurrentY();p.setAlpha(alpha);c.drawCircle(x, y, radius, p);p.setAlpha(origAlpha);}}

上面通过mOpacity算出当前画笔的透明度,这里用了一个+0.5f转成int类型,这个是很常用的float转int类型的计算方式吧,通常在现有基础上+0.5f。mOpacity变量是在opacity动画中通过它的property改变全局属性的方式,关于动画大家可以看看property的使用,这里用到的是FloatProperty的类型:

/*** Property for animating opacity between 0 and its target value.*/
private static final FloatProperty<RippleForeground> OPACITY =new FloatProperty<RippleForeground>("opacity") {@Overridepublic void setValue(RippleForeground object, float value) {object.mOpacity = value;object.invalidateSelf();}@Overridepublic Float get(RippleForeground object) {return object.mOpacity;}
};

时序图如下:

1. 水波纹效果执行流程

如果自定义了View,会响应ontouch 点击事件:

  • frameworks/base/core/java/android/view/View.java
    public boolean onTouchEvent(MotionEvent event) {final float x = event.getX();final float y = event.getY();final int viewFlags = mViewFlags;final int action = event.getAction();final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;if ((viewFlags & ENABLED_MASK) == DISABLED&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {// 这里有个 setPressedsetPressed(false);}mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;// A disabled view that is clickable still consumes the touch// events, it just doesn't respond to them.return clickable;}if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {// 如果是向下点击事件的话case MotionEvent.ACTION_DOWN:if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {mPrivateFlags3 |= PFLAG3_FINGER_DOWN;}mHasPerformedLongPress = false;if (!clickable) {checkForLongClick(ViewConfiguration.getLongPressTimeout(),x,y,TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);break;}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();}mPendingCheckForTap.x = event.getX();mPendingCheckForTap.y = event.getY();postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());// 否则,设置点击是 true} else {// Not inside a scrolling container, so show the feedback right awaysetPressed(true, x, y);checkForLongClick(ViewConfiguration.getLongPressTimeout(),x,y,TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);}break;
    private void setPressed(boolean pressed, float x, float y) {if (pressed) {// xy是点击的焦点drawableHotspotChanged(x, y);}
// 设置为 truesetPressed(pressed);}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);}

刷新图标的状态

    public void refreshDrawableState() {mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;// 绘制图标状态变化drawableStateChanged();ViewParent parent = mParent;if (parent != null) {parent.childDrawableStateChanged(this);}}protected void drawableStateChanged() {final int[] state = getDrawableState();boolean changed = false;// 看后台的Drawable 、前台的是否不为空 isStateful,如果不是,则去设置状态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 changesjumpDrawablesToCurrentState();}if (changed) {invalidate();}}

调用 Drawable 去setState 设置状态

  • frameworks/base/graphics/java/android/graphics/drawable/Drawable.java
// 这是子类 RippleDrawable 调用父类的 setState方法public boolean setState(@NonNull final int[] stateSet) {if (!Arrays.equals(mStateSet, stateSet)) {mStateSet = stateSet;return onStateChange(stateSet);}return false;}// 其对应会走到子类的 onStateChangeprotected boolean onStateChange(int[] state) {return false;}

调用 子类 RippleDrawable 的 onStateChanged 方法:

  • frameworks/base/graphics/java/android/graphics/drawable/RippleDrawable.java

onStateChange逻辑很清晰,在enable并且pressed状态下会触发setRippleActivesetBackgroundActive方法

    @Overrideprotected boolean onStateChange(int[] stateSet) {final boolean changed = super.onStateChange(stateSet);// 这里通过获取 stateSet 数组,得到对应的点击事件boolean enabled = false;boolean pressed = false;boolean focused = false;boolean hovered = false;for (int state : stateSet) {if (state == R.attr.state_enabled) {enabled = true;} else if (state == R.attr.state_focused) {focused = true;
// 如果有点击的状态的话} else if (state == R.attr.state_pressed) {pressed = true;} else if (state == R.attr.state_hovered) {hovered = true;}}
// 设置动态波纹效果的激活的setRippleActive(enabled && pressed);setBackgroundActive(hovered, focused, pressed);return changed;}
=============private void setRippleActive(boolean active) {if (mRippleActive != active) {mRippleActive = active;
// 极大可能走这里if (mState.mRippleStyle == STYLE_SOLID) {if (active) {
// //按下的时候调用该方法tryRippleEnter();} else {
//抬起的时候调用该方法tryRippleExit();}} else {if (active) {startPatternedAnimation();} else {exitPatternedAnimation();}}}}=============* Attempts to start an enter animation for the active hotspot. Fails if* there are too many animating ripples.*/private void tryRippleEnter() {
//限制了ripple最大的次数if (mExitingRipplesCount >= MAX_RIPPLES) {// This should never happen unless the user is tapping like a maniac// or there is a bug that's preventing ripples from being removed.return;}if (mRipple == null) {final float x;final float y;if (mHasPending) {
按下时候的坐标mHasPending = false;x = mPendingX;y = mPendingY;} else {x = mHotspotBounds.exactCenterX();y = mHotspotBounds.exactCenterY();}
//生成了一个RippleForegroundmRipple = new RippleForeground(this, mHotspotBounds, x, y, mForceSoftware);}
//紧接着调用了setUp和enter方法mRipple.setup(mState.mMaxRadius, mDensity);mRipple.enter();}

2. 自定义水波纹效果

自定义Drawable实现点击水波纹涟漪效果_Eshel的博客-CSDN博客

既然是自定义 Drawable 实现,那么有如下问题需要解答:

  • 在 Drawable 里怎么监听用户点击事件? 难道要外部调用吗? 系统可没有这么干
  • 怎么监听点击的位置? (明显系统的效果是从点击位置开始扩散的)
  • 水波纹效果分析, 包含哪些动画?

退出的动画

自定义的明显比系统的生硬,仔细观察系统波纹效果,其实抬起的时候是有一个渐变的动画的,接下来就来实现这个渐变动画。

private void exit() {//如果正在执行扩散动画, 需要等待动画执行完毕再执行退出动画if(progress != maxProgress && mState == STATE_ENTER){pendingExit = true;}else {mState = STATE_EXIT;startExitAnimation();}
}private void startExitAnimation() {if(mRunningAnimator != null && mRunningAnimator.isRunning()){mRunningAnimator.cancel();}mRunningAnimator = ValueAnimator.ofInt(maxProgress, 0);mRunningAnimator.setInterpolator(new LinearInterpolator());mRunningAnimator.setDuration(animationTime);//300msmRunningAnimator.addUpdateListener(animation -> {progress = (int) animation.getAnimatedValue();invalidateSelf();});mRunningAnimator.start();
}@Override
public void draw(@NonNull Canvas canvas) {if(mState == STATE_ENTER){if(mPaint.getAlpha() != mRealAlpha){mPaint.setAlpha(mRealAlpha);}canvas.drawCircle(mPressedPointF.x, mPressedPointF.y, mMaxRadius * progress / maxProgress, mPaint);}else if(mState == STATE_EXIT){mPaint.setAlpha(mRealAlpha * progress / maxProgress);canvas.drawRect(getBounds(), mPaint);}
}

3. 点击 Buttom 回调 onClick 流程

 在activity 中 自定义的点击响应事件:

        btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {}});

其中:View.OnClickListener,  是View的一个内部接口,只有一个实现方法。

//源码
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {/*** Interface definition for a callback to be invoked when a view is clicked.*/public interface OnClickListener {/*** Called when a view has been clicked.** @param v The view that was clicked.*/void onClick(View v);}
}

实现为:这里要设置 setOnClickListener 点击的监听器


public class TestA implements View.OnClickListener {// 这里要设置 setOnClickListenerbutton.setOnClickListener(this);/*** Called when a view has been clicked.** @param v The view that was clicked.*/@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.test1://BALBALAbreak;case...}}
}

看一下 button 的继承链:

  • frameworks/base/core/java/android/widget/Button.java
public class Button extends TextView {
  • frameworks/base/core/java/android/widget/TextView.java
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {static final String LOG_TAG = "TextView";
  • frameworks/base/core/java/android/view/View.java
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)private static final boolean DBG = false;// 调用父类 View的类,设置监听器为 OnClickListener ,赋值给mOnClickListener public void setOnClickListener(@Nullable OnClickListener l) {if (!isClickable()) {setClickable(true);}
//一个view组件含多个Listener监听器,这个只赋值click,其他的还有OnFocusChangeListener、OnLongClickListener等getListenerInfo().mOnClickListener = l;}

当有点击事件触发的时候,有up 事件,则去响应click 事件:

    public boolean onTouchEvent(MotionEvent event) {final float x = event.getX();final float y = event.getY();final int viewFlags = mViewFlags;final int action = event.getAction();final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;if ((viewFlags & ENABLED_MASK) == DISABLED&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {setPressed(false);}mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;// A disabled view that is clickable still consumes the touch// events, it just doesn't respond to them.return clickable;}if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}}if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {
// 响应 up事件case MotionEvent.ACTION_UP:mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;if ((viewFlags & TOOLTIP) == TOOLTIP) {handleTooltipUp();}if (!clickable) {removeTapCallback();removeLongPressCallback();mInContextButtonPress = false;mHasPerformedLongPress = false;mIgnoreNextUpEvent = false;break;}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, x, y);}if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {// This is a tap, so remove the longpress checkremoveLongPressCallback();// Only perform take click actions if we were in the pressed stateif (!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();}
// post 增加到消息队列中if (!post(mPerformClick)) {
// 响应点击事件performClickInternal();}}}if (mUnsetPressedState == null) {mUnsetPressedState = new UnsetPressedState();}if (prepressed) {postDelayed(mUnsetPressedState,ViewConfiguration.getPressedStateDuration());} else if (!post(mUnsetPressedState)) {// If the post failed, unpress right nowmUnsetPressedState.run();}removeTapCallback();}mIgnoreNextUpEvent = false;break;
// 响应 down 点击事件case MotionEvent.ACTION_DOWN:if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {mPrivateFlags3 |= PFLAG3_FINGER_DOWN;}mHasPerformedLongPress = false;

performClickInternal

    private boolean performClickInternal() {// Must notify autofill manager before performing the click actions to avoid scenarios where// the app has a click listener that changes the state of views the autofill service might// be interested on.notifyAutofillManagerOnClick();return performClick();}---------public boolean performClick() {// We still need to call this method to handle the cases where performClick() was called// externally, instead of through performClickInternal()notifyAutofillManagerOnClick();final boolean result;final ListenerInfo li = mListenerInfo;// 如果观察者不为空的话,回调响应 onclick 事件if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);notifyEnterOrExitForAutoFillIfNeeded(true);return result;}

【安卓R 源码】Ripple 水波纹效果源码相关推荐

  1. Android TabLayout选项卡点击选中Ripple水波纹

    Android TabLayout选项卡点击时候选中的Ripple水波纹 如果要实现自定义的TabLayout选项卡被点击选中时候的水波纹效果,要从xml属性定义中的: app:tabBackgrou ...

  2. Android 水波纹效果的探究

    前言 水波纹效果从Android5.0就已经出来了,基本的使用相信大家都知道了,这里多谈一些相对深层次的使用: 1.基本使用 2.水波纹效果与布局绘制之间的问题 3.长按水波纹扩散效果 4.Butto ...

  3. 水波纹效果,附工程源码

    2019独角兽企业重金招聘Python工程师标准>>> 实现的一个水波纹效果,附工程代码. 这个可以用任意图片响应触屏事件显示波纹效果. 很多方面都能用得上,比如动态桌面,游戏,水族 ...

  4. android水波纹动画制作,Framer之事件 | 如何制作安卓点击水波纹效果?

    之前的 Framer 教程都是按照个人喜好去写的,没有按照难易程度形成系列.为了让大家能更好地入门,我准备由易到难写一个系列教程,尽量保持在每周一篇的频率. 导读:事件是 Framer 中的一个重要概 ...

  5. 去除安卓点击的水波纹效果

    在引用了appcompat_v7包后,Android Studio主题样式Base application theme会自动引用最新的主题样式Theme.AppCompat.Light:sdk21版本 ...

  6. Android点击水波纹扩散效果整理(附带一个自定义的水波纹效果控件)

    很久很久没有写博客了,说来也有点惭愧.正好最近整理自己的项目工程目录,看到一些值得分享的控件,准备在之后的几篇博客中准备把它们陆续搬运上来. 这篇博客准备整理一下Android Material De ...

  7. 聊聊Android5.0中的水波纹效果

    水波纹效果已经不是什么稀罕的东西了,用过5.0新控件的小伙伴都知道这个效果,可是如果使用一个TextView或者Button或者其它普通控件的话,你是否知道如何给它设置水波纹效果呢?OK,我们今天就来 ...

  8. android自定义水波纹,Android自定义View——实现水波纹效果类似剩余流量球(示例代码)...

    最近突然手痒就想搞个贝塞尔曲线做个水波纹效果玩玩,终于功夫不负有心人最后实现了想要的效果,一起来看下吧: 效果图镇楼 一:先一步一步来分解一下实现的过程 需要绘制一个正弦曲线(sin)或者余弦曲线(c ...

  9. html5 水波式按钮_css3+jQuery实现按钮水波纹效果

    水波纹按钮 /*自定义按钮样式*/ .btns{ height: 30px; line-height: 30px; text-align: center; width: 200px; color: # ...

最新文章

  1. 2011年工作总结和展望(下篇)
  2. log4cxx体系结构
  3. 【Python】学习笔记总结7(简单爬虫)
  4. ftp列表错误,flashfxp列表错误,ftp无法列目录的解决方法
  5. Java 技术小图谱
  6. Kubernetes:标签、选择器、注解、容忍度、亲和性
  7. UNIX网络编程笔记(7):回射程序的UDP版本
  8. Python学习笔记之类(二)
  9. 32位linux系统支持多大内存吗,linux32位操作系统支持大内存
  10. red hat linux综合实验报告,实验一 Red Hat Linux 9.doc
  11. H3C交换机配置SSH
  12. pr cpu100%_PR插件Sapphire2019.52安装教程
  13. 图片太大了怎么改小kb?
  14. 从语义网络到知识图谱
  15. 计算机网络 第七章 网络安全
  16. 汽车年检,备忘一下,估计2年以后才有用了
  17. 共读四步法:看见正向核心的力量—欣赏式探询共读会
  18. Java泛型面试也能虐暴你
  19. 上官婉儿飞天连招(玩法解析)
  20. elementUI实现table表头展示上、下角标

热门文章

  1. sha 2 java加密_MD5、SHA、SHA-2算法的实现(Java)
  2. 使用HTML5和jQuery插件Reel实现一个超酷的星际争霸2兵种动画360度预览效果
  3. Win11如何使用IE浏览器
  4. 2021年中国音乐市场年度洞察——附下载链接
  5. 蝴蝶Java_模型从卡特彼勒到蝴蝶在Java
  6. Oracle 常用总结
  7. Drawing 2-Point Perspective 绘制2点透视图 Lynda课程中文字幕
  8. ISO三体系认证有哪些意义和好处?
  9. 关于Arduino Mega2560的最基本介绍
  10. 软考高项—第二章立项管理