首页 > 解决方案 > 位图而不是视图上的波纹动画

问题描述

我一直在使用位图在视图中渲染小的彩色圆圈。现在我想在点击时在这些圆圈周围显示自定义涟漪动画。

在通过 StackOverflow 的答案搜索了一段时间后,主要建议将这些位图包装在 VIEW 中,并使用 Android 的动画框架将动画应用于这些视图。

但问题是,我已经使用该视图的画布在视图中绘制这些位图

canvas.drawBitmap()

功能。我只想在单击时显示以这些位图为中心的涟漪。我的问题是,无论如何我可以为位图提供波纹或任何动画而不将它们包装为视图?

protected void fill(ILineDataSet set, boolean drawCircleHole,
        boolean drawTransparentCircleHole, int selectedEntryIndex) {

        int colorCount = set.getCircleColorCount();
        int holeColorCount = set.getCircleHoleColorCount();
        circleRadius = set.getCircleRadius();
        circleHoleRadius = set.getCircleHoleRadius();

        for (int i = 0; i < (colorCount > holeColorCount ? colorCount: holeColorCount); i++) {

            Bitmap.Config conf = Bitmap.Config.ARGB_4444;
            Bitmap circleBitmap = Bitmap.createBitmap((int) (circleRadius * 2.1),
                (int) (circleRadius * 2.1), conf);

            Canvas canvas = new Canvas(circleBitmap);
            circleBitmaps[i] = circleBitmap;

            //fill colors in the values' circles
            mRenderPaint.setColor(set.getCircleColor(i < colorCount ? i : 0));
            mCirclePaintInner.setColor(set.getCircleHoleColor(i < holeColorCount ? i : 0));

            if (drawTransparentCircleHole) {
                // Begin path for circle with hole
                mCirclePathBuffer.reset();

                mCirclePathBuffer.addCircle(
                        circleRadius,
                        circleRadius,
                        circleRadius,
                        Path.Direction.CW);

                // Cut hole in path
                mCirclePathBuffer.addCircle(
                        circleRadius,
                        circleRadius,
                        circleHoleRadius,
                        Path.Direction.CCW);

                // Fill in-between
                canvas.drawPath(mCirclePathBuffer, mRenderPaint);
            } else {

                canvas.drawCircle(
                        circleRadius,
                        circleRadius,
                        circleRadius,
                        mRenderPaint);

                if (drawCircleHole) {
                    canvas.drawCircle(
                            circleRadius,
                            circleRadius,
                            circleHoleRadius,
                            mCirclePaintInner);
                }
            }

            if (i == selectedEntryIndex) {
                circleBitmaps[i] = getScaledUpBitmap(circleBitmaps[i]);
            }
        }
    }

    private Bitmap getScaledUpBitmap(Bitmap bm) {
        //scale bitmap as twice its size
        int width = bm.getWidth();
        int height = bm.getHeight();

        Matrix matrix = new Matrix();
        matrix.postScale(1.5f, 1.5f);

        Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
        bm.recycle();
        return resizedBitmap;
    }

标签: javaandroidandroid-layoutandroid-animationandroid-bitmap

解决方案


Android Ripple 动画是增长动画 + 淡出动画的组合。换句话说,这是一个绘图过程,因此不建议直接在位图上进行。

我写了一个类,它是一个可以用作触摸面板的布局。这种名为 的布局RippleLayout是从 扩展而来的RelativeLayout。您可以使用此布局来绘制圆圈并管理onTouchListener事件以在给定点上显示波纹动画。

RippleLayout.java

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

import java.util.ArrayList;
import java.util.List;

public class RippleLayout extends RelativeLayout {

    private AnimatorSet mAnimatorSet;
    private Paint mPaint;
    private boolean mStarted;
    private float mX, mY;
    private int mCount = 1;
    private int mDuration = 300;
    private float mRadius = 300;
    private IntRippleView rippleView;

    public RippleLayout(Context context) {
        this(context, null, 0);
    }

    public RippleLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RippleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void build() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.GRAY);
        List<Animator> animators = new ArrayList<>();
        rippleView = new IntRippleView(getContext());
        rippleView.setCircleRadius(0);
        rippleView.setCircleAlpha(1);
        addView(rippleView);

        ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(rippleView, "CircleRadius", 0f, 1f);
        scaleXAnimator.setRepeatCount(0);
        animators.add(scaleXAnimator);

        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(rippleView, "CircleAlpha", 1f, 0f);
        alphaAnimator.setRepeatCount(0);
        animators.add(alphaAnimator);

        mAnimatorSet = new AnimatorSet();
        mAnimatorSet.setInterpolator(new LinearInterpolator());
        mAnimatorSet.playTogether(animators);
        mAnimatorSet.setDuration(mDuration);
        mAnimatorSet.addListener(mAnimatorListener);
    }

    public void doRipple(float x, float y) {
        doRipple(x, y, mRadius, mPaint.getColor());
    }

    public synchronized void doRipple(float x, float y, float radius, int acolor) {
        if (mStarted)
            mAnimatorSet.end();
        mX = x;
        mY = y;
        mRadius = radius;
        rippleView.bringToFront();
        mPaint.setColor(acolor);
        mAnimatorSet.start();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        build();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAnimatorSet != null) {
            mAnimatorSet.cancel();
            mAnimatorSet = null;
        }
        mPaint = null;
    }

    private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {

        @Override
        public void onAnimationStart(Animator animator) {
            mStarted = true;
        }

        @Override
        public void onAnimationEnd(Animator animator) {
            mStarted = false;
        }

        @Override
        public void onAnimationCancel(Animator animator) {
            mStarted = false;
        }

        @Override
        public void onAnimationRepeat(Animator animator) {
        }

    };

    private class IntRippleView extends View {

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

        private float CircleRadius = 0;

        public void setCircleRadius(float value) {
            CircleRadius = value;
            invalidate();
        }

        public float getCircleRadius() {
            return CircleRadius;
        }

        private float CircleAlpha = 1;

        public void setCircleAlpha(float value) {
            CircleAlpha = value;
            invalidate();
        }

        public float getCircleAlpha() {
            return CircleAlpha;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            int aColor = Color.RED;
            mPaint.setAlpha((int)(255 * CircleAlpha));
            canvas.drawCircle(mX, mY, mRadius * CircleRadius, mPaint);
        }

    }

}

如何使用

在 XML 布局中

<android.support.constraint.ConstraintLayout
    android:id="@+id/mainlayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="0dp"
    >

    <com.mcblau.pacerblue.components.RippleLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
    >

    </com.mcblau.pacerblue.components.RippleLayout>


</android.support.constraint.ConstraintLayout>

在您的活动中

    container = findViewById(R.id.container);
    container.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // detect here which circle was touch
            // get the center of the circle => mCenterX, mCenterY
            container.doRipple(mCenterX, mCenterY); // ripple animation 
            return true;
        }
    });

您可以直接在 RippleLayout 或另一个上绘制圆圈。如果你把它RippleLayout放在你的位图布局上,你可以把它用作触摸面板。在这种情况下,请使用setOnTouchListener您圈子的布局。

使用doRipple(x, y);doRipple(x, y, radius, acolor);将圆半径设置为波纹颜色。


推荐阅读