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.
Related
I have a chat app and i'm trying to ensure that when the user opens the screen the items display from the bottom with the most recent right above the input area. I've used a custom view for the input area and both the recycler and the custom view are within a ConstraintLayout.
My problem is that when I load items into the list, if the number of items is greater than the size of the recycler, it will not fully show the last item. When I make the input area visibility = Gone then the items display properly at the bottom. It's almost like the recyclers LinearLayoutManager thinks that the height of the Recycler is of the screen without the input field. I've manually printed out the size of the views and used layout inspector to ensure that the recycler is indeed drawn in the correct location (above the input and below the navigation bar).
What could be causing such an issue? I should note that whenever you click on a Linkified text in a chat bubble that the list scrolls a small amount equal to the offset that's incorrect when you open the screen. Clearly something is not measuring right here and not sure where to begin.
I should also note that if I try to add a post with smoothScroll it will go to the end of the list but then whenever a new item appears in the list from sending a message the items above the most recently added one seem to jump up a little with an unnecessary animation. It's like the last item in the list is in some special state?
if you're curious this is my scrolling function:
private fun scrollToFirstItem(dueToKeyboard: Boolean = false) {
val stackingFromEnd = (recyclerView.layoutManager as LinearLayoutManager).stackFromEnd
if (stackingFromEnd) {
val firstPosition = recyclerView.adapter?.itemCount?: 0 - 1
if (dueToKeyboard ) {
recyclerView.scrollBy(0, Integer.MAX_VALUE)
} else {
recyclerView.scrollToPosition(firstPosition)
}
recyclerView.post { recyclerView.smoothScrollToPosition(firstPosition) }
} else {
recyclerView.scrollToPosition(0)
}
}
And my xml for my fragment:
<androidx.constraintlayout.widget.ConstraintLayout
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">
<include
android:id="#+id/searchView"
layout="#layout/compose_new_message_include"/>
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="#+id/conversationEpoxyRV"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layout_constraintTop_toBottomOf="#id/searchView"
app:layout_constraintBottom_toTopOf="#id/composeView"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
tools:listitem="#layout/conversation_item_inbound"/>
<include layout="#layout/conversation_pip_view"
android:id="#+id/selectedMediaContainer"/>
<****.ComposeView
android:id="#+id/composeView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Any help is appreciated, I'm so lost..
To me this feels like more of an issue in the layout rather than in the scrolling function. I hope that this can be resolved easily if you use a Relative Layout instead of a Linear Layout. So in case if it may be helpful, i'll add my solution below using a Relative Layout.
<RelativeLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/input_field">
</androidx.recyclerview.widget.RecyclerView>
<EditText
android:id="#+id/input_field"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
So in case if it is not clear what i have done, an EditText element is set to align to the bottom of parent which is a Relative Layout. Then a RecyclerView is added so that RecylerView will be always constraint above the EditText but not overlapped.
Everything looks fine, except the restrictions you added to recycleview design, I see that you are setting the recycleview to top of searchView app:layout_constraintTop_toBottomOf="#id/searchView" while the searchView view is not restricted as should it be.
It's better when using ConstraintLayout to restrict every view inorder avoid unexpected behaviors, because every view has a relation with other view will be effected with other view (x, y), therefore your searchView should be look like:
<include
android:id="#+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
layout="#layout/compose_new_message_include"/>
Give the size of arraylist of your data to smoothScrollToPosition
var linearLayoutManager = LinearLayoutManager(context)
recyclerView.layoutManager=linearLayoutManager
recyclerView.adapter=adapter
linearLayoutManager.setStackFromEnd(true)
adapter.notifyDataSetChanged()
recyclerView.smoothScrollToPosition(dataMsgs.size())
I had a similar problem some years back and I solved it with scrollToPositionWithOffset (support library). Of course this was before constraint was used....
I kept my newer items at the bottom, too. The code I used for scrolling after inserting an item was:
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(getItemCount() - 1, 0);
To scroll and adjust the awkward positioning after removing an item, I used:
View vTopCard = layoutManager.getChildAt(0);
// Get its adapter position
final int topCardPos = layoutManager.getPosition(vTopCard);
// Get top of card position
int topOffset = layoutManager.getDecoratedTop(vTopCard);
int cardMargin = (int) getResources().getDimension(R.dimen.card_vertical_margin);
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(topCardPos, topOffset - cardMargin);
The getDecoratedTop helps with the "bounce" and final positioning, as does factoring the vertical margin.
I hope this helps (at least part of your issue)! Like I said, this is old code (when I was learning to program Android and Java at the same time), so if I left something out, let me know and I'll reexamine the app's code in more detail (though, I'll have to find the time).
Good luck!
Let's say that my Bottom Sheet has lines of widgets like the following. If I want to show only the first two lines (i.e., the first two LinearLayouts) initially, but not the rest of the widgets below. I do not want those to be seen initially. How can I set the correct peek height? Hard-coding app:behavior_peekHeight probably would not work, so I would need to set it programatically, but how to calculate the height?
Or is there a more recommended way to get the same result? I mean, if I test Google Maps, long pressing a location first shows only the title part as the bottom sheet, but when I try to scroll up the bottom sheet, it feels as if the title part (which might not have been a real bottom sheet) is replaced by a real bottom sheet that contains all the elements. If my explanation is not enough, please try Google Maps yourself.
<android.support.v4.widget.NestedScrollView
android:id="#+id/bottom_sheet"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView/>
<android.support.v7.widget.AppCompatSpinner/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView/>
<TextView/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView/>
<TextView/>
</LinearLayout>
<android.support.v7.widget.RecyclerView/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
I would solve this by using a ViewTreeObserver.OnGlobalLayoutListener to wait for your bottom sheet to be laid out, and then calling BottomSheetBehavior.setPeekHeight() with the y-coordinate of the first view you don't want to see.
private BottomSheetBehavior<View> behavior;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View bottomSheet = findViewById(R.id.bottomSheet);
behavior = BottomSheetBehavior.from(bottomSheet);
final LinearLayout inner = findViewById(R.id.inner);
inner.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
inner.getViewTreeObserver().removeOnGlobalLayoutListener(this);
View hidden = inner.getChildAt(2);
behavior.setPeekHeight(hidden.getTop());
}
});
}
In this case, my bottom sheet is a NestedScrollView holding a LinearLayout that holds many TextViews. By setting the peek height to be the top of the third TextView (obtained by getChildAt(2)), my bottom sheet winds up showing exactly two TextViews while collapsed.
Customized #Ben P.'s answer to target a view id as a reference of the peekHeight and made a function:
/**
* Gets the bottom part of the target view and sets it as the peek height of the specified #{BottomSheetBehavior}
*
* #param layout - layout of the bottom sheet.
* #param targetViewId - id of the target view. Must be a view inside the 'layout' param.
* #param behavior - bottom sheet behavior recipient.
*/
private fun <T : ViewGroup> getViewBottomHeight(layout: ViewGroup,
targetViewId: Int,
behavior: BottomSheetBehavior<T>) {
layout.apply {
viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
behavior.peekHeight = findViewById<View>(targetViewId).bottom
}
})
}
}
In our use case, we needed to target the bottom part of the view, so we set it that way. It can be adjusted depending on the use-case.
That's smart!
My problem was trying to getTop() or getHeight() at wrong timing, it returns 0 if the view is not ready.
And yes, use viewTreeObserver to avoid that.
This is actually no different with #Ben P.'s previous answer, just a kotlin version:
class MyBottomSheetDialog() : BottomSheetDialogFragment(){
private val binding by viewBinding(SomeLayoutViewBinding::bind)
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(this.dialog as BottomSheetDialog).behavior.let { behavior ->
/* Set "pivotView" as interested target and make it the pivot of peek */
binding.pivotView.viewTreeObserver.addOnGlobalLayoutListener(object :
OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.pivotView.viewTreeObserver.removeOnGlobalLayoutListener(this)
behavior.peekHeight = binding.pivotView.top
}
})
}
}
}
I am using a RecyclerView to show a list of videos.
Each item in the list holds Video and SeekBar (and more stuff actually but not relevant here) in a RelativeLayout, as follows:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/performance"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.my.company.VideoView
android:id="#+id/the_video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:keepScreenOn="true"
android:scaleType="fitCenter" />
<SeekBar
android:id="#+id/the_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:alpha="1.0"
android:maxHeight="#dimen/seekbar_height"
android:minHeight="#dimen/seekbar_height"
android:padding="0dp"
android:progressDrawable="#drawable/my_progressbar"
android:thumb="#drawable/my_progressbar_circle"
android:translationY="-5dp" />
</RelativeLayout>
As you can see I added a android:translationY property that brings the SeekBar up a little so it would be partially positioned on top of the previous cell, i.e. the previous Video.
However it remains partially hidden. I can only see the part that is in the RelativeLayout in which is it declared.
I tried calling bringToFront() on the seekbar and on the RelativeLayout (performance) itself - but that did not help.
Probably the question is not relevant to RecyclerView only. Being somewhat new in android dev I am not sure if I can place a view that is declared inside a RelativeLayout to show up outside of its borders.
Hope I was clear, need your help. Tx.
By default, every view is clipped to its parent size.
You could try to disable this clipping, by adding this in your RelativeLayout XML attributes:
android:clipChildren="false"
android:clipToPadding="false"
or in code
viewGroup.setClipChildren(false);
viewGroup.setClipToPadding(false);
In your case, it seems that either RecyclerView or LinearLayoutManager consider that previous items should be displayed over following ones. One way could be to use RecycleView decoration to overlap :
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
private final static int vertOverlap = -10;// TODO : do not forget to convert dp in pixels
#Override
public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.set(0, vertOverlap, 0, 0);
}
});
So, you would not need to use translationY on your SeekBar, but rather to add some paddingTop to your VideoView :
android:paddingTop="5dp"
That way, I think you could hide the SeekBar if needed, and cell overlapping would not be visible.
Follow this answer with same case only the difference is it is overlapping next item of Recycleview https://stackoverflow.com/a/40603773/3839498
I'm trying to animate a view and hide it after some DP's were scrolled and i made everything fine, but the problem is that it will flick horribly when you are scrolling slowly before or after the Y value that is supposed to trigger the animation.
I think the flick is because i have to set its visibility to Gone and update the other view as match_parent, it won't work with just the TraslationY:
view.animate()
.translationY(-view.getBottom())
.alpha(0)
.setDuration(HEADER_HIDE_ANIM_DURATION)
.setInterpolator(new DecelerateInterpolator());
I tried to set the layout to relative and View 2 as match_parent to see if i could avoid the visibility change but it didn't work...
I have implemented all required code from Google I/O 2014 BaseActivity.java file:
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/BaseActivity.java#L17
And the animation works... but i assume that, as my customview isn't an actionbar with overlay properties, the customview won't leave and LinearLayout below won't fill the empty space (there is none).
SO, i made it to work with an animationlistener and setting customview visibility to gone when the animation is over but it will flick in a horrible way when you are close to the expected Y point that trigger the animation (flick as customview visibility is gone and LinearLayout below needs to resize itself to fill the empty space, and will quickly repeat if you scroll slowly around there).
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:animateLayoutChanges="true">
<com.project.app.layouts.TabsLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/tabs">
<FrameLayout
android:id="#+id/content_frame_header"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
android:background="#android:color/white"/>
</LinearLayout>
</RelativeLayout>
Is there any way to do this when it's not an actionbar?
EDIT:
Added the comment about how it will flick when you scroll slowly around Y point that triggers the animation to hide/show.
I recommend you to use android:hardwareAccelerated="true" attribute in your Manifest file. It will use your device's GPU to draw views and animations.
I suggest you to check the value of view.getBottom() in both cases (when it works and when not).
It may be that it flicks because the value returned by view.getBottom() is very big.
Add this line in your code:
Log.i("YourAppName", "view.getBottom(): -" + view.getBottom());
view.animate()
.translationY(-view.getBottom())
.alpha(0)
.setDuration(HEADER_HIDE_ANIM_DURATION)
.setInterpolator(new DecelerateInterpolator());
Then check your log to see if the values are the same or not.
I have made it in a slightly different way. Note that the call I'm using requires SDK 19 (KitKat), but you can still do it using ViewPropertyAnimatorCompat
I have a FrameLayout that has the header view and the main view, with the header view on front.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fab="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<include layout="#layout/main_layout" android:id="#+id/main_layout" />
<include layout="#layout/header_layout" android:id="#+id/header_layout" />
</FrameLayout>
Once the views are measured (posting a runnable during onResume) I set the topPadding of the main view to be the Height of the header.
In the hide and show animation, I add an update listener and inside it I update the top padding of the main view to be the Height of the header + the Translation on Y.
final View header = findViewById(R.id. header_layout);
header.animate()
.translationY(-header.getBottom())
.setDuration(200)
.setInterpolator(new DecelerateInterpolator())
.setUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int top = (int) (header.getHeight()+header.getTranslationY());
findViewById(R.id.main_view).setPadding(0, top, 0, 0);
}
});
This makes it a bit smoother, since the padding gets updated together with the translation.
Actually, setting an AnimationListener using ViewPropertyAnimatorCompat does not work. The listener is never called, so for backwards compatibility I opted for this solution, not elegant, but at least it work on pre-KitKat devices:
final View mainView = findViewById(R.id.main_view);
Runnable mainUpdateRunnable = new Runnable() {
#Override
public void run() {
int top = (int) (header.getHeight()+header.getTranslationY());
mainView.setPadding(0, top, 0, 0);
if (mAnimatingHeader) {
mainView.post(this);
}
}
};
mainView.post(mainUpdateRunnable);
The variable mAnimatingHeader is updated using the AnimationListener (which works)
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.