如何使用支持库实现涟漪animation?

我试图添加一个涟漪animationbutton点击。 我喜欢下面,但它需要minSdKVersion 21。

ripple.xml

<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight"> <item> <shape android:shape="rectangle"> <solid android:color="?android:colorAccent" /> </shape> </item> </ripple> 

按键

 <com.devspark.robototextview.widget.RobotoButton android:id="@+id/loginButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/ripple" android:text="@string/login_button" /> 

我想让它向后兼容devise库。

如何做到这一点?

基本的纹波设置

  • 视图中包含的涟漪
    android:background="?selectableItemBackground"

  • 超出视野界限的涟漪:
    android:background="?selectableItemBackgroundBorderless"

    在这里看看解决Java代码中的?(attr) xml引用。

支持库

  • 使用?attr:?简写)而不是?android:attr引用支持库 ,所以可以返回到API 7。

涟漪与图像/背景

  • 要获得图像或背景并覆盖波纹,最简单的解决scheme是将View封装在FrameLayout ,并使用setForeground()setBackground()设置纹波。

老实说,除此之外,还没有干净的方式来做这件事,尽pipeNick Butcher在ImageView的主题上发表了涟漪。

我以前投票决定把这个问题作为题外话,但实际上我改变了主意,因为这是相当不错的视觉效果,不幸的是,它还不是支持图书馆的一部分。 它很可能在未来的更新中出现,但是没有时间框架。

幸运的是,有几个自定义的实现已经可用:

包括Materlial主题的小部件集与旧版本的Android兼容:

所以你可以尝试其中的一个或谷歌的其他“材料小工具”左右…

我做了一个简单的课,让涟漪button,我从来没有需要它,所以它不是最好的,但这里是:

 import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Handler; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.Button; public class RippleView extends Button { private float duration = 250; private float speed = 1; private float radius = 0; private Paint paint = new Paint(); private float endRadius = 0; private float rippleX = 0; private float rippleY = 0; private int width = 0; private int height = 0; private OnClickListener clickListener = null; private Handler handler; private int touchAction; private RippleView thisRippleView = this; public RippleView(Context context) { this(context, null, 0); } public RippleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RippleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { if (isInEditMode()) return; handler = new Handler(); paint.setStyle(Paint.Style.FILL); paint.setColor(Color.WHITE); paint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; } @Override protected void onDraw(@NonNull Canvas canvas) { super.onDraw(canvas); if(radius > 0 && radius < endRadius) { canvas.drawCircle(rippleX, rippleY, radius, paint); if(touchAction == MotionEvent.ACTION_UP) invalidate(); } } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { rippleX = event.getX(); rippleY = event.getY(); switch(event.getAction()) { case MotionEvent.ACTION_UP: { getParent().requestDisallowInterceptTouchEvent(false); touchAction = MotionEvent.ACTION_UP; radius = 1; endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY); speed = endRadius / duration * 10; handler.postDelayed(new Runnable() { @Override public void run() { if(radius < endRadius) { radius += speed; paint.setAlpha(90 - (int) (radius / endRadius * 90)); handler.postDelayed(this, 1); } else { clickListener.onClick(thisRippleView); } } }, 10); invalidate(); break; } case MotionEvent.ACTION_CANCEL: { getParent().requestDisallowInterceptTouchEvent(false); touchAction = MotionEvent.ACTION_CANCEL; radius = 0; invalidate(); break; } case MotionEvent.ACTION_DOWN: { getParent().requestDisallowInterceptTouchEvent(true); touchAction = MotionEvent.ACTION_UP; endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY); paint.setAlpha(90); radius = endRadius/4; invalidate(); return true; } case MotionEvent.ACTION_MOVE: { if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height) { getParent().requestDisallowInterceptTouchEvent(false); touchAction = MotionEvent.ACTION_CANCEL; radius = 0; invalidate(); break; } else { touchAction = MotionEvent.ACTION_MOVE; invalidate(); return true; } } } return false; } @Override public void setOnClickListener(OnClickListener l) { clickListener = l; } } 

编辑

因为很多人都在寻找这样的东西,我做了一个可以让其他观点产生连锁反应的课堂:

 import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Handler; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; public class RippleViewCreator extends FrameLayout { private float duration = 150; private int frameRate = 15; private float speed = 1; private float radius = 0; private Paint paint = new Paint(); private float endRadius = 0; private float rippleX = 0; private float rippleY = 0; private int width = 0; private int height = 0; private Handler handler = new Handler(); private int touchAction; public RippleViewCreator(Context context) { this(context, null, 0); } public RippleViewCreator(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RippleViewCreator(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { if (isInEditMode()) return; paint.setStyle(Paint.Style.FILL); paint.setColor(getResources().getColor(R.color.control_highlight_color)); paint.setAntiAlias(true); setWillNotDraw(true); setDrawingCacheEnabled(true); setClickable(true); } public static void addRippleToView(View v) { ViewGroup parent = (ViewGroup)v.getParent(); int index = -1; if(parent != null) { index = parent.indexOfChild(v); parent.removeView(v); } RippleViewCreator rippleViewCreator = new RippleViewCreator(v.getContext()); rippleViewCreator.setLayoutParams(v.getLayoutParams()); if(index == -1) parent.addView(rippleViewCreator, index); else parent.addView(rippleViewCreator); rippleViewCreator.addView(v); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; } @Override protected void dispatchDraw(@NonNull Canvas canvas) { super.dispatchDraw(canvas); if(radius > 0 && radius < endRadius) { canvas.drawCircle(rippleX, rippleY, radius, paint); if(touchAction == MotionEvent.ACTION_UP) invalidate(); } } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return true; } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { rippleX = event.getX(); rippleY = event.getY(); touchAction = event.getAction(); switch(event.getAction()) { case MotionEvent.ACTION_UP: { getParent().requestDisallowInterceptTouchEvent(false); radius = 1; endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY); speed = endRadius / duration * frameRate; handler.postDelayed(new Runnable() { @Override public void run() { if(radius < endRadius) { radius += speed; paint.setAlpha(90 - (int) (radius / endRadius * 90)); handler.postDelayed(this, frameRate); } else if(getChildAt(0) != null) { getChildAt(0).performClick(); } } }, frameRate); break; } case MotionEvent.ACTION_CANCEL: { getParent().requestDisallowInterceptTouchEvent(false); break; } case MotionEvent.ACTION_DOWN: { getParent().requestDisallowInterceptTouchEvent(true); endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY); paint.setAlpha(90); radius = endRadius/3; invalidate(); return true; } case MotionEvent.ACTION_MOVE: { if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height) { getParent().requestDisallowInterceptTouchEvent(false); touchAction = MotionEvent.ACTION_CANCEL; break; } else { invalidate(); return true; } } } invalidate(); return false; } @Override public final void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) { //limit one view if (getChildCount() > 0) { throw new IllegalStateException(this.getClass().toString()+" can only have one child."); } super.addView(child, index, params); } }