Android surfaceChanged incorrect values for width/height - android

I've a bitmap that is drawn on a surface and needs to be resized every time the device is rotated. Because of soft buttons, I use this code to get current dimensions (it seems to work ok):
public void getCanvasXY(){
int currentapiVersion = android.os.Build.VERSION.SDK_INT;
if (currentapiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2){
// HoneyComb 3.2 and up
DisplayMetrics metrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics);
Global.CanvasX = metrics.widthPixels;
Global.CanvasY = metrics.heightPixels;
} else{
// before HoneyComb
Global.CanvasX = getWidth();
Global.CanvasY = getHeight();
}
}
I thought the best place to recalculate the dimensions and create my bitmap was in surfaceChanged().
#Override
public void surfaceChanged(SurfaceHolder holder1, int format, int width, int height) {
getCanvasXY();
LoadBMP();
}
I see that this is called more than once, and I get diferent values for CanvasX and CanvasY while device is rotating. The last time it's called works ok, but I see how onDraw() is drawing my bitmap and sprites bad resized and finally ok. It's an ugly effect.
How to solve this?
thanks

Here I assume you mean SurfaceView, where you are using a background thread to draw to a surface and your are using SurfaceHolder.CallBack(). This is not a complete answer, however, this problem also has plagued me for a while. In my late night troubleshooting I managed to put together a workaround which may be a little more elegant than yours. The result being that nothing is drawn until the surfacechanged has settled to it's last state, so the user is unaware of what is happening.
In your surfacechanged you'll notice that your height or width isn't changing to the correct value, in otherwords the result surface if drawn to is MalFormed. What you need is a reference value to what your height and width should be, you can get that by placing the following code before you SetContentView() in your main activity:
WindowManager w = getWindowManager();
Display d = w.getDefaultDisplay();
int Meausredwidth = d.getWidth();
int Measuredheight = d.getHeight();
Now from your measured height and width, work out how much you are assigning to the surfaceView, and pass those values to your SurfaceView as global variables. And make sure you are calling the above code in onconfigurationchanged() in your main activity so those values get recalculated each time. Now on your SurfaceView which should somehow implement SurfaceHolder.Callback, Change your surfacechanged to look like this:
#Override
public void surfaceChanged(SurfaceHolder mholder, int format, int width,
int height) {
if (width != mWidth) {
MalFormed = true;
thread.setRunning(false);
count++;
} else if (width == mWidth) {
if (MalFormed) {
MalFormed = false;
Log.d("ROTATE", "ReDraw");
thread = new DrawingThread(this, holder, mHandler);
thread.setRunning(true);
thread.start();
}
}
}
Where mWidth is the desired width. Here you may need to change the if statement depending on your specific problem. And MalFormed is a global boolean in your surfaceview.
Now it's important to remember, if a change occurs SurfaceChanged() gets called before SurfaceCreated, which means we can easily modify any code which attempts to interact with the thread to suit our above changes.
So anywhere where else you are attempting to join or start that background drawing thread you must check first if the surface is MalFormed. E.g.
if (thread.getState() == State.TERMINATED){
if(!MalFormed){
holder.removeCallback(this);
holder = getHolder();
holder.addCallback(this);
thread = new DrawingThread(this, holder,mHandler);
thread.setRunning(true);
thread.start();
}
}else{
if(!MalFormed){
thread.setRunning(true);
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Hope I'm in the ballpark somewhere. All the best.

Related

ImageView Resizing On Orientation changes

I have a question regarding the process of resizing an ImageView Have on orientation changes.
I have to place a bitmap on the top of an image view. (Basically a marker placed by the user when touching it). On orientation change, I store the relatives position of the marker and intent to restaure it when the image is re-displayed.
for now I do it this way :
on restauration I call this :
handler.post(setCollectedAnswersRunnable);
refering to this :
Runnable setCollectedAnswersRunnable = new Runnable() {
#Override
public void run() {
if (iv.getMeasuredHeight() != 0) {
int x = Math.round(coordinateCollectedAnswer.x * iv.getMeasuredWidth() / (float) question.getImage().getWidth());
int y = Math.round(coordinateCollectedAnswer.y * iv.getMeasuredHeight() / (float) question.getImage().getHeight());
selectedX = x;
selectedY = y;
addPointerImageView(x, y);
} else {
handler.postDelayed(this, 330);
}
}
};
The important thing to note is this works very well when I go from a larger ImageView to a smaller one ie when passing from landscape to portrait.
But in the other way, it doesn't. The ImageView seems to be restaured first with the same size and then scaling up witch mess my marker up. So I certainely could check if it's the same size as in portrait in addition to the !=0 condition in my runnable. but first I'm not very proud of that way to do it, I'd rather be notified when the ImageView get it's final sizing. And I would prefer not passing the viewsize in my state bundle.
Is there a simpler way to achieve this ?
By the way I load the ImageView this way :
Picasso.with(iv.getContext())
.load(AppConstants.SERVER_URL + "/api/" + question.getImage().getUrl())
.config(Bitmap.Config.RGB_565)
.into(new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
iv.setImageBitmap(bitmap);
iv.setAdjustViewBounds(true);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
I´ve got a SurfaceView that keeps to be square. i´ve notice thatn when android runtime is doing all the stuff for layout will measure the component again and again and you never can tell when is the last time and the number of executions does not seem to be related to the amount of components on the screen, it is not random but maybe has something to do with the layout constraints of all the stuff around.
here´s my class overriding the measure ion order to keep square:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
int size = w;
if (w < h) {
setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
} else {
size = h;
setMeasuredDimension(heightMeasureSpec, heightMeasureSpec);
}
if (h > 0 && squareSurfaceViewListener != null) {
squareSurfaceViewListener.onChangeSize(size);
}
}
So I´d inherit the imageview, detect the change and reload the image.
Using picasso it´ll cache the stuff but, maybe, you could forget about that and call for a new load each time the component is measured or add some kind of observer in the middle.

Android: determining screen size in different ways cause different screen sizes

When I determine the screen size in an Activity with this attempt, I get 800x480 as result:
#SuppressWarnings("deprecation")
private void initSpecs()
{
WindowManager windowManager = (WindowManager)this.getSystemService(Context.WINDOW_SERVICE);
Point size = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
{
windowManager.getDefaultDisplay().getSize(size);
effectiveWidth = size.x;
effectiveHeight = size.y;
}
else
{
Display display = windowManager.getDefaultDisplay();
effectiveWidth = display.getWidth();
effectiveHeight = display.getHeight();
}
}
When I determine the screen size in my game view (a class extending view) with the following attempt, I get 800x442.
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
}
There is NO title bar. How does this discrepancy emerge?
Well the problem is that in the first way you are actually getting the "screen/display" size, however, In the second way what you are getting is the width/height measured values of the View where you overrode the "onMeasure", and that's not necessarily equal to the screen size, specially if another Views affect the size of your View during it's life cycle. This is kind of the life cycle of a View.
Attached
Measured*
Layout
Drawn
Notice that there's a chance this life cycle repeats if another View changes it's size and the OS thinks is a good idea to refresh the views.
Hope it Helps!
Regards!

Android, GL, screen size in IMMERSIVE MODE

I am developing with Nexus 4 KitKat 4.4 and
trying to add IMMERSIVE MODE to my game.
I need screen height to set glViewport correctly.
Previously I used
#SuppressWarnings("deprecation")
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public static int getScreenHeight() {
if (Main.m_activity == null)
return -1;
Display display = Main.m_activity.getWindowManager()
.getDefaultDisplay();
int height = -1;
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){
//width = display.getWidth(); // deprecated
height = display.getHeight(); // deprecated
}else{
Point size = new Point();
display.getSize(size);
height = size.y;
}
return height;
}
It does not return the real height of screen in IMMERSIVE MODE.
So I started to use values from
private static class Renderer implements GLSurfaceView.Renderer {
public void onSurfaceChanged(GL10 gl, int width, int height) {
and it worked fine when app starts. If I press home button and return to home screen and then back to game onSurfaceChanged get called again but with old wrong values (non-immersive mode screen size, smaller, regular)
IMMERSIVE SCREEN size is 800x1280
REGULAR size is 800x1184
When I get regular size and set it in glViewport then I get black line in top of screen.
PS Also IMMERSIVE MODE is lost when I press volume buttons.
PS2
I have following method impl. It does not help to handle screen/window resize.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
Good article but still not enough.
http://developer.android.com/training/system-ui/immersive.html
Right now we get screen size with
public void onSurfaceChanged(GL10 gl, int width, int height) {
and if height decreased then we enable immersive mode again with 1 second delay.
This works but hacky and I look for canonical solution myself.
I am afraid it is just buggy in KitKat.
I do not any famous title that implemented immersive mode. (except our app of course )))
Use Display.getRealSize to return the screen bounds without system windows.
You can use View.OnSystemUiVisibilityChangeListener to get a callback when immersive mode is disabled/enabled. Using this, you can figure out the actual height of the screen.

Getting the width/height of a layout in Android

I'm wondering how to measure the dimensions of a view. In my case it is aan Absolute Layout. I've read the answers concerning those questions but I still don't get it.
This is my code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
AbsoluteLayout layoutbase = (AbsoluteLayout) findViewById(R.id.layoutbase);
drawOval();
}
public void drawOval(){ //, int screenWidth, int screenHeight){
AbsoluteLayout layoutbase = (AbsoluteLayout) findViewById(R.id.layoutbase);
int screenWidth = layoutbase.getWidth();
int screenHeight = layoutbase.getHeight();
Log.i("MyActivity", "screenWidth: " + screenWidth + ", screenHeight: " +screenHeight);
Coordinates c = new Coordinates(BUTTONSIZE,screenWidth,screenHeight);
...some code ...
((ViewGroup) layoutbase ).addView(mybutton, new AbsoluteLayout.LayoutParams(BUTTONSIZE, BUTTONSIZE, c.mX, c.mY));
mybutton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
showText(mybutton);
}
});
}
public void showText(View button){
int x = findViewById(LAYOUT).getWidth();
int y = findViewById(LAYOUT).getHeight();
Toast message = Toast.makeText(this, "x: " + x , Toast.LENGTH_SHORT);
message.show();
}
The getWidth() command works great in showText() but it does not in drawOval(). I know it looks a bit different there but I also used the int x = findViewById(LAYOUT).getWidth(); version in drawOval(), and x/y are always 0. I don't really understand why there seems to be no width/height at that earlier point. Even if I actually draw a Button on the Absolute Layout, getWidth() returns 0. Oviously I want to measure the sizes in drawOval().
I think will help you.
LinearLayout headerLayout = (LinearLayout)findviewbyid(R.id.headerLayout);
ViewTreeObserver observer = headerLayout .getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
int headerLayoutHeight= headerLayout.getHeight();
int headerLayoutWidth = headerLayout.getWidth();
headerLayout .getViewTreeObserver().removeGlobalOnLayoutListener(
this);
}
});
}
getWidth() is giving you 0 because onCreate is called before layout actually happens. Due to views being able to have dynamic positions and sizes based on attributes or other elements (fill_parent for example) there's not a fixed size for any given view or layout. At runtime there is a point in time (actually it can happen repeatedly depending on many factors) where everything is actually measured and laid out. If you really need the height and width, you'll have to get them later as you've discovered.
This specially deal with Dimensions so
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
This may help you in managing dimensions.
Note: This returns the display dimensions in pixels - as expected. But the getWidth() and getHeight() methods are deprecated. Instead you can use:
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
as also Martin Koubek suggested.
If your goal is to simply draw an oval on the screen, then consider creating your own custom View rather than messing around with AbsoluteLayout. Your custom View must override onDraw(android.graphics.Canvas), which will be called when the view should render its content.
Here is some extremely simple sample code that might help get you started:
public class MainActivity extends Activity {
private final Paint mPaint = new Paint();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
// create a nested custom view class that can draw an oval. if the
// "SampleView" is not specific to the Activity, put the class in
// a new file called "SampleView.java" and make the class public
// and non-static so that other Activities can use it.
private static class SampleView extends View {
public SampleView(Context context) {
super(context);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.CYAN);
// smoothen edges
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4.5f);
// set alpha value (opacity)
mPaint.setAlpha(0x80);
// draw oval on canvas
canvas.drawOval(new RectF(50, 50, 20, 40), mPaint);
}
}
}
This give you screen resolution:
WindowManager wm = (WindowManager)context.getSystemService(context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point outSize = new Point();
display.getSize(outSize);
kabuko's answer is correct, but could be a little more clear, so let me clarify.
getWidth() and getHeight() are (correctly) giving you 0 because they have not been drawn in the layout when you call them. try calling the two methods on the button after addView() (after the view has been drawn and is present in the layout) and see if that gives you the expected result.
See this post for more information.

Determining the size of an Android view at runtime

I am trying to apply an animation to a view in my Android app after my activity is created. To do this, I need to determine the current size of the view, and then set up an animation to scale from the current size to the new size. This part must be done at runtime, since the view scales to different sizes depending on input from the user. My layout is defined in XML.
This seems like an easy task, and there are lots of SO questions regarding this though none which solved my problem, obviously. So perhaps I am missing something obvious. I get a handle to my view by:
ImageView myView = (ImageView) getWindow().findViewById(R.id.MyViewID);
This works fine, but when calling getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, etc., they all return 0. I have also tried manually calling measure() on the view followed by a call to getMeasuredWidth(), but that has no effect.
I have tried calling these methods and inspecting the object in the debugger in my activity's onCreate() and in onPostCreate(). How can I figure out the exact dimensions of this view at runtime?
Use the ViewTreeObserver on the View to wait for the first layout. Only after the first layout will getWidth()/getHeight()/getMeasuredWidth()/getMeasuredHeight() work.
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
viewWidth = view.getWidth();
viewHeight = view.getHeight();
}
});
}
There are actually multiple solutions, depending on the scenario:
The safe method, will work just before drawing the view, after the layout phase has finished:
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
runnable.run();
return true;
}
};
view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
Sample usage:
ViewUtil.runJustBeforeBeingDrawn(yourView, new Runnable() {
#Override
public void run() {
//Here you can safely get the view size (use "getWidth" and "getHeight"), and do whatever you wish with it
}
});
On some cases, it's enough to measure the size of the view manually:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth();
int height=view.getMeasuredHeight();
If you know the size of the container:
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
view.measure(widthMeasureSpec, heightMeasureSpec)
val width=view.measuredWidth
val height=view.measuredHeight
if you have a custom view that you've extended, you can get its size on the "onMeasure" method, but I think it works well only on some cases :
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
final int newHeight= MeasureSpec.getSize(heightMeasureSpec);
final int newWidth= MeasureSpec.getSize(widthMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
If you write in Kotlin, you can use the next function, which behind the scenes works exactly like runJustBeforeBeingDrawn that I've written:
view.doOnPreDraw { actionToBeTriggered() }
Note that you need to add this to gradle (found via here) :
android {
kotlinOptions {
jvmTarget = "1.8"
}
}
implementation 'androidx.core:core-ktx:#.#'
Are you calling getWidth() before the view is actually laid out on the screen?
A common mistake made by new Android developers is to use the width
and height of a view inside its constructor. When a view’s
constructor is called, Android doesn’t know yet how big the view will
be, so the sizes are set to zero. The real sizes are calculated during
the layout stage, which occurs after construction but before anything
is drawn. You can use the onSizeChanged() method to be notified of
the values when they are known, or you can use the getWidth() and
getHeight() methods later, such as in the onDraw() method.
Based on #mbaird's advice, I found a workable solution by subclassing the ImageView class and overriding onLayout(). I then created an observer interface which my activity implemented and passed a reference to itself to the class, which allowed it to tell the activity when it was actually finished sizing.
I'm not 100% convinced that this is the best solution (hence my not marking this answer as correct just yet), but it does work and according to the documentation is the first time when one can find the actual size of a view.
Here is the code for getting the layout via overriding a view if API < 11 (API 11 includes the View.OnLayoutChangedListener feature):
public class CustomListView extends ListView
{
private OnLayoutChangedListener layoutChangedListener;
public CustomListView(Context context)
{
super(context);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
if (layoutChangedListener != null)
{
layoutChangedListener.onLayout(changed, l, t, r, b);
}
super.onLayout(changed, l, t, r, b);
}
public void setLayoutChangedListener(
OnLayoutChangedListener layoutChangedListener)
{
this.layoutChangedListener = layoutChangedListener;
}
}
public interface OnLayoutChangedListener
{
void onLayout(boolean changed, int l, int t, int r, int b);
}
You can check this question. You can use the View's post() method.
Use below code, it is give the size of view.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e("WIDTH",""+view.getWidth());
Log.e("HEIGHT",""+view.getHeight());
}
This works for me in my onClickListener:
yourView.postDelayed(new Runnable() {
#Override
public void run() {
yourView.invalidate();
System.out.println("Height yourView: " + yourView.getHeight());
System.out.println("Width yourView: " + yourView.getWidth());
}
}, 1);
I was also lost around getMeasuredWidth() and getMeasuredHeight() getHeight() and getWidth() for a long time.......... later i found that getting the view's width and height in onSizeChanged() is the best way to do this........ you can dynamically get your CURRENT width and CURRENT height of your view by overriding the onSizeChanged() method.
might wanna take a look at this which has an elaborate code snippet.
New Blog Post: how to get width and height dimensions of a customView (extends View) in Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html
In Kotlin file, change accordingly
Handler().postDelayed({
Your Code
}, 1)
You can get both Position and Dimension of the view on screen
val viewTreeObserver: ViewTreeObserver = videoView.viewTreeObserver;
if (viewTreeObserver.isAlive) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
//Remove Listener
videoView.viewTreeObserver.removeOnGlobalLayoutListener(this);
//View Dimentions
viewWidth = videoView.width;
viewHeight = videoView.height;
//View Location
val point = IntArray(2)
videoView.post {
videoView.getLocationOnScreen(point) // or getLocationInWindow(point)
viewPositionX = point[0]
viewPositionY = point[1]
}
}
});
}
If you need to know the dimensions of a View right after it is drawn you can simply call post() on that given View and send there a Runnable that executes whatever you need.
It is a better solution than ViewTreeObserver and globalLayout since it gets called repeatedly not just once.
This Runnsble will execute only once and you will know the views size.
works perfekt for me:
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
CTEditor ctEdit = Element as CTEditor;
if (ctEdit == null) return;
if (e.PropertyName == "Text")
{
double xHeight = Element.Height;
double aHaight = Control.Height;
double height;
Control.Measure(LayoutParams.MatchParent,LayoutParams.WrapContent);
height = Control.MeasuredHeight;
height = xHeight / aHaight * height;
if (Element.HeightRequest != height)
Element.HeightRequest = height;
}
}

Categories

Resources