带有波纹动画的Android Custom View Edge Clipping [英] Android Custom View Edge Clipping with ripple animation

查看:89
本文介绍了带有波纹动画的Android Custom View Edge Clipping的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用自定义视图来获取棒棒糖前置设备的波纹效果.但是我还需要自定义容器形状,例如弯曲的形状,我想成为这样的按钮.
正如您在第二个和第三个按钮中看到的那样,当我们点击该视图时,波纹效果动画将超出容器视图的范围.那么如何解决呢?
请注意,我希望 Kitkat 版本具有这种波纹效果,并且能够更改波纹颜色.那有可能吗?

这是我的自定义视图,用于波纹效果

I'm using a custom view to get ripple effect for the pre-lollipop devices. But I also need to customize the container shape like a curved shape.I want to be the button like this.
As you can see in the second and third buttons when we tap the view the ripple effect animation goes outside of the container view. So how to resolve this?
Please note that I want this ripple effect for the Kitkat version with the ability to change the ripple color. So is this possible?

Here is my custom view which is used for the ripple effect

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>

推荐答案

有可能.最简单的方法是使用 Carbon ,它就是这样做的.我能够仅使用xml重新创建您的按钮,然后在Gingerbread上运行它.

It's possible. The easiest way is to use Carbon which does such things just like that. I was able to recreate your button using only xml and run it on Gingerbread.

<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" />

缺点是Carbon很大,您可能不希望只为那个按钮添加它.

The downside is that Carbon is huge and you may not want to include it just for that one button.

如果您希望自己做到这一点,则应使用路径和PorterDuff模式将按钮剪切到圆角的矩形.

If you wish to do that by yourself, you should use a path and a PorterDuff mode to clip your button to a rounded rect.

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

您可能应该在Lollipop上使用ViewOutlineProvider来尽可能使用本机内容.

And you should probably use ViewOutlineProvider on Lollipop to use native stuff where possible.

这篇关于带有波纹动画的Android Custom View Edge Clipping的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