Elliptica Elliptica - 3 months ago 25
Android Question

How Can I Animate Changing the Size of a VideoView?

I have a VideoView that I want to change the size of dynamically in my app. By extending the VideoView class, I have successfully gotten both the video and videoView to change size correctly. However, I'd like to be able to transition between the two sizes more gradually. How can I do that? I've tried a scaling animation, but while that changes the VideoView layout's size, the video itself does not scale. Thoughts?

Here is my video class:

public class MyVideoView extends VideoView {

private int mForceHeight,mForceWidth;
private int mOrigWidth, mOrigHeight, mMinWidth, mMinHeight;


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

/* Will cause inflator errors if not present */
public MyVideoView(Context context, AttributeSet attrs){
super(context, attrs);
}

public void setDimensions(int w, int h) {
this.mForceHeight = h;
this.mForceWidth = w;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

if (mForceHeight != 0)
setMeasuredDimension(mForceWidth, mForceHeight);
else
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}


public void setOrigDimens(int width, int height) {
mOrigWidth = width;
mOrigHeight = height;

mMinWidth = width/4; // My own decision for the small size
mMinHeight = height/4;
}


public void setSmallView() {
setNewViewSize(mMinWidth, mMinHeight);
}


public void setNormalView() {
setNewViewSize(mOrigWidth, mOrigHeight);
}


/* RESIZES THE VIEW */
public void setNewViewSize(int width, int height) {
mForceWidth = width;
mForceHeight = height;
setDimensions(width, height);
getHolder().setFixedSize(width, height);
}
}


Here is the scaling code I tried:

Animation scaling = new ScaleAnimation(1.0f, 0.2f, 1.0f, 0.2f);
scaling.setDuration(2000);
startAnimation(scaling);


Any help is much appreciated!

Answer

The best answer is to simply use a TextureView instead of a SurfaceView (a VideoView inherits from a SurfaceView by default). To do this, use code as follows:

Define your TextureView in activity_main.xml:

<TextureView
    android:id="@+id/videoTexture"
    android:layout_width="match_parent"
    android:layout_height="match_parent />

In your MainActivity.java, declare the following variables:

private MediaPlayer mMediaPlayer;
private TextureView mVideoTextureView;
private float mDisplayWidth;
private float mDisplayHeight;

Then in your onCreate() method, initialize them as follows:

mVideoTextureView =(TextureView) rootview.findViewById(R.id.videoTexture);
mVideoTextureView.setSurfaceTextureListener(this);

mMediaPlayer = new MediaPlayer();
loadNewMovie();

To load a movie into the MediaPlayer, use the following code:

private void loadNewMovie() {

    AssetFileDescriptor afd = this.getResources().openRawResourceFd(ID_OF_YOUR_MOVIE);

    try {

        // Set source

        mMediaPlayer.reset();
        mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
        mMediaPlayer.prepare();

        /*
        // Gets the Height/Width of the video but does NOT include space
        // taken up by black bars if the dimensions don't exactly fit the screen.

        MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
        metaRetriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
        String height = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
        String width = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
        mVideoHeight = Float.parseFloat(height);
        mVideoWidth = Float.parseFloat(width);
        */

        // Gets the size of the display in pixels (used for scaling)

        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);

        mDisplayWidth = size.x;
        mDisplayHeight = size.y;

        // Play movie

        if (currState == State.PLAYING)
            mMediaPlayer.start();

        afd.close();
    } 
    catch (Exception e) {
        Log.e("ERROR", "loadNewMovie: " + e.getMessage(), e);
    }

    mMediaPlayer.seekTo(DEFAULT_VIDEO_INIT_POSITION);
}

Finally, your video can be adjusted instantaneously using the following code:

private void updateTextureViewSize(int viewWidth, int viewHeight) {

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(viewWidth,viewHeight);

    // ANY OTHER RULES...EXAMPLE:
    // params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);

    mVideoTextureView.setLayoutParams(params);
}

Or you can animate the change like this:

private void animateTextureViewScaling(final float endScaleX, final float endScaleY, int duration, final boolean scaleDown) {

    // Note: Can't just use .scaleX and .scaleY directly because it will only scale the video, not it's holder

    mVideoTextureView.animate().setDuration(duration)
        .setUpdateListener(new AnimatorUpdateListener() {

            float value, scalingX, scalingY;
            float changeXScale = 1 - endScaleX;
            float changeYScale = 1 - endScaleY;

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                value = (Float) animation.getAnimatedValue();

                if (scaleDown) {
                    scalingX = (float) (1 - changeXScale*value);
                    scalingY = (float) (1 - changeYScale*value);
                }
                else {
                    scalingX = (float) (changeXScale*value);
                    scalingY = (float) (changeYScale*value);
                }

                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                        (int) (mDisplayWidth*scalingX), (int) (mDisplayHeight*scalingY));

                // ANY OTHER RULES...EXAMPLE:
                // params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);

                mVideoTextureView.setLayoutParams(params);
            }

        }).start();
}

In the above code, I use mDisplayWidth (set in loadNewVideo()) as my video's original size. You can use whatever you want.

The benefits of this are that a TextureView can be animated, transformed, and scaled (a SurfaceView can't).

The cons are that a TextureView can only be used in a hardware accelerated window and will use much more memory (about 30%) more than a SurfaceView. It may also experience a 1 to 3 frame latency.