I have a faux dialog which uses this layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="#+id/containerPageConatiner">
<FrameLayout android:id="#+id/dialogHolder"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="15dp"
android:layout_gravity="center"
android:background="#drawable/panel_picture_frame_bg_focus_blue"/>
</FrameLayout>
I place a fragment inside the <FrameLayout> depending on the dialog which is opening - The activity controlling the Dialog looks like this:
<activity
android:label="#string/app_name"
android:name=".activity.DialogActivity"
android:theme="#style/CustomTheme.Screen.Transparent"
android:windowSoftInputMode="adjustResize">
Unfortunately when you click on an edit text inside of the dialog, no resizing takes place. The windowSoftInputMode literally makes no difference as the functionality is the same as pan mode.
Documentation says "This of course only works for applications that have a resizeable area that can be reduced to make enough space" but doesn't tell you what it means by "a resizeable area" and makes me think that in some way I don't have a resizeable area?
If anyone knows what's up can they help me out?
EDIT
Surrounding the dialog like so doesn't change anything:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/containerPageConatiner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<FrameLayout
android:id="#+id/dialogHolder"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="15dp"
android:layout_gravity="center"
android:background="#drawable/panel_picture_frame_bg_focus_blue"/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
EDIT2
Scrollview as parent doesn't help either:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/containerPageConatiner"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/dialogHolder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="15dp" />
</ScrollView>
I created a new project in order to try and get the basic features working for window resizing and the slowly moved it towards the target peice of my project. Doing this I tracked the problem down to this:
In my theme hierarchy I had this property:
<item name="android:windowFullscreen">true</item>
which was burried at the level of Theme.Black.NoTitleBar.FullScreen - An ancestor of my custom theme.
The documentation suggests that this is a "Flag indicating whether this window should fill the entire screen". That sounds like a good thing to have if you have an app which takes up the whole screen... Except it still takes up the whole screen without the flag.
In fact, once you've taken this out, there is absolutely no change in the app at all... apart from adjustResize now works perfectly.
A while back i also had the same issue in a library i've created. (MaterialDrawer)
As far as i can see all the provided answers don't solve the main issue, they just point to remove the fullscreen flag (android:windowFullscreen), which is no solution for many out there.
The above mentioned "issue" only appears in Android versions starting with API Level 19 (KITKAT), because they changed the behavior. To be correct it is no issue, it is working as intended. See the comment by a Android employee (Android-Issue).
So i started digging around the Android source and came to the following solution by using the ViewTreeObserver.OnGlobalLayoutListener and reacting if the Keyboard gets shown / or hidden. If the Keyboard gets shown i add the padding to the bottom of the container view which will then emulate the same as the adjustResize would do.
Solution
To simplify the usage i've wrapped the whole thing in a simple KeyboardUtil helper class.
/**
* Created by mikepenz on 14.03.15.
* This class implements a hack to change the layout padding on bottom if the keyboard is shown
* to allow long lists with editTextViews
* Basic idea for this solution found here: http://stackoverflow.com/a/9108219/325479
*/
public class KeyboardUtil {
private View decorView;
private View contentView;
public KeyboardUtil(Activity act, View contentView) {
this.decorView = act.getWindow().getDecorView();
this.contentView = contentView;
//only required on newer android versions. it was working on API level 19 (Build.VERSION_CODES.KITKAT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}
}
public void enable() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}
}
public void disable() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
decorView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
}
}
//a small helper to allow showing the editText focus
ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
decorView.getWindowVisibleDisplayFrame(r);
//get screen height and calculate the difference with the useable area from the r
int height = decorView.getContext().getResources().getDisplayMetrics().heightPixels;
int diff = height - r.bottom;
//if it could be a keyboard add the padding to the view
if (diff != 0) {
// if the use-able screen height differs from the total screen height we assume that it shows a keyboard now
//check if the padding is 0 (if yes set the padding for the keyboard)
if (contentView.getPaddingBottom() != diff) {
//set the padding of the contentView for the keyboard
contentView.setPadding(0, 0, 0, diff);
}
} else {
//check if the padding is != 0 (if yes reset the padding)
if (contentView.getPaddingBottom() != 0) {
//reset the padding of the contentView
contentView.setPadding(0, 0, 0, 0);
}
}
}
};
/**
* Helper to hide the keyboard
*
* #param act
*/
public static void hideKeyboard(Activity act) {
if (act != null && act.getCurrentFocus() != null) {
InputMethodManager inputMethodManager = (InputMethodManager) act.getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(act.getCurrentFocus().getWindowToken(), 0);
}
}
}
You can then use it in your activity or fragment by doing the following:
//initialize the KeyboardUtil (you can do this global)
KeyboardUtil keyboardUtil = new KeyboardUtil(activity, getContent().getChildAt(0));
//enable it
keyboardUtil.enable();
//disable it
keyboardUtil.disable();
The whole util class is used in the above mentioned library MaterialDrawer and can be found here KeyboardUtil. This will always contain the latest version. (if there are improvements)
It seems that the problem is with FrameLayout, as it behaves that way, that each child occupying visible space of that frame, therefore no need to resize to fit children.
Try to use RelativeLayout. It should work.
Without using a ScrollView as my parent I just added android:fitsSystemWindows="true" to my parent view (which was a RelativeLayout) and adjustResize to the Manifest for the activity and it worked.
Try to put your LinearLayout on a ScrollView, that worked for me once..
I've had to set
<item name="android:windowFullscreen">false</item>
Despite, I've never set it to true and app actually wasn't full screen.
As original poster discovered when the Fullscreen Flag is assigned to an activity the android:windowFullscreen attribute will not work and so your window will not resize when the soft keyboard is visible and it won't be scrollable.
Simply removing the Fullscreen flag and not using a Fullscreen theme will allow scrolling when the soft keyboard is visible.
I don't know why, but if you have <item name="android:windowTranslucentNavigation">true</item> in your theme, change it to false. And it will start working. Really strange.
Ensure you set windowTranslucentStatus to false in your styles.
Related
It seems like the official way, to listen to keyboard height change, is to use WindowInsetsCompat based on https://developer.android.com/develop/ui/views/layout/sw-keyboard
( An unofficial is to have an invisible PopupWindow to monitor keyboard height change. But, this is not a reliable method due to today numerous devices with different notch, split screen mode, ... - Is there any way in Android to get the height of virtual keyboard of device )
We try to experiment, to see how we can monitor keyboard height correctly.
Before applying WindowInsetsCompat
After applying WindowInsetsCompat with the following code.
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View keyboardView = findViewById(R.id.keyboard_view);
ViewCompat.setOnApplyWindowInsetsListener(getWindow().getDecorView().getRootView(), (v, insets) -> {
boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
android.util.Log.i("CHEOK", "imeVisible = " + imeVisible + ", imeHeight = " + imeHeight);
ViewGroup.LayoutParams params = keyboardView.getLayoutParams();
params.height = imeHeight;
keyboardView.setLayoutParams(params);
return insets;
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top" />
<LinearLayout
android:id="#+id/bottom_linear_layout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:background="#22000000">
<ImageButton
android:id="#+id/image_button_0"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/actionBarItemBackground"
android:src="#drawable/ic_baseline_alarm_on_24" />
</LinearLayout>
<View
android:id="#+id/keyboard_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#ff0000" />
</LinearLayout>
Outcome of WindowInsetsCompat (When keyboard is not visible)
Outcome of WindowInsetsCompat (When keyboard is visible)
Here's the observation
For testing purpose, we do not use android:windowSoftInputMode="adjustResize".
After applying WindowInsetsCompat, the top status bar and bottom soft key background become white!
The returned keyboard's height isn't correct. (If the keyboard height is correct, we shouldn't see the red color keyboardView, because we have set the height of keyboardView to be same as keyboard's height)
May I know, when using WindowInsetsCompat to monitor keyboard's height, how can I
Avoid status bar and bottom soft key background from becoming white?
Get the correct keyboard's height?
The following is the workable demo - https://github.com/yccheok/wediary-sandbox/tree/master/keyboard-bottom-sheet-integration
Thanks.
Avoid status bar and bottom soft key background from becoming white?
This requires to readjust the insets to WindowInsetsCompatby returning ViewCompat.onApplyWindowInsets(v, insets) instead of insets from the setOnApplyWindowInsetsListener() callback.
Get the correct keyboard's height?
The insets are calculated based on the far ends of the screen (it makes sense as they're named as window insets, not the activity insets).
So, in case of the ime/keyboard we are interested in the bottom inset which equals to the top edge of the keyboard to the bottom edge of the screen; this includes the system navigation insets (i.e., the height of the navigation bar).
The red view is shown because it's drawn on top of the navigation bar; it doesn't take the navBar height into consideration.
So, to get the exact height of the keyboard, we need to subtract the navBarHeight from the imeHeight:
int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
- insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
Please notice the quote of the documentation about ime insets:
When running on devices with API Level 29 and before, the returned
insets are an approximation based on the information available. This
is especially true for the IME type, which currently only works when
running on devices with SDK level 23 and above.
I have set in XML visibility="gone" for the View. On the first click on the button (), the View should become visible (it works) viewView.visibility = VISIBLE. The second time you click on the button, the view should become "gone" again viewView.visibility = GONE.
But instead, the View doesn't disappear, but just becomes transparent, as if I had set invisible (look at the illustration from Layout Inspector).
How can I make the View truly "gone" like in the first frame?
Please look the illustration from Layout Inspector
Example code
var isVisible = false
val button = findViewById<View>(id.button)
val view = findViewById<View>(id.view)
button.setOnClickListener {
view.visibility = if (isVisible) { GONE } else { VISIBLE }
isVisible = !isVisible
}
And XML
<Button
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2"
android:background="#color/design_default_color_primary" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.4" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2">
<View
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/design_default_color_secondary"
android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
I think you're worrying too much about what's happening behind the scenes. Here's what happens if you add a TextView below that View:
Yes view still has its full size, but it's not actually affecting the layout. That's what GONE means - it's not visible, but it's also behaving as though it's not there, and everything else is laid out accordingly.
That doesn't mean the view itself has been recalculated with zero height or anything - for one thing, removing something doesn't change its height, right? It's just not in the layout anymore, so the space it's taking up is now zero. And there's also no reason to recalculate the height while the View isn't being displayed, it would be wasted work. It's just there, hanging around, waiting to be added to the layout again.
It's not like you can click it while it's in this state anyway, so what it's actually doing and how the system is managing stuff shouldn't be relevant. If your code does somehow rely on the View being "gone" in some sense (not present in the layout, garbage collected, zero width and height) then you'll have to account for determining that yourself, and whatever you're doing now isn't enough. You could check its visibility property remember!
Also just as an aside, be careful about what you name things - an ID called view and especially a variable called view inside a class that already has a view property is just asking for trouble. Same with a variable called isVisible being referenced in the scope of a Button and a Fragment, both of which have a property with the same name (and you'll get a warning about this one, accidental override)
It just leads to bugs when you accidentally reference the wrong thing. If these are just placeholder names you're using for your question, bear in mind people will copy your code to try and help you, and you're making work for them when they have to fix it. Not having a go or anything, just something to keep in mind!
try this:
button.setOnClickListener { v ->
v?.let {
val isViewVisible: Boolean = view.isVisible
view.isVisible = !isViewVisible
}
}
I have a BottomSheetView which has animateLayoutChanges="true". Initially it shows up fine. But if change the visibility of a view (inside BottomSheetView) from GONE to VISIBLE, the app messes up calculations and my BottomSheetView moves to the top of the screen. i have tried setting layout_gravity=bottom at the root of the BottomSheetView layout. But no success.
Here I have the image of my BottomSheetView before changing the visibility of any view. (Click image for full size)
After I change the visibility of a view (GONE to VISIBLE or VISIBLE to GONE), my BottomSheetView moves to the top. (Click image for full size)
I guess, Android is messing up while making calculations about the measurement of view width and height. Any way to solve this??
I also tried to make my BottomSheetView extend fully to match the parent view, but somehow that is making the height of the BottomSheetView longer than the phone screen and in-tun creating scrolling issues.
Expected solutions:
1> Prevent BottomSheetView from changing its position even when the visibility of a view is changed.
OR
2>Make the BottomSheetView match parent so that it does not look bad after messing up with the calculations.
The BottomSheetBehavior does not work well with LayoutTransition (animateLayoutChanges="true") for now. I will work on a fix.
For now, you can use Transition instead. Something like this will fade the view inside and animate the size of the bottom sheet.
ViewGroup bottomSheet = ...;
View hidingView = ...;
TransitionManager.beginDelayedTransition(bottomSheet);
hidingView.setVisibility(View.GONE);
You can refer to Applying a Transition for more information including how to customize the animation.
I was running into the same issue and determined to find a fix. I was able to find the underlying cause but unfortunately I do not see a great fix at the moment.
The Cause:
The problem occurs between the bottomsheet behavior and the LayoutTransition. When the LayoutTransition is created, it creates a OnLayoutChangeListener on the view so that it can capture its endValues and setup an animator with the proper values. This OnLayoutChangeListener is triggered in the bottomSheetBehavior's onLayout() call when it first calls parent.onLayout(child). The parent will layout the child as it normally would, ignoring any offsets that the behavior would change later. The problem lies here. The values of the view at this point are captured by the OnLayoutChangeListener and stored in the animator. When the animation runs, it will animate to these values, not to where your behavior defines. Unfortunately, the LayoutTransition class does not give us access to the animators to allow updating of the end values.
The Fix:
Currently, I don't see an elegant fix that involves LayoutTransitions. I am going to submit a bug for a way to access and update LayoutTransition animators. For now you can disable any layoutTransition on the parent container using layoutTransition.setAnimateParentHierarchy(false). Then you can animate the change yourself. I'll update my answer with a working example as soon as I can.
The question was asked more than two years ago, but unfortunately the problem persists.
I finally got a solution to keep the call to the addView and removeView functions in a BottomSheet, while having animateLayoutChanges="true".
BottomSheetBehavior cannot calculate the correct height when it changes, so the height must remain the same. To do this, I set the height of the BottomSheet to match_parent and divide it into two children: the content and a Space that changes height according to the height of the content.
To best mimic the true behavior of a BottomSheet, you also need to add a TouchToDismiss view that darkens the background when the BottomSheet is extended but also to close the BottomSheet when the user presses outside the content.
Here's the code:
activity.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="#+id/show_bottom_sheet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show bottom sheet"/>
<View
android:id="#+id/touch_to_dismiss"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:background="#9000"/>
<LinearLayout
android:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<Space
android:id="#+id/space"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:id="#+id/bottom_sheet_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:animateLayoutChanges="true">
<Button
android:id="#+id/add_or_remove_another_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add another view"/>
<TextView
android:id="#+id/another_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Another view"/>
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
activity.java
BottomSheetBehavior bottomSheetBehavior;
View touchToDismiss;
LinearLayout bottomSheet;
Button showBottomSheet;
Space space;
LinearLayout bottomSheetContent;
Button addOrRemoveAnotherView;
TextView anotherView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
touchToDismiss = findViewById(R.id.touch_to_dismiss);
touchToDismiss.setVisibility(View.GONE);
touchToDismiss.setOnClickListener(this);
bottomSheet = findViewById(R.id.bottom_sheet);
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
bottomSheetBehavior.setPeekHeight(0);
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN || newState == BottomSheetBehavior.STATE_COLLAPSED) {
touchToDismiss.setVisibility(View.GONE);
}else {
touchToDismiss.setVisibility(View.VISIBLE);
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
touchToDismiss.setAlpha(getRealOffset());
}
});
showBottomSheet = findViewById(R.id.show_bottom_sheet);
showBottomSheet.setOnClickListener(this);
space = findViewById(R.id.space);
bottomSheetContent = findViewById(R.id.bottom_sheet_content);
addOrRemoveAnotherView = findViewById(R.id.add_or_remove_another_view);
addOrRemoveAnotherView.setOnClickListener(this);
anotherView = findViewById(R.id.another_view);
bottomSheetContent.removeView(anotherView);
}
#Override
public void onClick(View v) {
if (v == showBottomSheet)
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
else if (v == addOrRemoveAnotherView) {
if (anotherView.getParent() == null)
bottomSheetContent.addView(anotherView);
else
bottomSheetContent.removeView(anotherView);
}
else if (v == touchToDismiss)
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
/**
* Since the height does not change and remains at match_parent, it is required to calculate the true offset.
* #return Real offset of the BottomSheet content.
*/
public float getRealOffset() {
float num = (space.getHeight() + bottomSheetContent.getHeight()) - (bottomSheet.getY() + space.getHeight());
float den = bottomSheetContent.getHeight();
return (num / den);
}
This is the result obtained with this code:
Hopefully it will be useful to someone since the problem is still there!
I use this and I got to keep the animations!
val transition = LayoutTransition()
transition.setAnimateParentHierarchy(false)
{Parent View which has animateLayoutChanges="true" }.layoutTransition = transition
In the BottomSheetDialog default layout (design_bottom_sheet_dialog) there is a TOP gravity on the dialog's design_bottom_sheet FrameLayout:
android:layout_gravity="center_horizontal|top"
I don't really know why on BottomSheetDialog gravity is top.
You need to create the same layout file (same content and name) in your project and replace this line with:
android:layout_gravity="center_horizontal|bottom"
In our case, we were displaying a progress bar on a button component. This was hiding the button text and displaying progress bar. This was causing a jump in bottom sheet fragment. Using Invisible instead of Gone fixed the problem.
If your view allows, make the visibility INVISIBLE instead of GONE.
Background
I have a rather complex layout being shown to the user in an activity.
One of the views is an EditText.
Since I had to make one of the views stay behind the soft-keyboard, yet the rest above it, I had to listen to view-layout changes (written about it here).
The problem
I've noticed that whenever the EditText has focus and shows its caret, the entire view-hierarchy gets re-layout.
You can see it by either looking at the log of the listener I've created, or by enabling "show surface updates" via the developers settings.
This causes bad performance on some devices, especially if the layout of the activity is complex or have fragments that have complex layouts.
The code
I'm not going to show the original code, but there is a simple way to reproduce the issue:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.user.myapplication.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="just some text"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone"
android:text="write here"
android:textSize="18dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="just some text 2"/>
</LinearLayout>
</FrameLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(android.R.id.content).getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
Log.d("AppLog", "onPreDraw");
return true;
}
});
}
}
What I've tried
When disabling the caret (using "cursorVisible", which for some reason is called a "cursor" instead) , I can see that the problem doesn't exist.
I've tried to find an alternative to the built-in caret behavior, but I can't find. Only thing I've found is this post, but it seems to make it static and I'm not sure as to how well it performs (performance and compatibility vs normal caret).
I've tried to set the size of the EditText forcefully, so that it won't need to cause invalidation of the layout that contains it. It didn't work.
I've also noticed that on the original app, the logs can (for some reason) continue being written even when the app goes to the background.
I've reported about this issue (including sample and video) here, hoping that Google will show what's wrong or a fix for this.
The question
Is there a way to avoid the re-layout of the entire view hierarchy ? A way that will still let the EditText have the same look&feel of normal EditText?
Maybe a way to customize how the EditText behaves with the caret?
I've noticed that whenever the EditText has focus and shows its caret,
the entire view-hierarchy gets re-layout.
This is not true. Size and position of EditText is constant - there is no re-layouting. You can check it by using code below.
findViewById(android.R.id.content).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Log.d("AppLog", "Layout phase");
}
});
Because of blinking caret - EditText constatly calls invalidate(). This forces the GPU to draw EditText again.
On my nexus 5 (marshmallow) I see that only EditText beeing redrawn (Show GPU view updates - enabled).
How about overriding dispatchOnPreDraw() all the vies you use in activity and having flag to check whether that specific view needs to redraw ?
As you need to disable redraw of all other views only when a text view is on focus. So when a text view is on focus have a flag to disable the redrawing of other views.
if dispatchOnPreDraw() method returns false then refreshing of view will be continues else not. I don't know how complex is your layout and how many views are used, but here a separate class should extent a view used and override that method, and also need a mechanism/variable to distinguish the object in current focus.
Hope this method helps!
I need to build a layout that fills the screen. I do that using a LinearLayout with weights in their child views.
This layout is a form, so I also need it to be scrollable when the soft keyboard is out.
What happens is that the screen height is shrunk when the keyboard is out, so the views are shrunk too, crowded together. They look awful.
Do you know any way to build a form layout that fills the screen but at the same time preserves its aspect and can be scrollable when the soft keyboard is out? This is really easy in iOS but in Android... I can't think of a solution.
Thank you
iOS always has the same screensize, Android doesnt. You need to use a responsive design in Android since you dont know how big the screen is that the user is using.
I dont think you need weights(not verticle anyway) to fix this.
Just use a ScrollView and add a LinearLayout(orientation:vertical) inside it.
Add your views inside this LinearLayout, and if you need to have multiple views horizontally. Add another LinearLayout(orientation:horizontal)
Set margin on your views to get a nice distance between them.
This will allow users that have small screens to scroll in your layout, and if the user has the keyboard out they can scroll aswell.
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
The first two will lie beneath each other.
The next two will lie next to each other, apply weights here if you want them to be of different sizes
Define your form within scroll view and in manifest make the windowSoftInputMode, configChanges entries as shown below
<activity
android:name="com.example.MainActivity"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize"
android:configChanges="keyboardHidden|orientation">
</activity>
Hopefully this can help you.
I have built the following scheduleConsolideHeights method to solve the problem. Thanks to Joakim for his comments.
Although it works fine for me, I'm sure it can be improved. Any suggestion will be welcome.
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.XXX);
// LinearLayout from your R.layout.XXX that has vertical orientation and
// its children have weights instead of absolute heights
final LinearLayout layout = (LinearLayout) findViewById(R.id.layout_with_weights);
scheduleConsolideHeights(layout);
}
/**
* Adds a listener to the layout {#link ViewTreeObserver} to wait for the view measurements
* and then calls {#link #consolideHeights(LinearLayout)}.
*/
private static void scheduleConsolideHeights(final LinearLayout layout) {
final ViewTreeObserver viewTreeObserver = layout.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
consolideHeights(layout);
}
});
}
}
/**
* Note: you might want to use {#link #scheduleConsolideHeights(android.widget.LinearLayout)}.
*
* Takes a {#link android.widget.LinearLayout} and, for each of its child views, sets the height
* of the child's LayoutParams to the current height of the child.
*
* Useful when a {#link android.widget.LinearLayout} that has relative heights (with weights)
* may display the keyboard. With absolute heights, the layout maintains its aspect when the soft
* keyboard appears.
*
* See: http://stackoverflow.com/q/22534107/1121497
*/
private static void consolideHeights(LinearLayout layout) {
for (int i = 0; i < layout.getChildCount(); i++)
{
final View child = layout.getChildAt(i);
final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) child.getLayoutParams();
params.height = child.getHeight();
params.weight = 0;
child.setLayoutParams(params);
}
}
}
As SiGangteng response in that question that must help you to solve the problem (inside the specific in the manifest)
android:windowSoftInputMode="adjustPan"