1

Я использую настраиваемый вид, чтобы получить эффект пульсации для устройств с предварительным леоптипом. Но мне также нужно настроить форму контейнера как изогнутую форму. Я хочу быть такой кнопкой. I want to be the button like this.
Как вы можете видеть во второй и третьей кнопках, когда мы нажимаем вид, анимация эффекта пульсации выходит за пределы вида контейнера. Итак, как решить это?
Обратите внимание, что я хочу этот эффект пульсации для Kitkat версия с возможностью изменить цвет пульсации. Так это возможно?

Вот мой пользовательский вид, который используется для пульсирующего эффектаAndroid Custom View Edge Clipping с волнистой анимацией

public class MyRippleView extends FrameLayout { 

private int WIDTH; 
private int HEIGHT; 
private int frameRate = 10; 
private int rippleDuration = 400; 
private int rippleAlpha = 90; 
private Handler canvasHandler; 
private float radiusMax = 0; 
private boolean animationRunning = false; 
private int timer = 0; 
private int timerEmpty = 0; 
private int durationEmpty = -1; 
private float x = -1; 
private float y = -1; 
private int zoomDuration; 
private float zoomScale; 
private ScaleAnimation scaleAnimation; 
private Boolean hasToZoom; 
private Boolean isCentered; 
private Integer rippleType; 
private Paint paint; 
private Bitmap originBitmap; 
private int rippleColor; 
private int ripplePadding; 
private GestureDetector gestureDetector; 
private final Runnable runnable = new Runnable() { 
    @Override 
    public void run() { 
     invalidate(); 
    } 
}; 

private OnRippleCompleteListener onCompletionListener; 

public MyRippleView(Context context) { 
    super(context); 
} 

public MyRippleView(Context context, AttributeSet attrs) { 
    super(context, attrs); 
    init(context, attrs); 
} 

public MyRippleView(Context context, AttributeSet attrs, int defStyle) { 
    super(context, attrs, defStyle); 
    init(context, attrs); 
} 

/** 
* Method that initializes all fields and sets listeners 
* 
* @param context Context used to create this view 
* @param attrs Attribute used to initialize fields 
*/ 
private void init(final Context context, final AttributeSet attrs) { 
    if (isInEditMode()) 
     return; 
    final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleView); 
    rippleColor = typedArray.getColor(R.styleable.RippleView_rv_color, getResources().getColor(R.color.rippelColor)); 
    rippleType = typedArray.getInt(R.styleable.RippleView_rv_type, 0); 
    hasToZoom = typedArray.getBoolean(R.styleable.RippleView_rv_zoom, false); 
    isCentered = typedArray.getBoolean(R.styleable.RippleView_rv_centered, false); 
    rippleDuration = typedArray.getInteger(R.styleable.RippleView_rv_rippleDuration, rippleDuration); 
    frameRate = typedArray.getInteger(R.styleable.RippleView_rv_framerate, frameRate); 
    rippleAlpha = typedArray.getInteger(R.styleable.RippleView_rv_alpha, rippleAlpha); 
    ripplePadding = typedArray.getDimensionPixelSize(R.styleable.RippleView_rv_ripplePadding, 0); 
    canvasHandler = new Handler(); 
    zoomScale = typedArray.getFloat(R.styleable.RippleView_rv_zoomScale, 1.03f); 
    zoomDuration = typedArray.getInt(R.styleable.RippleView_rv_zoomDuration, 200); 
    typedArray.recycle(); 
    paint = new Paint(); 
    paint.setAntiAlias(true); 
    paint.setStyle(Paint.Style.FILL); 
    paint.setColor(rippleColor); 
    paint.setAlpha(rippleAlpha); 
    this.setWillNotDraw(false); 

    gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { 
     @Override 
     public void onLongPress(MotionEvent event) { 
      super.onLongPress(event); 
      animateRipple(event); 
      sendClickEvent(true); 
     } 

     @Override 
     public boolean onSingleTapConfirmed(MotionEvent e) { 
      return true; 
     } 

     @Override 
     public boolean onSingleTapUp(MotionEvent e) { 
      return true; 
     } 
    }); 

    this.setDrawingCacheEnabled(true); 
    this.setClickable(true); 
} 

