I've got a DialogFragment, normally centered on the screen, that I'm trying to move out of the way of the on screen keyboard, if any should appear, because it's not a good UX experience for the keyboard to cover over parts of the window when there's perfectly unused screen real estate farther up.
Suppose I've solved the problem of detecting the keyboard appearing or disappearing, e.g. How to check visibility of software keyboard in Android? .
Currently I go to move the window out of the way by doing something like this:
...
final WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
params.gravity = Gravity.TOP;
params.verticalMargin = .1f; //or whatever
dialog.getWindow().setAttributes(params);
...
This works fine, but the window suddenly jerks into place, which isn't a pleasant UX experience. The window in question has a successful enter and exit animation - and these even work appropriately after the window layout change. How can I further animate the window between WindowManager.LayoutParams changes?
(I'd prefer if possible to keep working in terms of layout {of the} / {within the} http://developer.android.com/reference/android/view/Window.html rather than, say, forcing the DialogFragment into my activity's layout and animating it from within there).
I didn't have time to wait for the bounty to expire, so I coded the below stop gap until I can get a better solution. In case it helps anybody else, or gives them an idea for a bounty-worthy answer, this is what I did. However, I suspect its inefficient as all hell, since I assume it forces a window relayout every animation frame rather than just panning a bitmap across the screen. This isn't all of it of course, but is the critical bit:
// Not shown: setting currentVerticalMargin, targetVerticalMargin, or calling this method
private synchronized void restartVerticalMarginAnimator() {
if (verticalMarginAnimator != null) {
return;
}
final Dialog dialog = this.getDialog();
if (dialog == null) {
return;
}
final WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
verticalMarginAnimator = new TimeAnimator();
verticalMarginAnimator.setTimeListener(new TimeListener() {
#Override
public void onTimeUpdate(TimeAnimator a, long totalTime, long deltaTime) {
float stretch = targetVerticalMargin - currentVerticalMargin;
float distance = WINDOW_ANIMATION_SPEED * deltaTime / 1000L;
boolean finished = false;
// Adjust distance so it's capped at "going all the way to target" and no further,
// and has the right sign if we're animating upward.
if (distance > Math.abs(stretch)) {
distance = stretch;
finished = true;
} else if (stretch < 0) {
distance *= -1f;
}
// Move.
currentVerticalMargin += distance;
if (finished) {
verticalMarginAnimator.end();
verticalMarginAnimator = null;
}
params.verticalMargin = currentVerticalMargin;
dialog.getWindow().setAttributes(params);
}
});
verticalMarginAnimator.start();
}
Related
Using setTranslationX, I'm trying to animate a view as I swipe it across the screen. Then after it passes a threshold-X, I assign the view a new RelativeLayout.RIGHT_OF.
I want it to stop animating (whether or not I continue swiping) at that point and basically lock to that new anchor.
This is where the problem is: suddenly the view jumps X position to the right of its new anchor.
I've tried, when it's >= threshold, to set setTranslationX(0), but then I see the view twitch/flash twice, once to its original 0, then to the new 0.
I would love to get rid of that double twitch/flash, but don't know how at this point.
#Override
public void onChildDraw(Canvas c ... float dX) {
threshold = anchorView.getRight();
if (animate) {
if (dX >= 0) {
translationX = Math.min(dX, threshold);
if (dX >= threshold) {
translationX = 0; // (A) if I do this, then mainView flashs twice: original 0, then new 0
setToRightOf(mainView, anchorView);
mainView.invalidate(); // has no effect
}
} else {
translationX = 0;
}
// if I don't do (A), then mainView will suddenly jump to 2*threshold
mainView.setTranslationX(translationX);
return;
}
super.onChildDraw(c ... dX);
}
Okay, instead of assigning RelativeLayout.RIGHT_OF during onDraw to set the threshold boundary, I took it out and assigned it when my touch left the screen.
But to insure I wouldn't swipe back behind that threshold while swiping, I had to add another case to check translationX and instead of previously trying to rely on the RelativeLayout anchor.
Now, I'm using setTag() and getTag() to help confirm the threshold during the swipe:
if (dX >= 0) {
if ((Object) past != tag)
translationX = Math.min(dX, threshold);
else
translationX = threshold;
if (dX >= threshold) {
if ((Object) past != tag) {
anchorView.setTag(past);
}
}
} else {
...
}
Plus a couple other places to make sure I reset anchorView's tag and the translationX when needed, then it's all good.
It works for now!
(doesn't directly solve the double flash/twitch issue, but a different approach to the same goal)
(any other recommendations besides using setTag()?)
P.S. In my earlier attempts, instead of invalidate(), I later tried mainView.requestLayout() with no success either, thinking requestLayout() also factors in position.
I have an app that loads HTML content in three WebViews. For simplicity, let's call them top, middle, and bottom.
The user is always viewing the middle WebView. When the user reaches the top of the page and swipes down, the layout of the three WebViews change so that the top view is visible. Conversely, when the user reaches the bottom of the page and swipes up, the bottom page comes into view.
Imagine a 100x100 screen. Coordinate 0,0 is the top left of the screen and 100,100 is the bottom right of the screen. The top view will have a layout with the top at -105 so that it is not viewable, the middle view will occupy the screen, and the bottom view will have a layout with the top at 105 so that it is not viewable, as well.
topLayout.topMargin = -105;
middleLayout.topMargin = 0;
bottomLayout.topMargin = 105;
top.setLayoutParams(topLayout);
middle.setLayoutParams(middleLayout);
bottom.setLayoutParams(bottomLayout);
The content are books, so when the user changes pages, the content should flow logically. When scrolling backwards (up), the bottom of the previous page should be shown. When scrolling forwards (down), the top of the next page should be shown. This is accomplished through setting the WebView's scroll positions using scrollTo(x,y). The code looks like this, where a represents the bottom of the content and b represents the top of the content:
top.scrollTo(0, a);
middle.scrollTo(0, {a,b}); // a for previous page; b for next page
bottom.scrollTo(0, b);
When the user swipes to the previous page, the top WebView's layout changes to have a top of 0 to occupy the screen; the middle changes to 105, and the bottom changes to -105 and loads different content so the app will be prepared for a future previous swipe.
Now we actually come to my question. This works exactly as intended except in Android 4.4 (KitKat). In KitKat, it works for two swipes in either direction, but then on the third and subsequent swipe in the same direction, the content is loaded in the wrong position. When scrolling backwards, the content starts to load showing the top. When scrolling forwards, the content starts to load showing the bottom.
I have stepped through the debugger and noticed that the layouts are set properly, followed by the scroll positions being set correctly. Then, after those values are set, but before the content is actually drawn, something happens in the stack that changes the scroll position.
This is where I'm totally lost. Why are the scroll values getting set correctly, then magically changing before the screen is drawn?
I already tried using an onLayoutCompleteListener, it didn't work. I will update the list of things attempted as I receive answers and try the suggestions. Thank you in advance for any and all help.
Here's a summary of what I'm doing to change pages:
public class MyWebView extends WebView {
// Assume this is instantiated; it is by the time it is needed
private List<MyWebView> viewArray = new List<MyWebView>(3);
private class CustomGestureListener extends
GestureDetector.SimpleOnGestureListener {
private static final String DEBUG_TAG = "Gestures";
private int scrollYOnTouch;
private int scrollYOnRelease;
#Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(DEBUG_TAG, "onFling: " + event1.toString()
+ event2.toString());
Log.i(DEBUG_TAG, "onFling: vX[" + velocityX
+ "], vY[" + velocityY + "]");
scrollYOnRelease = getScrollY();
int bottomOfPage = scrollYOnTouch + getMeasuredHeight();
int endOfContent = (int) Math.floor(getContentHeight() * getScale());
int proximity = endOfContent - bottomOfPage;
boolean atBottom = proximity <= 1;
Log.i(DEBUG_TAG, "atBottom = (" + proximity + " <= 1)");
if ((velocityY > VELOCITY_THRESHOLD)
&& (scrollYOnRelease <= 0) && (scrollYOnTouch == 0)) {
// User flung down while at the top of the page.
// Go to the previous page.
changePages(PREVIOUS_PAGE);
} else if ((velocityY < -VELOCITY_THRESHOLD)
&& (scrollYOnRelease >= scrollYOnTouch) && atBottom) {
// User flung up while at the bottom of the page.
// Go to the next page.
changePages(NEXT_PAGE);
}
return true;
}
} // end of CustomGestureListener
#Override
public boolean onTouchEvent(MotionEvent event) {
// Send the event to our gesture detector
// If it is implemented, there will be a return value
this.mDetector.onTouchEvent(event);
// If the detected gesture is unimplemented, send it to the superclass
return super.onTouchEvent(event);
}
#Override
public void changePages(int direction) {
int screenWidth = getScreenWidth();
int screenHeight = getScreenHeight();
VerticalPagingWebView previous;
VerticalPagingWebView current;
VerticalPagingWebView next;
if (direction == NEXT_PAGE) {
// Rearrange elements in webview array
// Next page becomes current page,
// current becomes previous,
// previous becomes next.
Collections.swap(viewArray, 0, 1);
Collections.swap(viewArray, 1, 2);
previous = viewArray.get(0);
current = viewArray.get(1);
next = viewArray.get(2);
// Prepare the next page
next.loadData(htmlContent, "text/html", null);
} else if (direction == PREVIOUS_PAGE) {
// Rearrange elements in webview array
// Previous page becomes current page,
// current becomes next,
// next becomes previous.
Collections.swap(viewArray, 1, 2);
Collections.swap(viewArray, 0, 1);
previous = viewArray.get(0);
current = viewArray.get(1);
next = viewArray.get(2);
// Prepare the previous page
previous.loadData(htmlContent, "text/html", null);
}
LayoutParams previousLayout = (LayoutParams) previous.getLayoutParams();
previousLayout.leftMargin = LEFT_MARGIN;
previousLayout.topMargin = -screenHeight - TOP_MARGIN;
previous.setLayoutParams(previousLayout);
LayoutParams currentLayout = (LayoutParams) current.getLayoutParams();
currentLayout.leftMargin = LEFT_MARGIN;
currentLayout.topMargin = 0;
current.setLayoutParams(currentLayout);
LayoutParams nextLayout = (LayoutParams) next.getLayoutParams();
nextLayout.leftMargin = LEFT_MARGIN;
nextLayout.topMargin = screenHeight + TOP_MARGIN;
next.setLayoutParams(nextLayout);
previous.scrollToBottom();
next.scrollToTop();
// I'm unsure if this is needed, but it works on everything but KitKat
if (direction == NEXT_PAGE) {
current.scrollToTop();
} else {
current.scrollToBottom();
}
} // end of changePages
public void scrollToPageStart() {
scrollTo(0,0);
}
public void scrollToPageBottom() {
// I know getScale() is deprecated; I take care of it.
// This method works fine.
int endOfContent = (int) Math.floor(getContentHeight() * getScale());
int webViewHeight = getMeasuredHeight();
scrollTo(0, endOfContent - webViewHeight);
}
}
You're probably scrolling the WebView before it had finished loading the contents. The problem is that at some point during the page load the WebView resets the scroll (this is intentional, when you navigate between pages you don't want the scroll offset to persist) and sometimes this reset happens after you call scrollTo.
To fix this you have two options:
scroll from JavaScript (in window.onload or something), this ensures the scroll happens after the WebView has finished loading the contents,
wait for the contents to load before scrolling. This is harder since the WebView doesn't have reliable callbacks (onPageFinished will not work reliably). One option would be to poll for when the webview.getContentHeight() method is returning the height of your content before doing the scroll.
I'm having trouble using a Scroller to scroll a ScrollView programmatically, so no touch gestures are involved in this so far. I want to scroll the ScrollView down at a certain speed, as long as data from a sensor is in a certain range. So basically I want to start scrolling the first time the data enters the valid range and then not disturb the scrolling process until the data is out of the range again. I don't want to connect the onSensorChanged directly to a scrollBy() because it will probably not work right on other devices. Here's what I've got so far:
in my onCreate:
tx = new TextView(ShowLyrics.this);
mainscrollarea = (ScrollView) findViewById (R.id.mainscrollarea);
scroller = new Scroller(getApplicationContext(), new LinearInterpolator());
tx.setScroller(scroller);
in my onSensorChanged:
if(integratedAngle - scrollTolerance > pointzero){ //this is checking for the data range and works so far
if(!scrollingDown){
scrollText("down");
}
}
and the scrollText function:
void scrollText(String direction){
if(direction.matches("down")){
scrollingUp = false;
scrollingDown = true;
scroller.forceFinished(true);
int currY = mainscrollarea.getScrollY();
int endY = tx.getHeight();
int distance = endY - currY;
scroller.startScroll(0, currY, 0, -distance, 5000);
}
if(direction.matches("up")){
//nothing yet
}
}
So for now I've hardcoded 5 seconds for a scroll down, but nothing happens. A Log.d() of the Scroller's getCurrY in the onSensorChanged only spits out 0's. If someone could point me in the right direction, I would be thankful.
I kind of do an automated scrolling like you. Except I rely on user input (when the user is near the edge of the screen with his finger, I start scrolling at a specific speed).
I use a runnable which does the same as the scroller will do.
private final DragScroller mDragScroller;
/** inner class */
private class DragScroller implements Runnable {
private SCROLL_DIRECTION mDirection;
private boolean mIsFinished = true;
DragScroller(Context context) {
}
void start(SCROLL_DIRECTION direction) {
mState = STATE.DRAG_SCROLL;
mIsFinished = false;
mDirection = direction;
post(this);
}
#Override
public void run() {
if (mIsFinished) {
return;
}
if (mDirection.equals(SCROLL_DIRECTION.UP)) {
// check if the touch is still in the correct area...
if (!isOverThreshold(0, mTempY, mDragScrollThreshold)) {
scrollTo(0, ensureScrollBoundaries(getScrollY() - mDragScrollSpeed));
post(this);
} else {
forceFinish();
}
} else {
// check if the touch is still in the correct area...
if (!isOverThreshold(getHeight(), mTempY, mDragScrollThreshold)) {
scrollTo(0, ensureScrollBoundaries(getScrollY() + mDragScrollSpeed));
post(this);
} else {
forceFinish();
}
}
}
public boolean isFinished() {
return mIsFinished;
}
public void forceFinish() {
mIsFinished = true;
}
}
It is simply started by: mDragScroller.start(SCROLL_DIRECTION.UP); and can be stopped by mDragScroller.forceFinish();
edit
Based on your comment, you want to use the duration for the speed. This is kind of problematic because the resulting speed of the scroll depends on the distance you have to scroll in your given time. Short math sample: Scrolling 600px in 1 minute means you scroll 10px per second which is not that bad (depends on what you scroll, text or image...) but if you are near the edge and you need to scroll only 60px, the resulting speed depending on the given duration of 1min means very slow 1px per second.
Given that example you should base your scroll speed not on total duration but on pixel per second.
And yes, there is no need to use a Scroller for programmatically scrolling. Just the runnable which will call itself until it should stop and the speed can be adjusted to what ever you need...
As I've a master in MS Paint, I will just upload a picture selfdescripting what I'm trying to achieve.
I've searched, but I'm not really sure what do I've to search. I've found something called Animations. I managed to rotate, fade, etc an element from a View (with this great tutorial http://www.vogella.com/articles/AndroidAnimation/article.html)
But this is a bit limited for what I'm trying to achieve, and now, I'm stuck, because I don't know how is this really called in android development. Tried words like "scrollup layouts" but I didn't get any better results.
Can you give me some tips?
Thank you.
You can see a live example, with this app: https://play.google.com/store/apps/details?id=alexcrusher.just6weeks
Sincerely,
Sergi
Use something like this as your layout (Use Linear, Relative or other layout if you wish):
<LinearLayout
android:id="#+id/lty_parent">
<LinearLayout
android:id="#+id/lyt_first" />
<LinearLayout
android:id="#+id/lyt_second"/>
</LinearLayout>
And then in an onClick method on whatever you want to use to control it, set the Visibility between Visible and Gone.
public void buttonClickListener(){
((Button) findViewById(R.id.your_button))
.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (lyt_second.getVisibility() == View.GONE) {
lyt_second.setVisibility(View.VISIBILE);
}
else {
lyt_second.setVisibility(View.GONE);
}
});
Which is fine if you just want a simple appear/disappear with nothing fancy. Things get a little bit more complicated if you want to animate it, as you need to play around with negative margins in order to make it appear to grow and shrink, like so:
We use the same onClick method that we did before, but this time when we click it starts up a custom SlideAnimation for the hidden/visible view.
#Override
public void onClick(View v) {
SlideAnimation slideAnim = new SlideAnimation(lyt_second, time);
lyt_second.startAnimation(slideAnim);
}
The implementation of the SlideAnimation is based on a general Animation class, which we extend and then Override the transformation.
public SlideAnimation(View view, int duration) {
//Set the duration of the animation to the int we passed in
setDuration(duration);
//Set the view to be animated to the view we passed in
viewToBeAnimated = view;
//Get the Margin Parameters for the view so we can edit them
viewMarginParams = (MarginLayoutParams) view.getLayoutParams();
//If the view is VISIBLE, hide it after. If it's GONE, show it before we start.
hideAfter = (view.getVisibility() == View.VISIBLE);
//First off, start the margin at the bottom margin we've already set.
//You need your layout to have a negative margin for this to work correctly.
marginStart = viewMarginParams.bottomMargin;
//Decide if we're expanding or collapsing
if (marginStart == 0){
marginEnd = 0 - view.getHeight();
}
else {
marginEnd = 0;
}
//Make sure the view is visible for our animation
view.setVisibility(View.VISIBLE);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
// Setting the new bottom margin to the start of the margin
// plus the inbetween bits
viewMarginParams.bottomMargin = marginStart
+ (int) ((marginEnd - marginStart) * interpolatedTime);
// Request the layout as it happens so we can see it redrawing
viewToBeAnimated.requestLayout();
// Make sure we have finished before we mess about with the rest of it
} else if (!alreadyFinished) {
viewMarginParams.bottomMargin = marginEnd;
viewToBeAnimated.requestLayout();
if (hideAfter) {
viewToBeAnimated.setVisibility(View.GONE);
}
alreadyFinished = true;
}
hideAfter = false;
}
}
EDIT: If anyone had used this code before and found that if you click on the button that starts the animation more than once before the animation was finished, it would mess up the animation from then on, causing it to always hide the view after the animation finished. I missed the reset of the hideAfter boolean near the bottom of the code, added it now.
you can do this manually by using setvisibility feature on the event onClick()
or
use this
dynamically adding two views one below other
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;