Android Open Source - Material Toolbar Ripple Drawable






From Project

Back to project page Material.

License

The source code is released under:

Apache License

If you think the Android project Material listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.rey.material.drawable;
// www . j  a v a  2  s . co  m
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;

import com.rey.material.R;
import com.rey.material.util.ColorUtil;
import com.rey.material.util.ThemeUtil;
import com.rey.material.util.ViewUtil;

public class ToolbarRippleDrawable extends Drawable implements Animatable {
  
  private boolean mRunning = false;
    
  private Paint mShaderPaint;
  private Paint mFillPaint;  
  private RadialGradient mInShader;
  private RadialGradient mOutShader;
  private Matrix mMatrix;    
  private int mAlpha = 255;
  
  private RectF mBackgroundBounds;
  private Path mBackground;
  private int mBackgroundAnimDuration;
  private int mBackgroundColor;
  private float mBackgroundAlphaPercent;
    
  private PointF mRipplePoint;
  private float mRippleRadius;
  private int mRippleType;
  private int mMaxRippleRadius;
  private int mRippleAnimDuration;
  private int mRippleColor;
  private float mRippleAlphaPercent;
      
  private Interpolator mInInterpolator;
  private Interpolator mOutInterpolator;
  
  private long mStartTime;  
  
  private boolean mPressed = false;
    
  private int mState = STATE_OUT;
  
  private static final int STATE_OUT = 0;
  private static final int STATE_PRESS = 1;
  private static final int STATE_HOVER = 2;
  private static final int STATE_RELEASE_ON_HOLD = 3;  
  private static final int STATE_RELEASE = 4;  
    
  private static final int TYPE_TOUCH = 0;
  private static final int TYPE_WAVE = 1;
  
  private static final float[] GRADIENT_STOPS = new float[]{0f, 0.99f, 1f};
  private static final float GRADIENT_RADIUS = 16;
    
