Is it possible to have an animated drawable?

后端 未结 7 1033
野的像风
野的像风 2020-12-28 10:34

Is it possible to create a drawable that has some sort of animation, whether it is a frame by frame animation, rotation, etc, that is defined as a xml drawable and can be re

相关标签:
7条回答
  • 2020-12-28 10:46

    Drawables

    There you go! And this one for RotateDrawable. I believe that from the Doc it should be pretty straitght forward. You can define everything in a xml file and set the background of a view as the drawable xml. /drawable/myrotate.xml -> @drawable/myrotate

    Edit: This is an answer I found here. Drawable Rotating around its center Android

    Edit 2: You are right the RotateDrawable seem broken. I don't know I tried it too. I haven't yet succeded in making it animate. But I did succed to rotate it. You have to use setLevel which will rotate it. Though it doesn't look really useful. I browsed the code and the RotateDrawable doesn't even inflate the animation duration and the current rotation seems strangely use the level as a measure for rotation. I believe you have to use it with a AnimationDrawable but here again. It just crashed for me. I haven't used that feature yet but planned to use it in the future. I browsed the web and the RotateDrawable seems to be very undocumented like almost every Drawable objects.

    0 讨论(0)
  • 2020-12-28 10:50

    Yes! The (undocumented) key, which I discovered by reading the ProgressBar code is that you have to call Drawable.setLevel() in onDraw() in order for the <rotate> thing to have any effect. The ProgressBar works something like this (extra unimportant code omitted):

    The drawable XML:

    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item>
            <rotate
                 android:drawable="@drawable/spinner_48_outer_holo"
                 android:pivotX="50%"
                 android:pivotY="50%"
                 android:fromDegrees="0"
                 android:toDegrees="1080" />
        </item>
        <item>
            <rotate
                 android:drawable="@drawable/spinner_48_inner_holo"
                 android:pivotX="50%"
                 android:pivotY="50%"
                 android:fromDegrees="720"
                 android:toDegrees="0" />
        </item>
    </layer-list>
    

    In onDraw():

        Drawable d = getDrawable();
        if (d != null)
        {
            // Translate canvas so a indeterminate circular progress bar with
            // padding rotates properly in its animation
            canvas.save();
            canvas.translate(getPaddingLeft(), getPaddingTop());
    
            long time = getDrawingTime();
    
            // I'm not sure about the +1.
            float prog = (float)(time % ANIM_PERIOD+1) / (float)ANIM_PERIOD; 
            int level = (int)(MAX_LEVEL * prog);
            d.setLevel(level);
            d.draw(canvas);
    
            canvas.restore();
    
            ViewCompat.postInvalidateOnAnimation(this);
        }
    

    MAX_LEVEL is a constant, and is always 10000 (according to the docs). ANIM_PERIOD is the period of your animation in milliseconds.

    Unfortunately since you need to modify onDraw() you can't just put this drawable in an ImageView since ImageView never changes the drawable level. However you may be able to change the drawable level from outside the ImageView's. ProgressBar (ab)uses an AlphaAnimation to set the level. So you'd do something like this:

    mMyImageView.setImageDrawable(myDrawable);
    
    ObjectAnimator anim = ObjectAnimator.ofInt(myDrawable, "level", 0, MAX_LEVEL);
    anim.setRepeatCount(ObjectAnimator.INFINITE);
    anim.start();
    

    It might work but I haven't tested it.

    Edit

    There is actually an ImageView.setImageLevel() method so it might be as simple as:

    ObjectAnimator anim = ObjectAnimator.ofInt(myImageVew, "ImageLevel", 0, MAX_LEVEL);
    anim.setRepeatCount(ObjectAnimator.INFINITE);
    anim.start();
    
    0 讨论(0)
  • 2020-12-28 10:50

    You can start from studying the ProgressBar2 from API Demos project (it is available as a part of the SDK). Specifically pay attention to R.layout.progressbar_2.

    0 讨论(0)
  • 2020-12-28 10:55

    you can use stateListAnimator

        <ImageView
            android:id="@+id/your_id"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:src="@drawable/your_drawable"
            android:stateListAnimator="@anim/bounce" />
    

    and bounce

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
    
        android:interpolator="@android:anim/bounce">
        <translate
            android:duration="900"
    
            android:fromXDelta="100%p"
            android:toXDelta="0%p" />
    </set>
    
    0 讨论(0)
  • 2020-12-28 11:00

    I take back to life this post just to post my solution with vector drawable:

    So you need 1 vector drawable in drawable resource (@drawable/ic_brush_24dp):

    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
        <group
            android:name="rotation"  <---- target name of animated-vector
            android:pivotX="12.0"
            android:pivotY="12.0"
            android:rotation="0.0">  <--- propertyName of animator
            <path
                android:fillColor="#FF000000"
                android:pathData="M7,14c-1.66,0 -3,1.34 -3,3 0,1.31 -1.16,2 -2,2 0.92,1.22 2.49,2 4,2 2.21,0 4,-1.79 4,-4 0,-1.66 -1.34,-3 -3,-3zM20.71,4.63l-1.34,-1.34c-0.39,-0.39 -1.02,-0.39 -1.41,0L9,12.25 11.75,15l8.96,-8.96c0.39,-0.39 0.39,-1.02 0,-1.41z" />
        </group>
    </vector>
    

    Then you need your animator in animator resource folder (@animator/pendulum)

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <objectAnimator
            android:duration="350"
            android:repeatCount="infinite"
            android:repeatMode="reverse"
            android:propertyName="rotation"
            android:valueFrom="0.0"
            android:valueTo="-90.0" />
    </set>
    

    Then you need your animated-vector in drawable resource (@drawable/test_anim_brush2):

    <?xml version="1.0" encoding="utf-8"?>
    <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/ic_brush_24dp">
        <target
            android:name="rotation"
            android:animation="@animator/pendulum" />
    </animated-vector>
    

    Then you need to extend ImageView (because that's the only way i found to start the animation)

    public class ImageView extends AppCompatImageView{
        public ImageView(Context context){
            super(context);
            init(context, null, 0);
        }
        public ImageView(Context context, AttributeSet attrs){
            super(context, attrs);
            init(context, attrs, 0);
        }
        public ImageView(Context context, AttributeSet attrs, int defStyleAttr){
            super(context, attrs, defStyleAttr);
            init(context, attrs, 0);
        }
        private void init(Context context, AttributeSet attrs, int defStyleAttr){
    
        }
    @Override
    protected void onAttachedToWindow(){
        super.onAttachedToWindow();
        Drawable d = getBackground();
        if(d instanceof AnimatedVectorDrawable){
            AnimatedVectorDrawable anim = (AnimatedVectorDrawable)d;
            anim.start(); 
        }        
    }
    @Override
    protected void onDetachedFromWindow(){
        super.onDetachedFromWindow();
        Drawable d = getBackground();
        if(d instanceof AnimatedVectorDrawable){
            AnimatedVectorDrawable anim = (AnimatedVectorDrawable)d;
            anim.stop();
        }
    }
    }
    

    And then add your imageView to your layout :

        <ui.component.ImageView
        android:id="@+id/test_anim_brush"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_gravity="center_horizontal"
        android:background="@drawable/test_anim_brush2"/>
    

    Pro :

    • You can fully define your animation in xml with object animator (duration, interpolator, etc...)
    • Works with everything which accept drawable (as long as you add at good place the start animator)

    Con:

    • Works only with vector
    • Still need to add your custom start by extending class or whenever you think it is smart...
    0 讨论(0)
  • 2020-12-28 11:04

    Here is one of possible ways (especially useful when you have a Drawable somewhere set and need to animate it). The idea is to wrap the drawable and decorate it with animation. In my case, I had to rotate it, so below you can find sample implementation:

    public class RotatableDrawable extends DrawableWrapper {
    
        private float rotation;
        private Rect bounds;
        private ObjectAnimator animator;
        private long defaultAnimationDuration;
    
        public RotatableDrawable(Resources resources, Drawable drawable) {
            super(vectorToBitmapDrawableIfNeeded(resources, drawable));
            bounds = new Rect();
            defaultAnimationDuration = resources.getInteger(android.R.integer.config_mediumAnimTime);
        }
    
        @Override
        public void draw(Canvas canvas) {
            copyBounds(bounds);
            canvas.save();
            canvas.rotate(rotation, bounds.centerX(), bounds.centerY());
            super.draw(canvas);
            canvas.restore();
        }
    
        public void rotate(float degrees) {
            rotate(degrees, defaultAnimationDuration);
        }
    
        public void rotate(float degrees, long millis) {
            if (null != animator && animator.isStarted()) {
                animator.end();
            } else if (null == animator) {
                animator = ObjectAnimator.ofFloat(this, "rotation", 0, 0);
                animator.setInterpolator(new AccelerateDecelerateInterpolator());
            }
            animator.setFloatValues(rotation, degrees);
            animator.setDuration(millis).start();
        }
    
        @AnimatorSetter
        public void setRotation(float degrees) {
            this.rotation = degrees % 360;
            invalidateSelf();
        }
    
        /**
         * Workaround for issues related to vector drawables rotation and scaling:
         * https://code.google.com/p/android/issues/detail?id=192413
         * https://code.google.com/p/android/issues/detail?id=208453
         */
        private static Drawable vectorToBitmapDrawableIfNeeded(Resources resources, Drawable drawable) {
            if (drawable instanceof VectorDrawable) {
                Bitmap b = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
                Canvas c = new Canvas(b);
                drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
                drawable.draw(c);
                drawable = new BitmapDrawable(resources, b);
            }
            return drawable;
        }
    }
    

    and you can use it like this (rotating toolbar navigation icon 360 degrees):

    backIcon = new RotatableDrawable(getResources(), toolbar.getNavigationIcon().mutate());
    toolbar.setNavigationIcon(backIcon);
    backIcon.rotate(360);
    

    It shouldn't be hard to add a method that will rotate it indefinite (setRepeatMode INFINITE for animator)

    0 讨论(0)
提交回复
热议问题