@Override 
public void draw(Canvas canvas) { 
    super.draw(canvas); 
    if (animationRunning) { 
     if (rippleDuration <= timer * frameRate) { 
      animationRunning = false; 
      timer = 0; 
      durationEmpty = -1; 
      timerEmpty = 0; 
      canvas.restore(); 
      invalidate(); 
      if (onCompletionListener != null) onCompletionListener.onComplete(this); 
      return; 
     } else 
      canvasHandler.postDelayed(runnable, frameRate); 

     if (timer == 0) 
      canvas.save(); 


     canvas.drawCircle(x, y, (radiusMax * (((float) timer * frameRate)/rippleDuration)), paint); 

     paint.setColor(Color.parseColor("#ffff4444")); 

     if (rippleType == 1 && originBitmap != null && (((float) timer * frameRate)/rippleDuration) > 0.4f) { 
      if (durationEmpty == -1) 
       durationEmpty = rippleDuration - timer * frameRate; 

      timerEmpty++; 
      final Bitmap tmpBitmap = getCircleBitmap((int) ((radiusMax) * (((float) timerEmpty * frameRate)/(durationEmpty)))); 
      canvas.drawBitmap(tmpBitmap, 0, 0, paint); 
      tmpBitmap.recycle(); 
     } 

     paint.setColor(rippleColor); 

     if (rippleType == 1) { 
      if ((((float) timer * frameRate)/rippleDuration) > 0.6f) 
       paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timerEmpty * frameRate)/(durationEmpty))))); 
      else 
       paint.setAlpha(rippleAlpha); 
     } 
     else 
      paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timer * frameRate)/rippleDuration)))); 

     timer++; 
    } 

} 

@Override 
protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
    super.onSizeChanged(w, h, oldw, oldh); 
    WIDTH = w; 
    HEIGHT = h; 

    scaleAnimation = new ScaleAnimation(1.0f, zoomScale, 1.0f, zoomScale, w/2, h/2); 
    scaleAnimation.setDuration(zoomDuration); 
    scaleAnimation.setRepeatMode(Animation.REVERSE); 
    scaleAnimation.setRepeatCount(1); 
} 

/** 
* Launch Ripple animation for the current view with a MotionEvent 
* 
* @param event MotionEvent registered by the Ripple gesture listener 
*/ 
public void animateRipple(MotionEvent event) { 
    createAnimation(event.getX(), event.getY()); 
} 

/** 
* Launch Ripple animation for the current view centered at x and y position 
* 
* @param x Horizontal position of the ripple center 
* @param y Vertical position of the ripple center 
*/ 
public void animateRipple(final float x, final float y) { 
    createAnimation(x, y); 
} 

/** 
* Create Ripple animation centered at x, y 
* 
* @param x Horizontal position of the ripple center 
* @param y Vertical position of the ripple center 
*/ 
private void createAnimation(final float x, final float y) { 
    if (this.isEnabled() && !animationRunning) { 
     if (hasToZoom) 
      this.startAnimation(scaleAnimation); 

     radiusMax = Math.max(WIDTH, HEIGHT); 

     if (rippleType != 2) 
      radiusMax /= 2; 

     radiusMax -= ripplePadding; 

     if (isCentered || rippleType == 1) { 
      this.x = getMeasuredWidth()/2; 
      this.y = getMeasuredHeight()/2; 
     } else { 
      this.x = x; 
      this.y = y; 
     } 

     animationRunning = true; 

     if (rippleType == 1 && originBitmap == null) 
      originBitmap = getDrawingCache(true); 

     invalidate(); 
    } 
} 

@Override 
public boolean onTouchEvent(MotionEvent event) { 
    if (gestureDetector.onTouchEvent(event)) { 
     animateRipple(event); 
     sendClickEvent(false); 
    } 
    return super.onTouchEvent(event); 
} 