  private ToolbarRippleDrawable(int backgroundAnimDuration, int backgroundColor, int rippleType, int maxTouchRadius, int touchAnimDuration, int touchColor, Interpolator inInterpolator, Interpolator outInterpolator){
    mBackgroundAnimDuration = backgroundAnimDuration;
    mBackgroundColor = backgroundColor;
    
    mRippleType = rippleType;
    mMaxRippleRadius = maxTouchRadius;
    mRippleAnimDuration = touchAnimDuration;
    mRippleColor = touchColor;
    
    mInInterpolator = inInterpolator;
    mOutInterpolator = outInterpolator;
    
    mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mFillPaint.setStyle(Paint.Style.FILL);
    
    mShaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mShaderPaint.setStyle(Paint.Style.FILL);
    
    mBackground = new Path();
    mBackgroundBounds = new RectF();
    
    mRipplePoint = new PointF();
    
    mMatrix = new Matrix();
    
    mInShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{mRippleColor, mRippleColor, 0}, GRADIENT_STOPS, Shader.TileMode.CLAMP);    
    if(mRippleType == TYPE_WAVE)
      mOutShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{0, ColorUtil.getColor(mRippleColor, 0f), mRippleColor}, GRADIENT_STOPS, Shader.TileMode.CLAMP);
  }
  
  @Override
  public void setAlpha(int alpha) {
    mAlpha = alpha;
  }

  @Override
  public void setColorFilter(ColorFilter filter) {
    mFillPaint.setColorFilter(filter);
    mShaderPaint.setColorFilter(filter);
  }

  @Override
  public int getOpacity() {
    return PixelFormat.TRANSLUCENT;
  }
    
  public long getClickDelayTime(boolean waitToAnimEnd){
    if(mState == STATE_RELEASE_ON_HOLD)      
      return (waitToAnimEnd ? 2 : 1) * Math.max(mBackgroundAnimDuration, mRippleAnimDuration) - (SystemClock.uptimeMillis() - mStartTime);
    else if(mState == STATE_RELEASE)
      return (waitToAnimEnd ? Math.max(mBackgroundAnimDuration, mRippleAnimDuration) : 0) - (SystemClock.uptimeMillis() - mStartTime);
    
    return -1;
  }
  
  private void setRippleState(int state){
    if(mState != state){
      mState = state;
      
      if(mState != STATE_OUT){        
        if(mState != STATE_HOVER)
          start();
        else
          stop();
      }
      else        
        stop();      
    }
  }
  
  private boolean setRippleEffect(float x, float y, float radius){
    if(mRipplePoint.x != x || mRipplePoint.y != y || mRippleRadius != radius){
      mRipplePoint.set(x, y);
      mRippleRadius = radius;      
      radius = mRippleRadius / GRADIENT_RADIUS;
      mMatrix.reset();
      mMatrix.postTranslate(x, y);
      mMatrix.postScale(radius, radius, x, y);
      mInShader.setLocalMatrix(mMatrix);
      if(mOutShader != null)
        mOutShader.setLocalMatrix(mMatrix);
      
      return true;
    }    
    
    return false;
  }
      
  @Override
  protected void onBoundsChange(Rect bounds) {      
    mBackgroundBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
    mBackground.reset();
    mBackground.addRect(mBackgroundBounds, Direction.CW);
  }
    
  @Override
  public boolean isStateful() {
    return true;
  }
  
  @Override
  protected boolean onStateChange(int[] state) {    
    boolean pressed = ViewUtil.hasState(state, android.R.attr.state_pressed);
    
    if(mPressed != pressed){
      mPressed = pressed;
      
      if(mPressed){
        Rect bounds = getBounds();
        
        if(mState == STATE_OUT || mState == STATE_RELEASE){
          if(mRippleType == TYPE_WAVE)
            mMaxRippleRadius = getMaxRippleRadius(bounds.exactCenterX(), bounds.exactCenterY());
          
          setRippleEffect(bounds.exactCenterX(), bounds.exactCenterY(), 0);
          setRippleState(STATE_PRESS);
        }
        else if(mRippleType == TYPE_TOUCH)
          setRippleEffect(bounds.exactCenterX(), bounds.exactCenterY(), mRippleRadius);        
      }
      else{
        if(mState != STATE_OUT){
          if(mState == STATE_HOVER){
            if(mRippleType == TYPE_WAVE)
              setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0);
            
            setRippleState(STATE_RELEASE);
          }
          else
            setRippleState(STATE_RELEASE_ON_HOLD);  
        }
      }
      
      return true;
    }
    
    return false;
  }
    
  @Override
  public void draw(Canvas canvas) {    
    switch (mRippleType) {
      case TYPE_TOUCH:
        drawTouch(canvas);
        break;
      case TYPE_WAVE:
        drawWave(canvas);
        break;
    }
  }
  
  private void drawTouch(Canvas canvas){
    if(mState != STATE_OUT){            
      if(mBackgroundAlphaPercent > 0){
        mFillPaint.setColor(mBackgroundColor);
        mFillPaint.setAlpha(Math.round(mAlpha * mBackgroundAlphaPercent));        
        canvas.drawPath(mBackground, mFillPaint);
      }
                  
      if(mRippleRadius > 0 && mRippleAlphaPercent > 0){
        mShaderPaint.setAlpha(Math.round(mAlpha * mRippleAlphaPercent));
        mShaderPaint.setShader(mInShader);
        canvas.drawPath(mBackground, mShaderPaint);
      }      
    }
  }
  
  private void drawWave(Canvas canvas){
    if(mState != STATE_OUT){
      if(mState == STATE_RELEASE){
        if(mRippleRadius == 0){
          mFillPaint.setColor(mRippleColor);
          canvas.drawPath(mBackground, mFillPaint);
        }
        else{
          mShaderPaint.setShader(mOutShader);
          canvas.drawPath(mBackground, mShaderPaint);
        }        
      }
      else if(mRippleRadius > 0){
        mShaderPaint.setShader(mInShader);
        canvas.drawPath(mBackground, mShaderPaint);
      }
    }      
  }
    
  private int getMaxRippleRadius(float x, float y){
    float x1 = x < mBackgroundBounds.centerX() ? mBackgroundBounds.right : mBackgroundBounds.left;
    float y1 = y < mBackgroundBounds.centerY() ? mBackgroundBounds.bottom : mBackgroundBounds.top;
    
    return (int)Math.round(Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)));
  }
  
  //Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/
  
  public void cancel(){
    setRippleState(STATE_OUT);
  }
  
  private void resetAnimation(){  
    mStartTime = SystemClock.uptimeMillis();
  }
  
  @Override
  public void start() {    
    if(isRunning())
      return;
                
    resetAnimation();
    
    scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
      invalidateSelf();  
  }

  @Override
  public void stop() {    
    if(!isRunning())
      return;
        
    mRunning = false;
    unscheduleSelf(mUpdater);
    invalidateSelf();
  }

  @Override
  public boolean isRunning() {
    return mRunning;
  }
  
  @Override
  public void scheduleSelf(Runnable what, long when) {
    mRunning = true;
      super.scheduleSelf(what, when);
  }
  
  private final Runnable mUpdater = new Runnable() {

      @Override
      public void run() {
        switch (mRippleType) {
        case TYPE_TOUCH:
          updateTouch();
          break;
        case TYPE_WAVE:
          updateWave();
          break;
      }
        
      }
        
  };
    
  private void updateTouch(){
    if(mState != STATE_RELEASE){
      float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration);
      mBackgroundAlphaPercent = mInInterpolator.getInterpolation(backgroundProgress) * Color.alpha(mBackgroundColor) / 255f;
      
      float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration);      
      mRippleAlphaPercent = mInInterpolator.getInterpolation(touchProgress);
      
      setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(touchProgress));
      
      if(backgroundProgress == 1f && touchProgress == 1f){
        mStartTime = SystemClock.uptimeMillis();
        setRippleState(mState == STATE_PRESS ? STATE_HOVER : STATE_RELEASE);
      }
      
    }
    else{
      float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration);
      mBackgroundAlphaPercent = (1f - mOutInterpolator.getInterpolation(backgroundProgress)) * Color.alpha(mBackgroundColor) / 255f;
      
      float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration);      
      mRippleAlphaPercent = 1f - mOutInterpolator.getInterpolation(touchProgress);
      
      setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * (1f + 0.5f * mOutInterpolator.getInterpolation(touchProgress)));
      
      if(backgroundProgress == 1f && touchProgress == 1f)
        setRippleState(STATE_OUT);  
    }
    
    if(isRunning())
      scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
    
    invalidateSelf();    
  }
  
  private void updateWave(){
    float progress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration);
    
    if(mState != STATE_RELEASE){      
      setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(progress));
      
      if(progress == 1f){
        mStartTime = SystemClock.uptimeMillis();
        if(mState == STATE_PRESS)
          setRippleState(STATE_HOVER);        
        else{
          setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0);
          setRippleState(STATE_RELEASE);
        }
      }
    }
    else{      
      setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mOutInterpolator.getInterpolation(progress));
      
      if(progress == 1f)
        setRippleState(STATE_OUT);
    }
    
    if(isRunning())
      scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
    
    invalidateSelf();    
  }
  
  public static class Builder{
    private int mBackgroundAnimDuration = 200;
    private int mBackgroundColor;
      
    private int mRippleType;
    private int mMaxRippleRadius;
    private int mRippleAnimDuration = 400;
    private int mRippleColor;
    
    private Interpolator mInInterpolator;
    private Interpolator mOutInterpolator;
            
    public Builder(){}
    
    public Builder(Context context, AttributeSet attrs, int defStyle){
      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RippleDrawable, 0, defStyle);      
      int resId;
      
      backgroundColor(a.getColor(R.styleable.RippleDrawable_rd_backgroundColor, 0));
      backgroundAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_backgroundAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime)));
      rippleType(a.getInteger(R.styleable.RippleDrawable_rd_rippleType, ToolbarRippleDrawable.TYPE_TOUCH));
      maxRippleRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_maxRippleRadius, ThemeUtil.dpToPx(context, 48)));
      rippleColor(a.getColor(R.styleable.RippleDrawable_rd_rippleColor, ThemeUtil.colorControlHighlight(context, 0)));
      rippleAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_rippleAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime)));
      if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_inInterpolator, 0)) != 0)
        inInterpolator(AnimationUtils.loadInterpolator(context, resId));
      if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_outInterpolator, 0)) != 0)
        outInterpolator(AnimationUtils.loadInterpolator(context, resId));
      
      a.recycle();      
    }
    
    public ToolbarRippleDrawable build(){
      if(mInInterpolator == null)
        mInInterpolator = new AccelerateInterpolator();
      
      if(mOutInterpolator == null)
        mOutInterpolator = new DecelerateInterpolator();
      
      return new ToolbarRippleDrawable(mBackgroundAnimDuration, mBackgroundColor, mRippleType, mMaxRippleRadius, mRippleAnimDuration, mRippleColor, mInInterpolator, mOutInterpolator);
    }
    
    public Builder backgroundAnimDuration(int duration){
      mBackgroundAnimDuration = duration;
      return this;
    }
    
    public Builder backgroundColor(int color){
      mBackgroundColor = color;
      return this;
    }
    
    public Builder rippleType(int type){
      mRippleType = type;
      return this;
    }
    
    public Builder maxRippleRadius(int radius){
      mMaxRippleRadius = radius;
      return this;
    }
    
    public Builder rippleAnimDuration(int duration){
      mRippleAnimDuration = duration;
      return this;
    }
    
    public Builder rippleColor(int color){
      mRippleColor = color;
      return this;
    }
    
    public Builder inInterpolator(Interpolator interpolator){
      mInInterpolator = interpolator;
      return this;
    }
    
    public Builder outInterpolator(Interpolator interpolator){
      mOutInterpolator = interpolator;
      return this;
    }
    
  }
}




