I am using the DraggablePanel library (https://github.com/pedrovgs/DraggablePanel) to facilitate a YouTube like video player. If you are not familiar with this feature it basically allows the user to shrink the currently playing video into a small thumbnail that is docked into the corner of the screen.
Unfortunately (and I am not so sure this is an issue with the above library) I have noticed that if I apply an X and Y scale on the parent view of a VideoView, the VideoView itself will not resize its content to match.
See below screenshots, where VideoView is at its natural scale and then with its parent view scaled down to about 0.6:0.6. You should notice that in the scaled screenshot the VideoView has cropped its content instead of resizing to fit.
So I tried to force a width and height on the VideoView as its parent's scale changed. There are a few examples on the internet about overriding the dimensions of a VideoView - but here is my simple version:
public class ScalableVideoView extends VideoView {
private int mVideoWidth;
private int mVideoHeight;
public ScalableVideoView(Context context) {
super(context);
}
public ScalableVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScalableVideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mVideoWidth = 0;
mVideoHeight = 0;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mVideoWidth > 0 && mVideoHeight > 0) {
// If a custom dimension is specified, force it as the measured dimension
setMeasuredDimension(mVideoWidth, mVideoHeight);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void changeVideoSize(int width, int height) {
mVideoWidth = width;
mVideoHeight = height;
getHolder().setFixedSize(width, height);
requestLayout();
invalidate();
}
}
The fragment that contains the VideoView is responsible for calling changeVideoSize on it when it receives a notification from the DraggablePanel library about a change in scale. At which point I calculate a new pair of width and height based on the provided scale values. (scale is from 0f to 1f)
public void setVideoViewScale(float scaleX, float scaleY) {
if (mMaxVideoWidth == 0) {
mMaxVideoWidth = mVideoView.getWidth();
}
if (mMaxVideoHeight == 0) {
mMaxVideoHeight = mVideoView.getHeight();
}
if (mMaxVideoWidth > 0) {
mVideoView.changeVideoSize((int) (scaleX * mMaxVideoWidth),
(int) (scaleY * mMaxVideoHeight));
}
}
Unfortunately this causes some really interesting results. It seems that the Video portion of the VideoView is scaling appropriately - but the bounds of the VideoView seem to be scaling too fast (causing the video to both shrink and crop).
For further demonstration here are two videos:
https://github.com/npike/so_scalevideoview/blob/master/demo_videos/so_videoview_noscale.mp4
https://github.com/npike/so_scalevideoview/blob/master/demo_videos/so_videoview_scale.mp4
I have also uploaded this sample project to GitHub so that you can see the complete code:
https://github.com/npike/so_scalevideoview
I was having the same problem with my app, so I took your example that is much simpler, and tried to fix it.
This is the code that fixes the problem. Edit your changeVideoSize(int width, int height) method to:
public void changeVideoSize(int width, int height){
mVideoWidth = width;
mVideoHeight = height;
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(width, height);
lp.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
lp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
setLayoutParams(lp);
requestLayout();
invalidate();
}
With the invalidation of the view you force the VideoView to redraw the whole View with the new layout parameters.
I believe VideoView does not support scaling. Within the sample app that comes with DraggablePanel, there is a VideoSampleActivity, which demonstrates a draggable video. In order to achieve an effect like scaling, the top_view_resize attribute is set to true within activity_video_sample.xml:
<com.github.pedrovgs.DraggableView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:draggable_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/draggable_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
draggable_view:top_view_id="#+id/video_view"
draggable_view:bottom_view_id="#+id/iv_thumbnail"
draggable_view:top_view_x_scale_factor="#dimen/x_scale_factor"
draggable_view:top_view_y_scale_factor="#dimen/y_scale_factor"
draggable_view:top_view_height="#dimen/top_fragment_height"
draggable_view:top_view_margin_right="#dimen/top_fragment_margin"
draggable_view:top_view_margin_bottom="#dimen/top_fragment_margin"
draggable_view:enable_minimized_horizontal_alpha_effect="false"
draggable_view:top_view_resize="true"
android:background="#color/black">
Setting top_view_resize to true causes the TransformerFactory to return a ResizeTransformer instead of a ScaleTransformer. The ResizeTransformer uses setLayoutParams() to resize the associated View, where the ScaleTransformer applies a scale factor.
Rather than attempting to scale the VideoView, you should be able to set top_view_resize to true.
Note: if you use a DraggablePanel (which supports top and bottom fragments) instead of a DraggableView, you currently do not have access to the top_view_resize attribute of the DraggableView that's nested inside the DraggablePanel. I consider this to be a bug, and I'm hoping I can work with Pedro to expose a top_view_resize attribute on DraggablePanel that will pass its value through to the nested DraggableView. As a result of this bug, DraggablePanel always uses the default Transformer, which is the ScaleTransformer -- which does not work with a VideoView.
Related
I'm using the HorizontalListView provided here, and I'm trying to show custom views with a fixed height and width, like this:
public class MyView extends View {
public MyView(Context context) {
super(context);
init();
}
private void init() {
setBackgroundColor((int) (Math.random() * Integer.MAX_VALUE));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int h = getHeight(); // shows 255, correct height
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.makeMeasureSpec(425, MeasureSpec.EXACTLY);
int h = MeasureSpec.makeMeasureSpec(255, MeasureSpec.EXACTLY);
super.onMeasure(w, h);
}
}
When showing this MyView in a normal LinearLayout, the height and width are perfect. However, when I show the view in the HorizontalListView, the width is perfect, but the height is not. See this screenshot:
The width is 425px, which is correct, the height is only 160px instead of 255.
In the source of the HorizontalListView there is this method:
private void addAndMeasureChild(final View child, int viewPos) {
LayoutParams params = child.getLayoutParams();
if(params == null) {
params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
}
addViewInLayout(child, viewPos, params, true);
child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
}
Is there something I should change in this method, or is there something else to make this work?
When you do int h = MeasureSpec.makeMeasureSpec(255, MeasureSpec.EXACTLY);, that 255 value is a PX value. If you said the height was 200 DP in the XML that value will be adjusted according to the device. Not sure if the screen shown is HDPI or XHDPI (my guess is the latter), it will be multiplied, so your 200 DP becomes 300 PX in an HDPI phone (x1.5). You can either use a PX value in the XML or you can define a dimension DP value that's 200DP in a values.xml.
The preference would be the latter. You can use that dimension value in your XML and retrieve that from the Resources when you create your View. That means that if at some point you want to make it bigger you can just change one value and you're done.
Edit
I see you found the real problem. In any case, you should still use the dimension approach, it will make your code cleaner and it will work in any phone (255 will only look right in a specific combination).
I have a VideoView which is set to fill parent and works fine with full size videos ,I am finding it difficult to find a solution when video size is 4:3 or smaller it keeps to left of my view whereas same application in ios display's it in full screen.
Can we resize the video and show it with same size of that of a Video View?or can content of VideoView resized?
I tried
view.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
but no results
Make a custom video class like this:
public class CustomVideoView extends VideoView {
protected int _overrideWidth = 480;
protected int _overrideHeight = 360;
public CustomVideoView(Context context) {
super(context);
}
public CustomVideoView(Context context, AttributeSet set) {
super(context, set);
}
public void resizeVideo(int width, int height) {
_overrideHeight = height;
_overrideWidth = width;
// not sure whether it is useful or not but safe to do so
getHolder().setFixedSize(width, height);
//getHolder().setSizeFromLayout();
requestLayout();
invalidate(); // very important, so that onMeasure will be triggered
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(_overrideWidth, _overrideHeight);
}
}
In your class use the resizeVideo method with the screen width and height as parameters.
Take in account that all depends on the movie ratio and the screen ratio. If they are the same, than the video is displayed full screen, but when they are different, the video is adjusted on width/height.
I am attempting to dynamically set the size of an Android VideoView. I have looked on StackOverflow as well as the internet; and the best solution I found was from here. I have put my implementation below:
public class ResizableVideoView extends VideoView {
public ResizableVideoView(Context c) {
super(c);
}
private int mVideoWidth = 100;
private int mVideoHeight = 100;
public void changeVideoSize(int width, int height) {
mVideoWidth = width;
mVideoHeight = height;
// not sure whether it is useful or not but safe to do so
getHolder().setFixedSize(width, height);
forceLayout();
invalidate(); // very important, so that onMeasure will be triggered
}
public void onMeasure(int specwidth, int specheight) {
Log.i("onMeasure","On Measure has been called");
setMeasuredDimension(mVideoWidth, mVideoHeight);
}
public void onDraw(Canvas c) {
super.onDraw(c);
Log.i("onDraw","Drawing...");
}
}
The video resizes correctly on the Android emulator as well as on a Motorola Droid X; but on a Motorola Droid, the VideoView resizes but the video playing in the VideoView does not resize. On the Motorola Droid, if the VideoView is set to a larger dimension than the video playing, a black background appears in the VideoView with the video playing in the top left corner of the VideoView on top of the black background.
How does one resize a VideoView properly in Android?
Thanks,
Vance
My implementation works like this:
RelativeLayout.LayoutParams videoviewlp = new RelativeLayout.LayoutParams(newwidth, newheight);
videoviewlp.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
videoviewlp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
videoview.setLayoutParams(videoviewlp);
videoview.invalidate();
By invalidating the videoview, you will force it to redraw the whole videoview using the new LayoutParams (and the new dimensions).
I'm struggling with this video issue for a while. I thought you may have some ideas to help me.
So I'm having this VideoView in a frame layout and on top I have a ToggleButton to make zoom and came back from zoom.
<CustomVideoView
android:id="#+id/video_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:keepScreenOn="true" >
</CustomVideoView>
and I have a video 480x360 and I thought that when making zoom in portrait, I will resize it at the screen height and the calculated width, based on the video ratio.(in landscape otherwise).
I have extended VideoView to CustomVideoView using:
public class CustomVideoView extends VideoView {
protected int _overrideWidth = 480;
protected int _overrideHeight = 360;
public CustomVideoView(Context context) {
super(context);
}
public CustomVideoView(Context context, AttributeSet set) {
super(context, set);
}
public void resizeVideo(int width, int height) {
_overrideHeight = height;
_overrideWidth = width;
// not sure whether it is useful or not but safe to do so
getHolder().setFixedSize(width, height);
requestLayout();
invalidate(); // very important, so that onMeasure will be triggered
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(_overrideWidth, _overrideHeight);
}
}
on some devices the trick works fine, but on Google Nexus with 4.0.3 it stretches the video to the screen and on Galaxy S 2.3.3 it doesn't work at all.
It may be dependent on underlying native code, that is vendor (Samsung in your case) used for SurfaceView (and so on VideoView) realisation. Also MediaPlayer, which is part of VideoView too, can have device-specific realisation (also native), and it can affect all VideoView behavior.
I am working on a subclass of FrameLayout that is supposed to rotate all of its children by 90 degrees. I am doing this to overcome the landscape-only camera limitation present in android 2.1 and below, by having the activity be in landscape, but placing my camera overlay into this framelayout overlay to cause it to appear as if it was portrait (this is how Layar does it) To accomplish this, I'm adapting Jeff Sharkey's code to rotate views. My problem is that I can rotate the Framelayout, but I cannot resize it to match the new dimensions. So on my g1, instead of a 320x480 portrait view over a 480x320 camera view in landscape, I get a 320x320 box in the middle showing my portrait view with the sides chopped off.
Here is my code so far:
public class RotateLayout extends FrameLayout {
private Matrix mForward = new Matrix();
private Matrix mReverse = new Matrix();
private float[] mTemp = new float[2];
public RotateLayout(Context context) {
super(context);
}
public RotateLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/* (non-Javadoc)
* #see android.widget.FrameLayout#onMeasure(int, int)
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//This didn't work:
//super.onMeasure(heightMeasureSpec, widthMeasureSpec);
}
/* (non-Javadoc)
* #see android.widget.FrameLayout#onSizeChanged(int, int, int, int)
*/
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.rotate(270, getWidth()/2, getHeight()/2);
//This code will stretch the canvas to accommodate the new screen size. This is not what I want.
//float scaleX=(float)getHeight()/getWidth();
//float scaleY=(float)getWidth()/getHeight();
//canvas.scale(scaleX, scaleY, getWidth()/2, getHeight()/2);
mForward = canvas.getMatrix();
mForward.invert(mReverse);
canvas.save();
canvas.setMatrix(mForward); //This is the matrix we need to use for proper positioning of touch events
super.dispatchDraw(canvas);
canvas.restore();
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
final float[] temp = mTemp;
temp[0] = event.getX();
temp[1] = event.getY();
mReverse.mapPoints(temp);
event.setLocation(temp[0], temp[1]);
return super.dispatchTouchEvent(event);
}
}
I have tried overriding OnMeasure to switch the X and Y dimensions of the View, but have not been able to get that to work.
Any help you can provide is greatly appreciated.
I had the same problem and managed to solve it.
Instead of rotating each view or the layout by hand, I used a LayoutAnimationController.
First, place a file in /res/anim/ called rotation.xml
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="-90"
android:pivotX="50%"
android:pivotY="50%"
android:duration="0" android:fillAfter="true">
</rotate>
Then, in your Activity's onCreate, do
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.myscreen);
Animation rotateAnim = AnimationUtils.loadAnimation(this, R.anim.rotation);
LayoutAnimationController animController = new LayoutAnimationController(rotateAnim, 0);
FrameLayout layout = (FrameLayout)findViewById(R.id.MyScreen_ContentLayout);
layout.setLayoutAnimation(animController);
}
If you want to rotate elements that lie above your camera preview view (SurfaceHolder), simply place a FrameLayout above the SurfaceHolder, place all your elements in that FrameLayout and call the Layout "MyScreen_ContentLayout". Done.
Hope that helped someone out, took me quite a while to get everything together.
Using API level 11 and later you can use the method setRotation(degreesFloat); to change the rotation of a view programmatically, or you can use the XML attribute android:rotation="" to change it in your XML. There are also methods/attributes for changing only the X or Y values of a view's rotation: Android Docs - View (setRotation).
So nowadays as long as you're using API level 11 or above, you should be able to apply the rotation to a wrapper layout node. However, you probably will also have to change the dimensions of the top-level layout to match the dimensions you desire after the rotation. I.e. if you have a portrait view w/ dimensions 800x1280, you'll have to change them to 1280x800 in order for it to line up after rotating to landscape.
Using this library you can rotate whole view hierarchy https://github.com/rongi/rotate-layout
Like this
This is what has worked for me in general.
private void init() {
setRotation(90f);
}
public YourViewOrViewGroup(final Context context) {
super(context);
init();
}
... (all the View/ViewGroup constructors) ...
#Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
super.onLayout(changed, l, t, r, b);
final int width = getWidth();
final int height = getHeight();
final int offset = Math.abs(width - height) / 2;
setTranslationX(-offset);
setTranslationY(offset);
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
}
What you want to do is swap the width with height and then put the X & Y offsets so that the view becomes full screen after the rotation.
The above is a 'landscape'-rotated version. To achieve a landscape inverted just apply 270-deg rotation. You can either modify code within the snippet or apply the rotation outside in a more generic way, i.e
final YourViewOrViewGroup layout = inflater.inflate(...);
if (currentOrientation.isInverted()) {
layout.setRotation(layout.getRotation + 180f);
}
this way you are able to embed the rotated View/ViewGroup within the xml definition and inflate 'port' and 'land' versions while the screen orientation changes, but this seems out of this topic.
Edit: actually it is much better to defer the offset setting until at least one layout pass is over.
This is due the fact that in my case after first onMeasure() the view would be drawn (before the offsets were set). Eventually it could be experienced as glitching because the view/layout would not get drawn within the final bounds at first.
(updated the snippet)
I think you forgot one line in your onMeasure.
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
Taken from the code for a vertical seekbar here:
How can I get a working vertical SeekBar in Android?
You might need to fiddle with the getMeasuredHeight() to make it the correct size of your screen.
Try turning off children clipping of your view root: call setClipChildren(false) on parent of your RotateLayout and in onMeasure method of your RotateLayout put these lines:
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
I'm having basically the same problem as you and I still haven't tested my solution - I'll do it tomorrow and tell if it is working correctly.