如何使用支持库实现涟漪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
的主题上发表了涟漪。
我以前投票决定把这个问题作为题外话,但实际上我改变了主意,因为这是相当不错的视觉效果,不幸的是,它还不是支持图书馆的一部分。 它很可能在未来的更新中出现,但是没有时间框架。
幸运的是,有几个自定义的实现已经可用:
- https://github.com/traex/RippleEffect
- https://github.com/balysv/material-ripple
- https://github.com/siriscac/RippleView
- https://github.com/ozodrukh/RippleDrawable
包括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); } }
- Android XML布局的“包含”标签真的有用吗?
- 在牛轧糖上的android.os.TransactionTooLargeException
- Android:java.lang.ClassCastException:android.widget.imageView无法转换为android.widget.textView
- 在Android中分页文字
- 如何知道一个RecyclerView / LinearLayoutManager是滚动到顶部还是底部?
- 如何获得android的本地方法的源代码
- 使用具有ClassLoader作为参数的Parcel.read方法时,“BadParcelableException:解组时使用ClassNotFoundException”<unclassified> <myclass>
- Android模拟器不启动,显示“无效的命令行参数”
- android:configChanges =“orientation”不能使用片段