I have a game with full screen SurfaceView (Portrait) and thread that renders continously on the surface.
The SurfaceView is initialized onCreate and its size is determined by the width and height of the screen in pixels.
After it's created, it is set as the content view of the Game Activity (no XML definitions):
Bitmap frameBuffer = Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888);
MySurfaceView renderView = new MySurfaceView(this, frameBuffer);
this.setContentView(renderView);
The problem is, height of the screen doesn't include Display Cutouts (for devices that have them) and is taller than the allowed drawing screen, because the renderView is placed below the cutout area and does not use it for rendering.
I seek to find the real screen height - display cutout height so that I can make the frame buffer have that height.
Do you know if there is way to get display cutout area height onCreate, the same way we're able to get navigation/status bar height. Below is a snippet that returns navigation bar height:
Resources resources = getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
int navigationBarHeight = resources.getDimensionPixelSize(resourceId);
}
Thank you for your help!
--- UPDATE ---
I will try to clarify the question more:
1) onCreate a full screen portrait SurfaceView is created with the dimensions of the screen width and height (no XML)
2) Immersive mode is enabled for API level > 19 (KitKat)
3) Screen width and height is taken with Display.getSize() or Display.getRealSize() depending on API level
4) On devices with Top Display Cutout the view is automatically placed below the cutout and bottom content is cut out, and this is the issue
5) I would like to get the height of the Display Cutout onCreate, so that I can create the SurfaceView height to be screenHeight - displayCutoutHeight.
6) The problem boils down to finding the height of the Display Cutout.
There seems to be no way to get WindowInsets object in onCreate. Your Activity has to be attached to window first. So your options are:
1.
override fun onAttachedToWindow() {
val cutout = window.decorView.rootWindowInsets.displayCutout
}
2.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView?.setOnApplyWindowInsetsListener { _, insets ->
val cutout = insets.displayCutout
return#setOnApplyWindowInsetsListener insets
}
}
WindowInsets.getDisplayCutout() returns the DisplayCutout object if present.
To get the bounding rectangle of display cutout you can make use of
DisplayCutout.getBoundingRects()
It will give you a list of Rect of all the display cutouts(non-functional area) on the screen.
In order to get the height of each display cutout,
Rect.height()
This might return negative values also so make sure to take the modulus of height.
For more information about display cutout support, see the following links:
android:windowLayoutInDisplayCutoutMode
DisplayCutout
layoutInDisplayCutoutMode
WindowInsets
WindowInsets.getDisplayCutout()
This modification of Roman's answer (above) worked for me:
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
Log.i("Chicken", "cutout: " + getWindow().getDecorView().getRootWindowInsets().getDisplayCutout());
}
Here's how in onCreate():
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
drumRoll1Button.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
DisplayCutout displayCutout = drumRoll1Button.getRootWindowInsets().getDisplayCutout();
if (displayCutout!=null) {
displayCutoutLengthPx = displayCutout.getSafeInsetLeft();
}
Log.v(LOG_TAG,"displayCutoutLengthPx:"+displayCutoutLengthPx);
}
});
}
Please swap in any view on screen (for drumRoll1Button) and getSafeInsetTop() (for getSafeInsetLeft()) if you are in Portrait mode, which the original question is asking. This sample works for Landscape.
Related
Let's say we have a main_activity.xml layout that defines all dimensions in a relative manner -- constraints, percentages, and guidelines (that are percentages)... no "static" dp.
But in MainActivity.java, we programatically create some subviews, and we want to define their height/width dimensions as relative to existing views.
We do not know the dimensions or density of the device so, so nor do we know the (actual integer) dimensions of any view before run-time...
But we can say something like:
int heightDimensionForNewView = (int) (someAlreadyInflatedView.getHeight() / 7f)
But what if, under certain circumstances, these "new" views need to be displayed immediately at app start-time?
So, the question:
In the Android Activity life-cycle, when is the earliest point at which you can (somehow) safely query (something) for actual/finalized/guaranteed layout dimensions? And what is that something and somehow?
I haven't been able to find an override method such as "onContentViewInflated()" and there is no onCreateView() method like there is in Fragments.
I've also tried Logging from inside onStart() and onResume() but the dimension results are always "0," presumably because they haven't been inflated yet.
I know that any given View can get its own dimensions in onMeasure(), but then you would have make a static variable in MainActivity in order to assign it and use it from there... or some way of sending that information from the View back to the Activity.
What am I missing? I just want to be able to get the number somehow from inside MainActivity itself.
My suggestions are:
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int oldWidth = oldRight - oldLeft; // right exclusive, left inclusive
if( v.getWidth() != oldWidth ) {
// width has changed
}
}
});
and
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// View has laid out
// Remove the layout observer if you don't need it anymore
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
I have an Android application that extensively uses PopupWindows. I've found that when the layouts of the contents of the PopupWindow use WRAP_CONTENT the dialog will only grow to a specific width before it begins truncating the content. I've traced this down to a config.xml dimension:
<dimen name="config_prefDialogWidth">580dp</dimen>
This dimension is used to create a maximum width in the measureHierarchy method of ViewRootImpl when determining the dialog size. The following code is used to access the value:
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
The config_prefDialogWidth seems to have values for different device configurations, for example, the one I listed is for sw600dp. The one for default devices has 320dp as a value.
It would seem that this dimension is tuned for portrait orientation. Since my app forces landscape orientation this width is too small.
How do I override the config_prefDialogWidth dimension?
What you need is to make the width of your PopupWindow(wrap_content) larger than config_prefDialogWidth.
So here is my solution.
Choose one child View from the contentView of your PopupWindow.
A TextView, for example.
Override its onMeasure, add View.MEASURED_STATE_TOO_SMALL if necessary:
TextView textView = new TextView(context) {
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Text breaks into at least 2 lines, maybe the width is too small
if (getLineCount() > 1) {
int measuredWidth = getMeasuredWidth();
final int desiredMaxWidth = (getResources().getDisplayMetrics().widthPixels >> 2) * 3;
// Compare with the desired max width you want, for example: 3/4 * screenWidth
if (measuredWidth < disiredMaxWidth) {
// Tell ViewRootImpl that the width used to measure is too small
measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
// ViewRootImpl will remeasure contentView with screem width.
setMeasuredDimension(measuredWidth, getMeasuredHeight());
}
}
}
};
Generally speaking:
Override onMeasure of one child View
Check whether the measured width is too small
Ask for remeasurement if necessary (Using View.MEASURED_STATE_TOO_SMALL)
You cannot override nor change config_prefDialogWidth's value because this dimension is a internal resource. A workaround is you must create your own class. You can copy PopupWindows from its source code, and modify it.
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!
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.
Is there a way to know the size of the keyboard that is shown in the screen?
I am using Cocos2dx for programming, but I want to know the height of the keyboard shown in screen in the part of Android or the part of Cocos, it does not matter.
I know that Keyboard has a getHeight() method but I don't want to create new keyboards, i want to use the default one.
We did it with this
myLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
parent.getWindowVisibleDisplayFrame(r);
int screenHeight = parent.getRootView().getHeight();
int heightDifference = screenHeight - (r.bottom - r.top);
Log.d("Keyboard Size", "Size: " + heightDifference);
}
});
We only resize views with the keyboard, so we could use this.
Rect r = new Rect();
View rootview = this.getWindow().getDecorView(); // this = activity
rootview.getWindowVisibleDisplayFrame(r);
Result of this is the amount of space your application uses on screen (works even when activity is not resized). Obviously remaining screen space will be used by the keyboard ( if its visible)
Found id up here: https://github.com/freshplanet/ANE-KeyboardSize/blob/master/android/src/com/freshplanet/ane/KeyboardSize/getKeyboardY.java
if your activity is not fullscreen, using code below:
content.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
if (keyBoardHeight <= 100) {
Rect r = new Rect();
content.getWindowVisibleDisplayFrame(r);
int screenHeight = content.getRootView()
.getHeight();
int heightDifference = screenHeight
- (r.bottom - r.top);
int resourceId = getResources()
.getIdentifier("status_bar_height",
"dimen", "android");
if (resourceId > 0) {
heightDifference -= getResources()
.getDimensionPixelSize(resourceId);
}
if (heightDifference > 100) {
keyBoardHeight = heightDifference;
}
Log.d("Keyboard Size", "Size: " + heightDifference);
}
// boolean visible = heightDiff > screenHeight / 3;
}
});
If you want to calculate the Virtual Keyboard height while your activity does not change in size (adjustPan) then you can use this sample:
https://github.com/siebeprojects/samples-keyboardheight
It uses a hidden window in order to calculate the height difference between the window and the root view of the activity.
You can't tell. No, really: you simply can't tell.
The keyboard does not need to be any particular shape. It does not have to be placed at the bottom of the screen (many of the most popular options are not), it does not have to keep its current size when you change text fields (almost none do depending on the flags). It does not even have to be rectangular. It may also just take over the entire screen.
I know this is an old post, but I noticed that the chosen solution for me did not work on all devices. There seemed to be a discrepancy and so I implemented this and it seems to be a catch all:
final int[] discrepancy = new int[1];
discrepancy[0] = 0;
// this gets the height of the keyboard
content.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
View rootview = activity.getWindow().getDecorView(); // this = activity
rootview.getWindowVisibleDisplayFrame(r);
int screen_height = rootview.getRootView().getHeight();
int keyboard_height = screen_height - (r.bottom + r.top) - discrepancy[0];
if (discrepancy[0] == 0) {
discrepancy[0] = keyboard_height;
if (keyboard_height == 0) discrepancy[0] = 1;
}
int margin_bottom = keyboard_height + Helper.getDp(10, activity);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) carousel_container.getLayoutParams();
params.setMargins(0, 0, 0, margin_bottom);
//boolean visible = heightDiff > screenHeight / 3;
}
});
When the listener is first called it measures the screen without a keyboard and if there is a discrepancy I account for it the next time around. If there is no discrepancy I set the discrepancy to 1 just so it is no longer 0.
After 2020, if your min SDK large or equal then 21, you can check the visibility and height of IME by below functions:
fun isKeyboardVisible(attachedView: View): Boolean {
val insets = ViewCompat.getRootWindowInsets(attachedView)
return insets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false
}
fun getKeyboardHeight(attachedView: View): Int {
val insets = ViewCompat.getRootWindowInsets(attachedView)
return insets?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
}
Ref: Animating your keyboard (part 1). New WindowInsets APIs for checking theā¦ | by Chris Banes | Android Developers | Medium
in cocos2d-x we have got CCEditBox.
Inside Extensions->GUI->CCEditBox, you can find the class CCEditBox.
The beauty is that it hides the keyboard of tapping somewhere else on the scene. and automatically moves the keyboard up incase your edit box was placed too low on the scene.
If you are using cocos2d-x v2.1.3 then you can navigate to sample Project by going to
samples->cpp->TestCpp->Classes->ExtensionTest->EditBoxTest.
I'm just going to use it instead of CCTextField from now on. just came across it yesterday :)
After hours of searching I found a solution if you want to set windowSoftInput="adjustPan"
Here is the code snippet:
final View root = findViewById(android.R.id.content);
root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
Rect r = new Rect();
{
root.getWindowVisibleDisplayFrame(r);
}
#Override
public void onGlobalLayout() {
Rect r2 = new Rect();
root.getWindowVisibleDisplayFrame(r2);
int keyboardHeight = r.height() - r2.height();
if (keyboardHeight > 100) {
root.scrollTo(0, keyboardHeight);
}
else {
root.scrollTo(0, 0);
}
}
});
In this code, after I found the keyboard height I scroll the view up to not covered by the keyboard which is the main reason for finding the keyboard height.
According to the docs :
void getWindowVisibleDisplayFrame(Rect outRect) : Retrieve the overall visible display size in which the window this view is attached to has been positioned in.
The ROOT_VIEW of an android display screen can be visualized as being a single screen view with VISIBLE DISPLAY FRAME which displays your activity's view.
This VISIBLE DISPLAY FRAME is adjusted when SOFT KEYBOARD is displayed or hidden from the screen.
NOTE : Please look at the two images by clicking on the links given below for better understanding
So the ROOT VIEW of a display screen can be visualized as :
RootView of display screen
The adjustment of VISIBLE DISPLAY FRAME with the opening and closing of SOFT KEYBOARD can be visualized as :
VISIBLE_DISPLAY_SCREEN adjustment
This adjustment of the VISUAL DISPLAY FRAME can be very well used to find out the height of the keyboard as :
(when the soft keyboard is open)
SOFT_KEYBOARD_HEIGHT = ROOT_VIEW_HEIGHT - (VISUAL_DISPLAY_FRAME_HEIGHT + EXTRA_SCREEN_HEIGHT)
The code to achieve the above is :
int mExtraScreenHeight=-1, mKeyboardHeight=-1;
boolean mKeyboardOpen;
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int rootViewHeight, visibleDisplayFrameHeight, fakeHeight;
/* (rootViewHeight - visibleDisplayFrameHeight) is not the real height of the keyboard
it is the fake height as it also consist of extra screen height
so FAKE_HEIGHT = KEYBOARD_HEIGHT + EXTRA_SCREEN_HEIGHT
To get keyboard height extra screen height must be removed from fake height
*/
Rect rect = new Rect();
rootView.getWindowVisibleDisplayFrame(rect);
rootViewHeight = rootView.getRootView().getHeight();
visibleDisplayFrameHeight = rect.height();
fakeHeight = rootViewHeight-visibleDisplayFrameHeight;
if (mExtraScreenHeight == -1){
mExtraScreenHeight=fakeHeight;
}
/* Suppose the soft keyboard is open then the VISIBLE_DISPLAY_FRAME is in reduced size
due to the space taken up by extra screen and the keyboard but when the soft keyboard closes
then KEYBOARD_HEIGHT=0 and thus FAKE_HEIGHT = EXTRA_SCREEN_HEIGHT
*/
else if (fakeHeight <= mExtraScreenHeight){
mExtraScreenHeight=fakeHeight;
mKeypadOpen=false;
}
else if (fakeHeight > mExtraScreenHeight){
mKeypadHeight=fakeHeight-mExtraScreenHeight;
mKeypadOpen=true;
}
}
});
NOTE : The onGlobalLayout() function will be called only when the global layout changes like when the soft keyboard opens. So the soft keyboard must be open at least once to get the soft keyboard height.
It worked for me ;)
Sorry for not being able to comment, two or three of the answers helped me solve my issue and they were related to using the AddOnGlobalLayoutListener and then determining the remaining height before and after a keyboard showed up.
The solution I used was based off of Rudy_TM's answer.
HOWEVER, one thing that I had to find was that in order for that method to work, you must have the following line somewhere
Window.SetSoftInputMode(SoftInput.AdjustResize);
Before I had SoftInput.AdjustNothing (or something like that) and it would not work. Now it works perfect. Thanks for the answers!
Complete answer & worked perfectly for me:
Rect r = new Rect();
View rootview = this.getWindow().getDecorView(); // this = activity
rootview.getWindowVisibleDisplayFrame(r);
int keyboardHeight = rootview.getHeight() - r.bottom;