Java Source Code List

com.rey.material.ApplicationTest.java
com.rey.material.demo.ButtonFragment.java
com.rey.material.demo.MainActivity.java
com.rey.material.demo.ProgressFragment.java
com.rey.material.demo.SnackbarFragment.java
com.rey.material.demo.SwitchesFragment.java
com.rey.material.demo.TextfieldFragment.java
com.rey.material.drawable.ArrowDrawable.java
com.rey.material.drawable.BlankDrawable.java
com.rey.material.drawable.CheckBoxDrawable.java
com.rey.material.drawable.CircularProgressDrawable.java
com.rey.material.drawable.DividerDrawable.java
com.rey.material.drawable.LineMorphingDrawable.java
com.rey.material.drawable.LinearProgressDrawable.java
com.rey.material.drawable.NavigationDrawerDrawable.java
com.rey.material.drawable.RadioButtonDrawable.java
com.rey.material.drawable.RevealDrawable.java
com.rey.material.drawable.RippleDrawable.java
com.rey.material.drawable.ToolbarRippleDrawable.java
com.rey.material.util.ColorUtil.java
com.rey.material.util.ThemeUtil.java
com.rey.material.util.ViewUtil.java
com.rey.material.view.Button.java
com.rey.material.view.CheckBox.java
com.rey.material.view.CheckedTextView.java
com.rey.material.view.CompoundButton.java
com.rey.material.view.EditText.java
com.rey.material.view.FloatingActionButton.java
com.rey.material.view.ListPopupWindow.java
com.rey.material.view.ListView.java
com.rey.material.view.PopupWindow.java
com.rey.material.view.ProgressView.java
com.rey.material.view.RadioButton.java
com.rey.material.view.RippleManager.java
com.rey.material.view.SnackBar.java
com.rey.material.view.Spinner.java
com.rey.material.view.Switch.java
com.rey.material.view.TabPageIndicator.java
com.rey.material.view.TextView.java