@Override 
public boolean onInterceptTouchEvent(MotionEvent event) { 
    this.onTouchEvent(event); 
    return super.onInterceptTouchEvent(event); 
} 

/** 
* Send a click event if parent view is a Listview instance 
* 
* @param isLongClick Is the event a long click ? 
*/ 
private void sendClickEvent(final Boolean isLongClick) { 
    if (getParent() instanceof AdapterView) { 
     final AdapterView adapterView = (AdapterView) getParent(); 
     final int position = adapterView.getPositionForView(this); 
     final long id = adapterView.getItemIdAtPosition(position); 
     if (isLongClick) { 
      if (adapterView.getOnItemLongClickListener() != null) 
       adapterView.getOnItemLongClickListener().onItemLongClick(adapterView, this, position, id); 
     } else { 
      if (adapterView.getOnItemClickListener() != null) 
       adapterView.getOnItemClickListener().onItemClick(adapterView, this, position, id); 
     } 
    } 
} 

private Bitmap getCircleBitmap(final int radius) { 
    final Bitmap output = Bitmap.createBitmap(originBitmap.getWidth(), originBitmap.getHeight(), Bitmap.Config.ARGB_8888); 
    final Canvas canvas = new Canvas(output); 
    final Paint paint = new Paint(); 
    final Rect rect = new Rect((int)(x - radius), (int)(y - radius), (int)(x + radius), (int)(y + radius)); 

    paint.setAntiAlias(true); 
    canvas.drawARGB(0, 0, 0, 0); 
    canvas.drawCircle(x, y, radius, paint); 

    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 
    canvas.drawBitmap(originBitmap, rect, rect, paint); 

    return output; 
} 

/** 
* Set Ripple color, default is #FFFFFF 
* 
* @param rippleColor New color resource 
*/ 
@ColorRes 
public void setRippleColor(int rippleColor) { 
    this.rippleColor = getResources().getColor(rippleColor); 
} 

public int getRippleColor() { 
    return rippleColor; 
} 

public RippleType getRippleType() 
{ 
    return RippleType.values()[rippleType]; 
} 

/** 
* Set Ripple type, default is RippleType.SIMPLE 
* 
* @param rippleType New Ripple type for next animation 
*/ 
public void setRippleType(final RippleType rippleType) 
{ 
    this.rippleType = rippleType.ordinal(); 
} 

public Boolean isCentered() 
{ 
    return isCentered; 
} 

/** 
* Set if ripple animation has to be centered in its parent view or not, default is False 
* 
* @param isCentered 
*/ 
public void setCentered(final Boolean isCentered) 
{ 
    this.isCentered = isCentered; 
} 

public int getRipplePadding() 
{ 
    return ripplePadding; 
} 

/** 
* Set Ripple padding if you want to avoid some graphic glitch 
* 
* @param ripplePadding New Ripple padding in pixel, default is 0px 
*/ 
public void setRipplePadding(int ripplePadding) 
{ 
    this.ripplePadding = ripplePadding; 
} 

public Boolean isZooming() 
{ 
    return hasToZoom; 
} 

/** 
* At the end of Ripple effect, the child views has to zoom 
* 
* @param hasToZoom Do the child views have to zoom ? default is False 
*/ 
public void setZooming(Boolean hasToZoom) 
{ 
    this.hasToZoom = hasToZoom; 
} 

public float getZoomScale() 
{ 
    return zoomScale; 
} 

/** 
* Scale of the end animation 
* 
* @param zoomScale Value of scale animation, default is 1.03f 
*/ 
public void setZoomScale(float zoomScale) 
{ 
    this.zoomScale = zoomScale; 
} 

public int getZoomDuration() 
{ 
    return zoomDuration; 
} 

/** 
* Duration of the ending animation in ms 
* 
* @param zoomDuration Duration, default is 200ms 
*/ 
public void setZoomDuration(int zoomDuration) 
{ 
    this.zoomDuration = zoomDuration; 
} 

