I'm working on a custom view that's giving me unexpected results. What I'm trying to make happen is to fill a picture the width of the view and scale it keeping its aspect ratio, but it's doing something weird...
Here's what my generated log is spitting out.
View size: 768x942
Pic is portrait
Original Size: 960x1280 Scaled: 768x768
Scaled bitmap: 768x768
Now let me explain whats the log is saying according to the code. First we let the view onMeasure itself and once it's done that we're allowed to grab the width and height of the view. Next we check if the picture is landscape or portrait. Then we just do the math to find the size we need to scale to. After the math is done we create a new scaled bitmap with the results. The width is right but the height should be 1024 not 768. I can't see where its messing up.
public void setBitmap(Bitmap bmp) {
this.mOriginalBitmap = bmp;
if(mHasMeasured) {
//Make sure the view has measured itself so we can grab the width and height
Log.d("", "View size: " + String.valueOf(this.mViewWidth)
+ "x" + String.valueOf(this.mViewHeight));
int reqWidth, reqHeight; //The required sizes we need
//Get the new sizes for the pic to fit in the view and keep aspect ratio
if(this.mOriginalBitmap.getWidth() > this.mOriginalBitmap.getHeight()) {
//Landscape :/
Log.d("", "Pic is Landscape");
reqHeight = this.mViewHeight;
reqWidth = (this.mOriginalBitmap.getWidth()
/ this.mOriginalBitmap.getHeight()) * reqHeight;
} else {
//Portrait :)
Log.d("", "Pic is portrait");
reqWidth = this.mViewWidth;
reqHeight = (this.mOriginalBitmap.getHeight()
/ this.mOriginalBitmap.getWidth()) * reqWidth;
}
Log.d("", "Original Size: "
+ String.valueOf(mOriginalBitmap.getWidth()) + "x"
+ String.valueOf(mOriginalBitmap.getHeight())
+ " Scaled: " + String.valueOf(reqWidth)
+ "x" + String.valueOf(reqHeight) );
this.mBmpScaledForView = Bitmap.createScaledBitmap(mOriginalBitmap,
reqWidth, reqHeight, false);
this.mSrc.top = 0;
this.mSrc.left = 0;
this.mSrc.right = this.mBmpScaledForView.getWidth();
this.mSrc.bottom = this.mBmpScaledForView.getHeight();
this.mDst = this.mSrc;
Log.d("", "Scaled bitmap : "
+ String.valueOf(this.mBmpScaledForView.getWidth())
+ "x" + String.valueOf(this.mBmpScaledForView.getHeight()));
}
}
The issue here is with the image ratio computation. Bitmap.getHeight() and getWidth() return ints, which makes the result of
(this.mOriginalBitmap.getHeight()
/ this.mOriginalBitmap.getWidth())
be 1 for an image that is 960x1280.
There are several options around this. You can make a float division by converting the return value of getHeight() and getWidth():
((float) this.mOriginalBitmap.getHeight()
/ (float) this.mOriginalBitmap.getWidth())
Or you can simply start by the multiplication :
reqHeight = (reqWidth * this.mOriginalBitmap.getHeight()) / this.mOriginalBitmap.getWidth();
package com.riktamtech.app.utils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* ImageView that keeps aspect ratio when scaled
*/
public class CustomImageView extends ImageView {
public CustomImageView(Context context) {
super(context);
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
Drawable drawable = getDrawable();
if (drawable == null) {
setMeasuredDimension(0, 0);
} else {
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
if (measuredHeight == 0 && measuredWidth == 0) { // Height and
// width set
// to
// wrap_content
setMeasuredDimension(measuredWidth, measuredHeight);
} else if (measuredHeight == 0) { // Height set to wrap_content
int width = measuredWidth;
int height = width * drawable.getIntrinsicHeight()
/ drawable.getIntrinsicWidth();
setMeasuredDimension(width, height);
} else if (measuredWidth == 0) { // Width set to wrap_content
int height = measuredHeight;
int width = height * drawable.getIntrinsicWidth()
/ drawable.getIntrinsicHeight();
setMeasuredDimension(width, height);
} else { // Width and height are explicitly set (either to
// match_parent or to exact value)
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
} catch (Exception e) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
Related
I'm building a custom camera using the new camera2 API. My code is based on the code sample provided by Google here.
I can't find a way to get the camera preview in full screen. In the code sample, they use ratio optimization to adapt to all screens but it's only taking around 3/4 of the screen's height.
Here is my code of AutoFitTextureView :
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* #param width Relative horizontal size
* #param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
Thank you very much for your help.
You should change measured width and height to cover full screen, not to fit the screen as below.
From:
if (width < height * mRatioWidth / mRatioHeight)
to
if (width > height * mRatioWidth / mRatioHeight)
It worked fine for me.
this is the solution for your problem. In this line the aspect ratio is set to 3/4. I changed chooseVideSize method to pick video size with hd resolution for MediaRecorder.
private static Size chooseVideoSize(Size[] choices) {
for (Size size : choices) {
// Note that it will pick only HD video size, you should create more robust solution depending on screen size and available video sizes
if (1920 == size.getWidth() && 1080 == size.getHeight()) {
return size;
}
}
Log.e(TAG, "Couldn't find any suitable video size");
return choices[choices.length - 1];
}
Then I corrected this method to pick preview size accordingly to video size aspect ratio and below is result.
private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<Size>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
double ratio = (double) h / w;
for (Size option : choices) {
double optionRatio = (double) option.getHeight() / option.getWidth();
if (ratio == optionRatio) {
bigEnough.add(option);
}
}
// Pick the smallest of those, assuming we found any
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[1];
}
}
I hope it will help you!
I'm working with camera2 and I'm showing a preview of my photo/video after longclick in my thumbnail. Also, I'm rotating it depending of which orientation the camera had when the picture was taken. For example, if I did a picture in 90º, my preview will be also rotated 90º.
Everything is working fine, I'm using a customContainer and there I'm using onLayout and OnMeasure to create my preview depending of the size of the screen, aspect ratio and orientation. It works fine with photos. My problem appear when I try to do the same with videos, they only work in 0º.
I tried to rotate the TextureView which contain my MediaPlayer but after this my onLayout become crazy and Itś impossible find a (l,t,r,b) combination to measure it correctly.
Here is my XML:
<?xml version="1.0" encoding="utf-8"?>
<com.android.camera.ui.common.ThumbnailContainer xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/preview_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/rounded_rectangle_thumbnail_preview"
android:visibility="invisible">
<TextureView
android:id="#+id/show_video_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
<ImageView
android:id="#+id/image_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:visibility="invisible"
/>
</com.android.camera.ui.common.ThumbnailContainer>
Here is my Surface code:
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Log.i(TAG, "InicializoSurface. Width: " + width + " HEIGHT:" + height);
Log.i(TAG, "InicializoSurface. Width: " + mVideoView.getMeasuredWidth() + " HEIGHT:" + mVideoView.getMeasuredHeight());
Log.i(TAG, "View transform. Width: " + mVideoView.getWidth() + " HEIGHT:" + mVideoView.getHeight());
mMediaSurface = new Surface(mVideoView.getSurfaceTexture());
initializeMediaPlayer();
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if (mMediaPlayer != null) {
// Make sure we stop video and release resources when activity is destroyed.
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
return false;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
//////////
private void initializeMediaPlayer(){
mMediaPlayer = new CustomMediaPlayer();
Uri uri = Uri.parse(mCameraDataAdapter.getList().get(0).getPath());
try {
mMediaPlayer.setDataSource(mActivity, uri);
mMediaPlayer.setSurface(mMediaSurface);
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(mMediaPlayer);
mMediaPlayer.setOnCompletionListener(mMediaPlayer);
} catch (IOException e) {
e.printStackTrace();
}
}
///////////
mVideoView.setVisibility(View.VISIBLE);
// mVideoView.setTranslationX(-200);
// mVideoView.setTranslationY(-200);
Log.i(TAG, "X: " + mVideoView.getX() + "Y: " + mVideoView.getY());
if (mVideoView.isAvailable()) {
onSurfaceTextureAvailable(mVideoView.getSurfaceTexture(), mVideoView.getWidth(), mVideoView.getHeight());
}
if (mMediaPlayer == null) {
initializeMediaPlayer();
}
// mMediaPlayer.mVideoHolder = mVideoView.getHolder();
// mMediaPlayer.setDisplay(mMediaPlayer.mVideoHolder);
if (mMediaPrepared) {
Log.i(TAG,"Comienzo Video");
mMediaPlayer.start();
}
Finally here is my onMeasure/OnLayout from my CustomView
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width;
int height;
int wantedWidth = 0;
int wantedHeight = 0;
if(mWidth == 0 && mHeight == 0 ){
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight =MeasureSpec.getSize(heightMeasureSpec);
}
width = mWidth;
height = mHeight;
if (mOrientation == 0 || mOrientation == 180) {
wantedWidth = width - (int)(mMargin * 2);
mVideo.measure(MeasureSpec.makeMeasureSpec(wantedWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (wantedWidth * mVideoAspectRatio), MeasureSpec.EXACTLY));
wantedHeight = (mViewTop.getLayoutParams().height) * 2 + (int) (wantedWidth * mAspectRatio);
} else {
Log.e(TAG, "Real Width = " + width + " real Height = " + height);
wantedHeight = width - 2 * mViewTop.getLayoutParams().height - (int)(mMargin * 2);
mVideo.measure(MeasureSpec.makeMeasureSpec(wantedHeight, MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec((int) (wantedHeight * mAspectRatio), MeasureSpec.EXACTLY));
//
wantedWidth =(int) (wantedHeight * mAspectRatio) ;
wantedHeight = width - (int)(mMargin * 2);
}
Log.e(TAG, "onMeasure: " + wantedWidth + "x" + wantedHeight);
setMeasuredDimension(MeasureSpec.makeMeasureSpec(wantedWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(wantedHeight, MeasureSpec.EXACTLY));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int w = getMeasuredWidth();
int h = getMeasuredHeight();
int viewHeight = mViewBottom.getMeasuredHeight();
int imageViewHeight = mImage.getMeasuredHeight();
int wantedHeight = 0;
// w = w - (int) (2 * mMargin);
if (mOrientation == 0 || mOrientation == 180) {
mVideo.layout(0,wantedHeight,w,wantedHeight + imageViewHeight);
}else{
mVideo.layout(viewHeight,0,r-viewHeight - (int) mMargin,w);
}
}
I have been looking in other post as Android MediaRecorder making rotated video and I saw that it's not possible to rotate the textureView, but I can't believe that I can rotate a image so easily and have to fight during this to rotate 90 degrees a video.
Thanks to #pskink for their comments in the post I found a solution with him. Finally I used a Matrix to rotate the Video Container(Texture View). The method that pskink give me is the next one:
private void setupMatrix(int width, int height, int degrees, boolean isHorizontal) {
Log.d(TAG, "setupMatrix for " + degrees + " degrees");
Matrix matrix = new Matrix();
//The video will be streched if the aspect ratio is in 1,5(recording at 480)
RectF src;
if (isHorizontal)
//In my case, I changed this line, because with my onMeasure() and onLayout() methods my container view is already rotated and scaled, so I need to sent the inverted params to the src.
src = new RectF(0, 0,mThumbnailContainer.getmWidth(), mThumbnailContainer.getmHeight());
else
src = new RectF(0, 0, mThumbnailContainer.getmWidth(),mThumbnailContainer.getmHeight());
RectF dst = new RectF(0, 0, width, height);
RectF screen = new RectF(dst);
Log.d(TAG, "Matrix: " + width + "x" + height);
Log.d(TAG, "Matrix: " + mThumbnailContainer.getmWidth() + "x" + mThumbnailContainer.getmHeight());
matrix.postRotate(degrees, screen.centerX(), screen.centerY());
matrix.mapRect(dst);
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
matrix.mapRect(src);
matrix.setRectToRect(screen, src, Matrix.ScaleToFit.FILL);
matrix.postRotate(degrees, screen.centerX(), screen.centerY());
mVideoView.setTransform(matrix);
}
Finally it worked and it looks totally awesome. With this I have been able to rotate and scale any video totally dynamically depending of the screen of my device and the Aspect Ratio used for record the video or take the picture.
I'm playing with the API2 Camera of Google and I'm having some problems with my code. I had two different CameraSessions, one for video and another one for images. To do it more efficient I change the code to use a unique Session and make the app more efficient.
After I did this, my camera preview is not working adequately. When I'm using a 4:3 aspect ratio my preview become stretched at height. In other way it looks fine when I'm using 16:9 ratio. In both cases my pictures looks fine, I mean, preview doesn't work correctly but the pictures that I took, have the correct aspect ratio.
I already check different post with the same problem:
Camera Preview Stretched on Few Android Devices
Camera display / preview in full screen does not maintain aspect ratio - image is skewed, stretched in order to fit on the screen
But the different answers didn't help me. I know that the problem is inside my onMeasure(), setTransformMatrix() or OnLayoutChangeListener() methods, but I don't know what I'm doing wrong.
Ignore the code about Rotation, right now it's dynamic. It always enter at else condition.
Here is my code:
private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
Log.d(TAG, "[onLayoutChange] " + mCameraUI.getTextureView().getMeasuredWidth() + "x" + mCameraUI.getTextureView().getMeasuredHeight());
int width = right - left;
int height = bottom - top;
if (mPreviewWidth != width || mPreviewHeight != height
|| (mOrientationResize != mPrevOrientationResize)
|| mAspectRatioResize || mOrientationChanged) {
Log.i(TAG, "[onLayoutChange] Layout changed");
mPreviewWidth = width;
mPreviewHeight = height;
Log.i(TAG, "[onLayoutChange] Preview size: "+ mPreviewWidth + "x" + mPreviewHeight);
setTransformMatrix(width, height);
mController.onScreenSizeChanged((int) mSurfaceTextureUncroppedWidth,
(int) mSurfaceTextureUncroppedHeight);
mAspectRatioResize = false;
mOrientationChanged = true;
}
}
};
setTransform
private void setTransformMatrix(int width, int height) {
Log.i(TAG, "Screen: " + mPreviewWidth + "x" + mPreviewHeight);
mMatrix = new Matrix();
//mCameraUI.getTextureView().getTransform(mMatrix);
float scaleX = 1f, scaleY = 1f;
float scaledTextureWidth, scaledTextureHeight;
mAspectRatio= (float)height/(float)width;
if (mAspectRatio==(4f / 3f)){
scaledTextureWidth = Math.max(width,
(int) (height / mAspectRatio));
scaledTextureHeight = Math.max(height,
(int) (width * mAspectRatio));
Log.i(TAG, "[PhotoUIManager]: Aspect Ratio 4:3=" + scaledTextureWidth + "x" + scaledTextureHeight );
}
else{
scaledTextureWidth = Math.max(width,
(int) (height / mAspectRatio));
scaledTextureHeight = Math.max(height,
(int) (width * mAspectRatio));
Log.i(TAG, "[PhotoUIManager]: Aspect Ratio 16:9=" + scaledTextureWidth + "x" + scaledTextureHeight );
}
if (mSurfaceTextureUncroppedWidth != scaledTextureWidth || mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
Log.e(TAG,"mi SurfaceWidth = " + mSurfaceTextureUncroppedWidth + "and mi scaledWidth=" + scaledTextureWidth);
Log.e(TAG,"mi SurfaceHeigh = " + mSurfaceTextureUncroppedHeight + "and mi scaledHeight=" + scaledTextureHeight);
mSurfaceTextureUncroppedWidth = scaledTextureWidth;
mSurfaceTextureUncroppedHeight = scaledTextureHeight;
Log.e(TAG,"Surfaces: " + mSurfaceTextureUncroppedWidth + "x" + mSurfaceTextureUncroppedHeight);
if (mSurfaceTextureSizeListener != null) {
mSurfaceTextureSizeListener.onSurfaceTextureSizeChanged(
(int) mSurfaceTextureUncroppedWidth, (int) mSurfaceTextureUncroppedHeight);
}
}
scaleX = scaledTextureWidth / width;
scaleY = scaledTextureHeight / height;
mMatrix.setScale(scaleX, scaleY, scaledTextureWidth/2, scaledTextureHeight/2);
Log.e(TAG, "scale: X= " + scaleX + " Y=" + scaleY + "Width= " + scaledTextureWidth + "Height= " + scaledTextureHeight);
// init the position (this seems to be necessary too when the ratio is 16/9
mCameraUI.getTextureView().setX(0);
mCameraUI.getTextureView().setY(0);
// Translate the preview with the rotation is aspect ration is 4/3
if (mAspectRatio == 4f / 3f) {
Log.e(TAG, "aspect ratio standard");
float verticalTranslateOffset = (mCameraUI.getTextureView().getMeasuredHeight() - scaledTextureHeight) / 2;
float horizontalTranslateOffset = (mCameraUI.getTextureView().getMeasuredWidth() - scaledTextureWidth) / 2;
int rotation = CameraUtil.getDisplayRotation(mActivity);
switch (rotation) {
case 0:
// phone portrait; translate the preview up
mCameraUI.getTextureView().setY(-verticalTranslateOffset);
mFaceView.setStandardPreviewTranslationOffset(-verticalTranslateOffset);
mFocusView.setStandardPreviewTranslationOffset(-verticalTranslateOffset);
break;
case 90:
// phone landscape: translate the preview left
mCameraUI.getTextureView().setX(-horizontalTranslateOffset);
mFaceView.setStandardPreviewTranslationOffset(-horizontalTranslateOffset);
mFocusView.setStandardPreviewTranslationOffset(-horizontalTranslateOffset);
break;
case 180:
// phone upside down: translate the preview bottom
mCameraUI.getTextureView().setY(verticalTranslateOffset);
mFaceView.setStandardPreviewTranslationOffset(verticalTranslateOffset);
mFocusView.setStandardPreviewTranslationOffset(verticalTranslateOffset);
break;
case 270:
// reverse landscape: translate the preview right
mCameraUI.getTextureView().setX(horizontalTranslateOffset);
mFaceView.setStandardPreviewTranslationOffset(horizontalTranslateOffset);
mFocusView.setStandardPreviewTranslationOffset(horizontalTranslateOffset);
break;
}
} else {
Log.e(TAG, "aspect ratio full");
mFaceView.setStandardPreviewTranslationOffset(0);
mFocusView.setStandardPreviewTranslationOffset(0);
}
mRenderOverlay.updateLayout();
mCameraUI.getTextureView().setTransform(mMatrix);
RectF previewRect = new RectF(0, 0, width, height);
mController.onPreviewRectChanged(CameraUtil.rectFToRect(previewRect));
}
onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
Log.d(TAG, "onMeasure PREVIOUS. Width x Height [" + widthMeasureSpec + " = " + width + "x" + heightMeasureSpec + " = " + height + "]");
int rotation = ((Activity) getContext()).getWindowManager().getDefaultDisplay().getRotation();
boolean isInHorizontal = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation;
int newWidth;
int newHeight;
if (isInHorizontal) {
newHeight = getMeasuredHeight();
newWidth = (int) (newHeight * mAspectRatio);
} else {
newWidth = getMeasuredWidth();
newHeight = (int) (newWidth * mAspectRatio);
}
setMeasuredDimension(newWidth, newHeight);
Log.d(TAG, "onMeasure. Width x Height [" + newWidth + "x" + newHeight + "]");
}
Solved!
I had a default values for the BufferSize of my texture, which just are restarted when I inicializated a new Session or after change the after ratio. But the Width and Height values for the texture were not updated with the ratio, so it becomes streched again and again.
I solved it changing my defaultbufferSize with the PreviewSizes which I update always that i change the ratio.
public void createCameraPreviewSession(Size previewsize, Surface recordingSurface) {
try {
if (mCaptureSession != null) {
mCaptureSession.stopRepeating();
mCaptureSession.close();
mCaptureSession = null;
}
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
List<Surface> surfaces = new ArrayList<Surface>();
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(previewsize.getWidth(),previewsize.getHeight());
;
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
.......
A bit difficult to explain, hope everyone get my point.
At onMeasure() I done this to make wrap_content work as expect.
// Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
but deiredWidth required getHeight() method to calculate
which getHight() not ready so, I can't calculate correct value from here.
So, is there a way to get given width from layout_width attribute at onMeasure()
or do I misunderstand somethings.
All Relevant Code
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int desiredWidth = getTotalIndicatorWidth(getMeasuredHeight());
int desiredHeight = DEFAULT_HEIGHT;
// Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
private int getTotalIndicatorWidth(int h) {
int width = 0;
int indicatorWidth = (h == 0) ? DEFAULT_HEIGHT : h;
for (int i = 0; i < indicatorSize; i++) {
if (i != 0) {
width = width + indicatorSpacing + indicatorWidth;
} else {
width = indicatorWidth;
}
}
return width;
}
#Override
protected void onDraw(Canvas canvas) {
pen.setStyle(Paint.Style.STROKE);
pen.setColor(indicatorColor);
pen.setStrokeWidth(indicatorStokeWidth);
pen.setAntiAlias(true);
drawIndicator(canvas, indicatorSize, 1);
}
private void drawIndicator(Canvas paper, int indicatorSize, int position) {
int indent = 0;
int cr = (getHeight() / 2) - getPaddingTop() - getPaddingBottom() - indicatorStokeWidth;
int cx = cr + getPaddingLeft() + indicatorStokeWidth;
int cy = (getHeight() / 2) + getPaddingTop() - getPaddingBottom();
for (int i = 0; i < indicatorSize; i++) {
paper.drawCircle(cx + indent, cy, cr, pen);
indent = (cr * 2) + (indicatorStokeWidth) + indent;
if ((i+1) < indicatorSize) {
indent += indicatorSpacing;
}
}
}
As you can see, the below image shown the area of custom view even it layout_width was set as wrap_content but it doesn't work for me now.
You say that you need to access getHeight() to calculate desiredWidth inside the onMeasure() method and also that getHeight() is not ready.
Yes, getHeight() or getWidth() can't be accessed from onMeasure() because these height and width are set in onMeasure() method. In your onMeause() method, you need to call setMeasuredDimension(). You have done that in your code.
setMeasuredDimension(width, height);
This width is what you will get when you call getWidth() and this height is what you will get when you call getHeight().
So if you want to access getHeight(), then use height instead.
The following code solve my problem of how can I get my layout_width or layout_height at onMesaure() so I can calculate my view correctly when user request for wrap_content
public PageIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
int[] attrsArray = new int[] {
android.R.attr.id, // 0
android.R.attr.background, // 1
android.R.attr.layout_width, // 2
android.R.attr.layout_height, // 3
};
TypedArray taa = context.obtainStyledAttributes(attrs, attrsArray);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PageIndicator, 0, 0);
try {
indicatorColor = ta.getColor(R.styleable.PageIndicator_indicator_color, Color.WHITE);
indicatorStokeWidth = (int) ta.getDimension(R.styleable.PageIndicator_indicator_stokeWidth, 4);
indicatorSpacing = (int) ta.getDimension(R.styleable.PageIndicator_indicator_spacing, 5);
indicatorSize = ta.getInteger(R.styleable.PageIndicator_indicator_size, 3);
indicatorPosition = ta.getInteger(R.styleable.PageIndicator_indicator_startPosition, 0);
mLayoutWidth = taa.getLayoutDimension(2, ViewGroup.LayoutParams.MATCH_PARENT);
mLayoutHeight = taa.getLayoutDimension(3, ViewGroup.LayoutParams.MATCH_PARENT);
} finally {
ta.recycle();
taa.recycle();
}
}
I use a Frame Layout, on which i bind my video player like this:
private void initVideoPlayer(View root) {
mVideoPlayer = new TextureViewVideoPlayer(width, height);
mVideoPlayer.setOnErrorListener(this);
mVideoPlayer.setOnPreparedListener(this);
mVideoPlayer.setOnCompletionListener(this);
mVideoPlayer.setOnBufferingUpdateListener(this);
mVideoPlayer.setOnSeekCompleteListener(this);
mVideoPlayer.setOnVideoSizeChangedListener(this);
mVideoPlayer.bindView((FrameLayout) root.findViewById(R.id.videoFrame), 0);
}
This is the onVideoSizeChanged function, from the TextureViewVideoPlayer:
#Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
Log.i(TAG, "onVideoSizeChanged " + width + " ZZ " + height + " Display Height: " + displayheight + " --- REAL VIDEO RATIO: -- " + ((double) width / (double) height));
if (this.mOnVideoSizeChangedListener != null)
this.mOnVideoSizeChangedListener.onVideoSizeChanged(this, width, height);
if (width == 0 || height == 0) {
mp.release();
Log.e(TAG, "invalid video width(" + width + ") or height(" + height + ")");
return;
}
double ratio = ((double) width / (double) height);
if (displayheight > height) {
ratio = ((double) displayheight / (double) height);
} else {
ratio = ((double) height / (double) displayheight);
}
LayoutParams params = new FrameLayout.LayoutParams((int) (width * ratio), (int) (height * ratio));
// LayoutParams params = new FrameLayout.LayoutParams((width),
// (height));
Log.i(TAG, "onVideoSizeChanged mod" + params.width + " ZZ " + params.height + " RATIO: " + ratio + " --- REAL RATIO: -- " + ((double) params.width / (double) params.height));
params.gravity = Gravity.CENTER;
mTextureView.setLayoutParams(params);
this.mTextureView.requestLayout();
}
As you can see, it takes the video width and height of the video, it calculates the ratio, then it takes the displays height, and makes a ratio to see how much bigger (or smaller) the video is than the screen, then it creates the params for the video, with that ratio, and sets this parameters on the textureView.
In the xml, the VideoFrame i bind my player too has this:
<FrameLayout
android:id="#+id/videoFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true" >
</FrameLayout>
Now, if I keep it like this, the video is stretched, and even though it looks like its a rectangle, a part from the top and bottom of the video disappear (being 1:1 the width and height are the same, and the height is bigger that the screen). Now, if I don't use the ratio, and create the parameters with the width and height of the video (640 x 480) It will be a small square. (ratio 1:1), and stretched. Any ideea how this could be avoided?
PS: My texture view in fact is a Square Texture View, which had this onMeasure function:
#Override
protected void onMeasure(int paramInt1, int paramInt2) {
int i = View.MeasureSpec.getSize(paramInt1);
setMeasuredDimension(i, i);
}
I've changed it into:
#Override
protected void onMeasure(int paramInt1, int paramInt2) {
int i = View.MeasureSpec.getSize(paramInt1);
int j = View.MeasureSpec.getSize(paramInt2);
setMeasuredDimension(i, j);
}
Hopefully, this fixes my issues.
MY texture view had this:
#Override
protected void onMeasure(int paramInt1, int paramInt2) {
int i = View.MeasureSpec.getSize(paramInt1);
setMeasuredDimension(i, i);
}
To fix it, I've changed it into:
#Override
protected void onMeasure(int paramInt1, int paramInt2) {
int i = View.MeasureSpec.getSize(paramInt1);
int j = View.MeasureSpec.getSize(paramInt2);
setMeasuredDimension(i, j);
}