【问题标题】:android: how to animate-translate single layer of a LayerDrawableandroid:如何动画翻译 LayerDrawable 的单层
【发布时间】:2016-10-06 15:27:21
【问题描述】:

我已经浏览了 android 动画类,但我不确定我是否看到了我正在寻找的东西。我想知道是否可以将平移动画(更改 X 坐标)添加到 LayerDrawable 的单层?我找到了 TranslateAnimation 类,但它似乎只适用于整个 ImageView,我只想为我的 LayerDrawable 的一个图层设置动画。有什么建议吗?提前致谢。

【问题讨论】:

    标签: android animation


    【解决方案1】:

    简而言之:可以使用ValueAnimator 来调整可绘制对象的边界

    更详细: 假设您有一个可绘制的图层列表,其中包含可绘制的内部项目 (id:innerRed),并假设您的布局中有一个 Button (id:btnWithDrawableBg),并且您已将此分层可绘制对象分配给 background 属性,您可以使用ValueAnimator 来调整该drawable 的边界,如下例所示(将x 位置每边100 乒乓球):

    pos_animator.xml

    <?xml version="1.0" encoding="utf-8"?>
    <animator xmlns:andoird="http://schemas.android.com/apk/res/android"
      andoird:repeatCount="infinite"
      andoird:repeatMode="reverse"
      andoird:valueFrom="-100"
      andoird:valueTo="100"
      andoird:valueType="intType"
    />
    

    MyActivity.java

     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // our button with the drawable background
        Button btnWithBg = (Button) findViewById(R.id.btnWithDrawableBg);
        // the layered drawable
        final LayerDrawable layerDrawable = (LayerDrawable) btnWithBg.getBackground();
        // internal layer (item) drawable with id:innerRed
        final GradientDrawable innerRedShape = (GradientDrawable)layerDrawable.findDrawableByLayerId(R.id.innerRed);
        // our animator based on the xml above
        ValueAnimator posAnim = (ValueAnimator) AnimatorInflater.loadAnimator(
                mainLayout.getContext(), R.animator.pos_animator);
    
        posAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // get current offset value and adjust drawable bounds
                int value = (Integer)animation.getAnimatedValue();
                Rect bounds = innerRedShape.copyBounds();//see Note below
                // here only manipulating x value
                bounds.offset(value, 0);
                innerRedShape.setBounds(bounds);
            }
        });
        posAnim.setTarget(innerRedShape);
        posAnim.start();
    }
    

    注意:基于此 SO postcopyBounds() 是必需的(而不仅仅是 getBounds().offset()

    【讨论】:

      【解决方案2】:

      我已经找到了答案……似乎一旦组成了 LayerDrawable,您就真的无法操纵图层了。由于缺乏回应,我认为我的问题可能是问错了问题,我重新设计为使用 LayerDrawable 但将动画作为单独的可绘制/图像视图对象,我在每个动画之前和之后生成和销毁这些对象。然后我在 imageView 上使用 ObjectAnimator 来实现所需的图像翻译。因此,以防万一有人读到这篇文章想要为其 LayerDrawable 的单个图层设置动画,请尝试重新设计以便您可以使用 Animator 类。

      【讨论】:

        【解决方案3】:

        我是这样做的:

        onCreate()

        ImageView imageView = (ImageView) findViewById(R.id.imageView);
        
        Resources r = getResources();
        
        Drawable[] layers = new Drawable[2];
        
        layers[0] = r.getDrawable(R.drawable.your_background_res);
        
        PulseDrawable pulseDrawable = new PulseDrawable(Color.WHITE);
        layers[1] = pulseDrawable;
        
        LayerDrawable layerDrawable = new LayerDrawable(layers);
        
        // This will adjust X & Y of layer, we have to pass layer index (250 pixels from Left and 150 pixels from Right)
        layerDrawable.setLayerInset(1, 250, 150, 0, 0);
        
        imageView.setImageDrawable(layerDrawable);
        

        PulseDrawable.java

        import android.animation.AnimatorSet;
        import android.animation.ValueAnimator;
        import android.graphics.Canvas;
        import android.graphics.Color;
        import android.graphics.ColorFilter;
        import android.graphics.Paint;
        import android.graphics.RadialGradient;
        import android.graphics.Rect;
        import android.graphics.Shader;
        import android.graphics.drawable.Drawable;
        import android.view.animation.DecelerateInterpolator;
        
        public class PulseDrawable extends Drawable {
            private final static float CENTER_AREA_SIZE = 0.6f;
            private final static int PULSE_START_COLOR_OPACITY = 0;
            private final static float MINIMUM_RADIUS = 0;
            private final static int ANIMATION_DURATION_IN_MS = 1500;
            private final int color;
            private Paint centerPaint;
            private Paint pulsePaint;
            private float fullSizeRadius = 60;
            private float currentExpandAnimationValue = 0f;
            private int currentAlphaAnimationValue = 255;
        
            public PulseDrawable(int color) {
                this.color = color;
                initializeDrawable();
            }
        
            private void initializeDrawable() {
                preparePaints();
                prepareAnimation();
            }
        
            private void prepareAnimation() {
                final ValueAnimator expandAnimator = ValueAnimator.ofFloat(0f, 1f);
                expandAnimator.setRepeatCount(ValueAnimator.INFINITE);
                expandAnimator.setRepeatMode(ValueAnimator.RESTART);
                expandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        currentExpandAnimationValue = (float) animation.getAnimatedValue();
                        if (currentExpandAnimationValue == 0f) {
                            currentAlphaAnimationValue = 255;
                        }
                        invalidateSelf();
                    }
                });
                final ValueAnimator alphaAnimator = ValueAnimator.ofInt(255, 0);
                alphaAnimator.setStartDelay(ANIMATION_DURATION_IN_MS / 4);
                alphaAnimator.setRepeatCount(ValueAnimator.INFINITE);
                alphaAnimator.setRepeatMode(ValueAnimator.RESTART);
                alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        currentAlphaAnimationValue = (int) animation.getAnimatedValue();
                    }
                });
                AnimatorSet animation = new AnimatorSet();
                animation.playTogether(expandAnimator, alphaAnimator);
                animation.setDuration(ANIMATION_DURATION_IN_MS);
                animation.setInterpolator(new DecelerateInterpolator());
                animation.start();
            }
        
            private void preparePaints() {
                pulsePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                pulsePaint.setStyle(Paint.Style.FILL);
                centerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                centerPaint.setStyle(Paint.Style.FILL);
                centerPaint.setColor(color);
            }
        
            @Override
            public void setAlpha(int alpha) {
                pulsePaint.setAlpha(alpha);
            }
        
            @Override
            public void setColorFilter(ColorFilter colorFilter) { }
        
            @Override
            public int getOpacity() {
                return pulsePaint.getAlpha();
            }
        
            @Override
            public void draw(Canvas canvas) {
                Rect bounds = getBounds();
        
                //TODO - If we want to draw circle on center of canvas use below code
        
                /*float centerX = bounds.exactCenterX();
                float centerY = bounds.exactCenterY();*/
        
                float startX = bounds.left;
                float startY = bounds.top;
        
        //        calculateFullSizeRadius();
                preparePaintShader();
                renderPulse(canvas, startX, startY);
                renderCenterArea(canvas, startX, startY);
            }
        
            private void renderPulse(Canvas canvas, float centerX, float centerY) {
                float currentRadius = fullSizeRadius * currentExpandAnimationValue;
                if (currentRadius > MINIMUM_RADIUS) {
                    canvas.drawCircle(centerX, centerY, currentRadius, pulsePaint);
                }
            }
        
            private void renderCenterArea(Canvas canvas, float centerX, float centerY) {
                float currentCenterAreaRadius = fullSizeRadius * CENTER_AREA_SIZE;
                if (currentCenterAreaRadius > MINIMUM_RADIUS) {
                    canvas.save();
                    float left = centerX - currentCenterAreaRadius;
                    float top = centerY - currentCenterAreaRadius;
                    float right = centerX + currentCenterAreaRadius;
                    float bottom = centerY + currentCenterAreaRadius;
                    canvas.clipRect(left, top, right, bottom);
                    canvas.drawCircle(centerX, centerY, currentCenterAreaRadius, centerPaint);
                    canvas.restore();
                }
            }
            private void preparePaintShader() {
                Rect bounds = getBounds();
                float centerX = bounds.exactCenterX();
                float centerY = bounds.exactCenterY();
                float radius = (Math.min(bounds.width(), bounds.height()) / 2);
                if (radius > MINIMUM_RADIUS) {
                    int edgeColor = getPulseColor();
                    int centerColor = Color.argb(PULSE_START_COLOR_OPACITY, Color.red(color),
                            Color.green(color),
                            Color.blue(color));
                    pulsePaint.setShader(new RadialGradient(centerX, centerY, radius,
                            centerColor, edgeColor, Shader.TileMode.CLAMP));
                } else {
                    pulsePaint.setShader(null);
                }
            }
        
            private int getPulseColor() {
                return Color.argb(currentAlphaAnimationValue, Color.red(color),
                        Color.green(color),
                        Color.blue(color));
            }
        
            private void calculateFullSizeRadius() {
                Rect bounds = getBounds();
                float minimumDiameter = Math.min(bounds.width(), bounds.height());
                fullSizeRadius = (minimumDiameter / 2);
            }
        }
        

        输出

        注意:你可以添加多个图层,为了演示我添加了两个图层。

        希望这会对你有所帮助。

        【讨论】:

        • 什么是imageView?只有静圈与脉搏?什么是 your_background_res ?静态圈子?