public int getRippleDuration() 
{ 
    return rippleDuration; 
} 

/** 
* Duration of the Ripple animation in ms 
* 
* @param rippleDuration Duration, default is 400ms 
*/ 
public void setRippleDuration(int rippleDuration) 
{ 
    this.rippleDuration = rippleDuration; 
} 

public int getFrameRate() 
{ 
    return frameRate; 
} 

/** 
* Set framerate for Ripple animation 
* 
* @param frameRate New framerate value, default is 10 
*/ 
public void setFrameRate(int frameRate) 
{ 
    this.frameRate = frameRate; 
} 

public int getRippleAlpha() 
{ 
    return rippleAlpha; 
} 

/** 
* Set alpha for ripple effect color 
* 
* @param rippleAlpha Alpha value between 0 and 255, default is 90 
*/ 
public void setRippleAlpha(int rippleAlpha) 
{ 
    this.rippleAlpha = rippleAlpha; 
} 

public void setOnRippleCompleteListener(OnRippleCompleteListener listener) { 
    this.onCompletionListener = listener; 
} 

/** 
* Defines a callback called at the end of the Ripple effect 
*/ 
public interface OnRippleCompleteListener { 
    void onComplete(MyRippleView rippleView); 
} 

public enum RippleType { 
    SIMPLE(0), 
    DOUBLE(1), 
    RECTANGLE(2); 

    int type; 

    RippleType(int type) 
    { 
     this.type = type; 
    } 
} 

}

В файле макета XML

<FrameLayout 
    android:background="@drawable/curved_button" 
    android:layout_width="match_parent" 
    android:layout_height="50dp"> 
    <com.package.MyRippleView 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     app:rv_color="@color/colorAccent" 
     rv_centered="true"> 
    </com.package.MyRippleView> 
</FrameLayout> 

Изогнутая форма

<selector xmlns:android="http://schemas.android.com/apk/res/android" > 
<item > 
    <shape android:shape="rectangle" > 
     <corners android:radius="40dip" /> 
     <stroke android:width="1dp" android:color="#FF9A00" /> 
    </shape> 
</item> 

ответ

0

Возможно. Самый простой способ - использовать Carbon, который делает такие вещи просто так. Я смог воссоздать вашу кнопку, используя только xml и запустил ее на Gingerbread.

enter image description here

<carbon.widget.Button 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:text="Rounded with ripple" 
    android:textColor="@color/carbon_amber_700" 
    app:carbon_cornerRadius="100dp" 
    app:carbon_backgroundTint="@color/carbon_white" 
    app:carbon_rippleColor="#40ff0000" 
    app:carbon_stroke="@color/carbon_amber_700" 
    app:carbon_strokeWidth="2dp" /> 

Недостатком является то, что углерод огромен, и вы не можете включить его только для этой одной кнопки.

Если вы хотите сделать это самостоятельно, вы должны использовать путь и режим PorterDuff, чтобы скопировать вашу кнопку на закругленный прямоугольник.

private float cornerRadius; 
private Path cornersMask; 
private static PorterDuffXfermode pdMode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); 

private void initCorners() { 
     cornersMask = new Path(); 
     cornersMask.addRoundRect(new RectF(0, 0, getWidth(), getHeight()), cornerRadius, cornerRadius, Path.Direction.CW); 
     cornersMask.setFillType(Path.FillType.INVERSE_WINDING); 
} 

@Override 
public void draw(@NonNull Canvas canvas) { 
     int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); 

     super.draw(canvas); 

     paint.setXfermode(pdMode); 
     canvas.drawPath(cornersMask, paint); 

     canvas.restoreToCount(saveCount); 
     paint.setXfermode(null); 
} 

Возможно, вы должны использовать ViewOutlineProvider на Lollipop, чтобы использовать родной материал там, где это возможно.

+0

Спасибо @Zielony за вашу замечательную библиотеку. Но поскольку он огромный, я не могу использовать его в своем проекте. Но можете ли вы указать, как использовать ваши предложения в моем файле MyRippleView. –