2014-10-28 62 views
129

我正在嘗試在按鈕單擊上添加一條漣漪動畫。我不喜歡以下,但它需要的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" /> 

我希望把它與設計庫向後兼容。

這是如何做到的?

回答

293

基本紋波設置

  • 漣漪包含在視圖中。
    android:background="?selectableItemBackground"

  • 漣漪超出視圖邊界:
    android:background="?selectableItemBackgroundBorderless"

    有解決Java代碼?(attr) XML引用看看here

支持庫

  • 使用?attr:(或?簡寫),而不是?android:attr引用support library,所以可回API 7。

漣漪與圖像/背景

  • 擁有一個圖像或背景和重疊紋波的最簡單的解決方案是用setForeground()setBackground()設置波紋包裹ViewFrameLayout

老實說存在,否則這樣的沒有乾淨的方式,雖然尼克肉店老闆後this上的ImageView s的漣漪的主題。

+32

這不會爲21之前的版本添加紋波支持。 – AndroidDev 2015-04-06 06:11:56

+16

它可能不會添加紋波支持,但此解決方案很好地降級。這實際上解決了我遇到的特殊問題。我想要L上的漣漪效果,以及之前版本的Android上的簡單選擇。 – 2015-06-16 07:12:58

+3

@AndroidDev,@Dave Jensen:實際上,使用'?attr:'而不是'android:attr'引用v7支持庫,假設你使用它,它可以向後兼容API 7。 //developer.android.com/tools/support-library/features.html#v7 – 2015-06-17 07:54:42

53

我以前投票決定關閉這個問題作爲題外話題,但實際上我改變了主意,因爲這是相當不錯的視覺效果,不幸的是,它還不是支持庫的一部分。它很可能出現在未來的更新中,但沒有公佈時間表。

幸運的是有一些自定義的實現已經可用:

包括Materlial爲主題的小部件設置與舊版Android的兼容:

所以你可以嘗試這些中的一個或谷歌等 「材料部件」 或使...

+12

這是現在支持庫的一部分,請參閱我的答案。 – 2015-06-17 09:08:31

+0

謝謝!我使用了[second lib](https://github.com/balysv/material-ripple),第一個在慢速手機中太慢了。 – 2015-11-25 18:29:21

25

我做了一個簡單的類,使波紋按鍵,我從來沒有需要它到底所以它不是最好的,但在這裏它是:

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); 
    } 
} 
+0

else if(clickListener!= null){ clickListener.onClick(thisRippleView); } – 2016-09-28 14:34:36

+0

簡單實現...即插即用:) – 2017-10-10 04:09:06

+0

如果我在RecyclerView的每個視圖上使用此類,我將得到ClassCastException。 – 2017-11-08 06:56:13