I'm having a problem where Dialogs don't scroll when their content is too large for the screen. I assume this is because Dialogs are not displayed within Scrollable containers.
Screenshot contains content wrapped in a ScrollView - You can see only the content is scrollable
Extra fields added to artificially increase dialog size for this example
You can see from the Android Developer Documentation that Dialogs should be wrapped within DialogFragments (This gives the benefit of having your dialogs survive an orientation change and response to lifecycle events) and this is the set up I'm trying to make work.
Much of the other answers I've found are all similar to one another and revolve around ensuring the window is set to "adjustResize". However, this would only make the parent view smaller on keyboard opening, it wouldn't make a View scrollable if it wasn't in a Scrollable container.
If someone could let me know if they have info to make a Dialog scrollable or to confirm that you cannot make a Dialog scrollable I'd appreciate it.
I have achieved scrolling in dialog fragment,
Dialog layout design
<LinearLayout>
<NestedScrollView>
<ConstrainLayout>
</ConstrainLayout>
</NestedScrollView>
</LinearLayout>
In DialogFrament class ,
override fun onStart() {
super.onStart()
dialog.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZ)
}
This is possibly the dirtiest and most likely to break solution to this problem:
After creating the Dialog in onCreateDialog() but before returning it you can achieve scrolling by adding this code:
final ViewGroup content = (ViewGroup) dialog.findViewById(android.R.id.content);
content.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
content.getViewTreeObserver().removeGlobalOnLayoutListener(this);
View inner = content.getChildAt(0);
content.removeViewAt(0);
ScrollView scrollView = new ScrollView(getContext());
scrollView.addView(inner);
content.addView(scrollView);
}
});
I had the same problem with DialogFragment, but I changed:
<fragment android:layout_height="425dp"
...
to this:
<fragment android:layout_height="wrap_content"
...
And now it's scrolling when the keyboard is open, or when the screen is in landscape mode.
NOTE: I have ScrollView as a rootView in DialogFragment layout, of course.
Related
I was using Collapsible Toolbar in my app. On activity launch Collapsible Toolbar is expanded state with scrolling enabled and its working well normally. But now I have a requirement to show a full screen error layout in case my API fails. In that case I have to collapsed toolbar with scrolling effect blocked.
Error Layout shows a Retry Button. On Retry I make API call again and if API gives success I have to again expand Toolbar and enable scrolling effect.
I was able to collapse toolbar with setExpanded(flag, animate) but in that case I am not able to block scrolling effect of Collapsible Toolbar while error layout is shown.
I need to provide a way to block as well as unblock scroll effect + Expand/Collapse Toolbar. Any help would be really appreciated.. !!!
Make your error layout such that it will overlap Collapsible Toolbar. Also set android:clickable="true" to your error layout.
When you set visibility to your error layout, set Toolbar scrolling accordingly.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f3f3f3"
android:orientation="vertical"
>
<!-- Add your other layout including Collapsible Toolbar here.-->
<RelativeLayout
android:id="#+id/errorLayout"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
I created a library AppBarrr to lock the screen in expanded mode, based on my previous answer.
As I said, the height of the Toolbar is the key: the CollapsingToolbarLayout will collapse until the Toolbar's height and will expand until the AppBarLayout's height.
With this library, you must set two layouts as the Toolbar and your Expanded Layout (used to lock the screen and the scroll), it will create a CollapsingToolbarLayout and inflate these layouts inside.
You can declare the animations duration, the color of the inner CollapsingToolbarLayout, the collapsed/expanded title's style, even the height of the locked layout... You could also hide the Expanded Layout if you click outside it. It can support NestedScrollView and ScrollView inside the Expanded Layout. The documentation and a sample app are available on Github.
For those who don't want to use the library, my previous answer shows the way to do it. Here's the output of the previous answer:
Basically, this is the same concept, but no need to write a full class, with the lib you just need to have a simple widget in xml and that's it!
Feel free to use, fork or test. Hope it will be useful ;)
If you use AlertDialog to communicate the error and a ProgressDialog (spinner) to show you are doing stuff, you can block user input while your app is doing it's thing.
A simple solution that you can apply is just use the property
android:visibility="gone"
for the content that you don't want to show and just make your error layout visible by using property android:visibility="visible"
place the error layout at the bottom of your parent layout
once the contents are not visible on screen and error layout is just visible you will achieve the desired result that you want. Hope this helps you.
You can implement the interface and call its methods when to enable or disable the collapsing effect.
public interface AppbarRequestListener {
void unlockAppBarOpen();
void lockAppBarClosed();
}
#Override
public void unlockAppBarOpen() {
appBarLayout.setExpanded(true, false);
appBarLayout.setActivated(true);
setAppBarDragging(false);
}
#Override
public void lockAppBarClosed() {
appBarLayout.setExpanded(false, false);
appBarLayout.setActivated(false);
setAppBarDragging(false);
}
private void setAppBarDragging(final boolean isEnabled) {
CoordinatorLayout.LayoutParams params =
(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return isEnabled;
}
});
params.setBehavior(behavior);
}
So I need the view of my settings to display over my main page.where there is a "setting"button in Main page which opens this settings view. But I want My main page to be visible beneath my settings view which only covers a half or less of the main view.
I tried adding
<org.example.myCustomView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:theme="#android:style/Theme.Dialog" />
Which I found from stackoverflaw itself. But I cannot do it instead the application corrupts at button click.
I am not sure I got this too correct, Or is there any other clear way to do it?
I replaced myCustomView with my relevent class and created the manifest also but it did not work.
If there is any other alternative way to do this mention to me.
I am not talking about how to place a TextView, Button, EditText on a view
I am talking about completely two layouts.
Below image is only an example to express my question.
I think you need to utilize layoutinflater. Here is a simple example how to use it
setContentView(R.layout.yourMainView);
LayoutInflater mInflater = LayoutInflater.from(getApplicationContext());
View overView = mInflater.inflate(R.layout.yourSmallView, null);
addContentView(overView, new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
Use FrameLayout.FrameLayout is like container.In it place first your bluelayout and then your settings layout.It will show as if settings layout is placed on top of your blue layout.
and then hide and show your settings layout on the onclick when required.
eg:
You could use a Dialog Fragment which would be much more simpler and show more complicated stuff on UI with better responsiveness. Have a look at Dialog fragment:
http://developer.android.com/reference/android/app/DialogFragment.html
I have a nested layout like the following:
<LinearLayout> <!----Parent layout--->
<LinearLayout> <!-----child 1--->
...
</LinearLayout> <!----child 1 ended--->
<LinearLayout> <!-----child 2--->
...
</LinearLayout> <!----child 2 ended--->
</LinearLayout> <!----Parent endded--->
The problem I am having now is that since all my data items are within child 1 or child 2 Linearlayout, if I add or delete a item the child linearlayout will animated with the effect of animateLayoutChanges but the parent layout will not do any animation. (I have android:animateLayoutChanges set to true for all linear layouts). Especially when I delete an item within child 1 the animation effect becomes weird (basically child 2 will jump up while child 1 is still doing its animation).
Does anybody have any idea how to solve this?
Thanks
UPDATE
Shortly after I posted this question, I found this on android developer's site in the LayoutTransition API.
Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the interrelationship of the various levels of layout.
So does anyone have any work around suggestions for this issue?
The animateLayoutChanges property makes use of LayoutTransitions, which animate both the layout's children and, from Android 4.0 onward, ancestors in the layout hierarchy all the way to the top of the tree. In Honeycomb, only the layout's children will be animated. See this Android Developers Blog post for details.
Unfortunately, it seems that there's currently no simple way to have the siblings of a layout react to its LayoutTransitions. You could try using a TransitionListener to get notified when the layout's bounds are being changed, and move the sibling views accordingly using Animators. See Chet Haase's second answer in this Google+ post.
EDIT - Turns out there is a way. In Android 4.1+ (API level 16+) you can use a layout transition type CHANGING, which is disabled by default. To enable it in code:
ViewGroup layout = (ViewGroup) findViewById(R.id.yourLayout);
LayoutTransition layoutTransition = layout.getLayoutTransition();
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
So, in your example, to have child 2 layout animated, you'd need to enable the CHANGING layout transformation for it. The transformation would then be applied when there is a change in the bounds of its parent.
See this DevBytes video for more details.
Ok, after digesting the first answer, I make it simple here, for those who don't get proper animation result when using android:animateLayoutChanges="true" in NESTED layout:
Make sure you add android:animateLayoutChanges="true" to the will-be-resized ViewGroup (LinearLayout/RelativeLayout/FrameLayout/CoordinatorLayout).
Use setVisibility() to control the visibility of your target View.
Listen carefully from here, add android:animateLayoutChanges="true" to the outer ViewGroup of your will-be-resized ViewGroup, this outer ViewGroup must be the one who wraps all the position-changing View affected by the animation.
Add following code in your Activity before the setVisibility(), here the rootLinearLayout is the outer ViewGroup I mentioned above:
LayoutTransition layoutTransition = rootLinearLayout.getLayoutTransition();
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
Before:
After:
Reminder: If you miss the 3rd step, you will get null pointer exception.
Good luck!
As a Kotlin Extension
fun ViewGroup.forceLayoutChanges() {
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
}
Usage
someContainer.forceLayoutChanges()
Notes:
In my experience, this happens when the container is a deep nested layout. For some reason android:animateLayoutChanges="true" just doesn't work, but using this function will force it to work.
We had added the android:animateLayoutChanges attribute to our LinearLayout but the change didn’t trigger an animation. To fix that, use this code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
((ViewGroup) findViewById(R.id.llRoot)).getLayoutTransition()
.enableTransitionType(LayoutTransition.CHANGING);
}
More details.
It seems that a delayed transition on the parent also works for animating. At least for me the following code gives a proper expand/collapse animation.
expandTrigger.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition(parentLayout);
expanded = !expanded;
child1.setVisibility(expanded ? View.VISIBLE : View.GONE);
}
});
For deeply nested layouts you sometimes might need to use a parent higher up in the hierarchy in the call to the TransitionManager.
I had a similar issue:
I was using a animatelayoutchanges in one of my activity with a recycler view, I also added some custom layout transition because I wanted to increase speed of the animation while an item disappears in the list. It was working fine when it was not in a nested layout.
I had used the same adapter for another recyclerview which was in a nested layout. It was not working and I tried all the above solutions, None worked for me.
The real reason was, I forgot to set
mTicketsListAdapter.setHasStableIds(true);
in the nested layout activity. And after setting setHasStableIds to true, the animations was working perfectly in the nested layout.
I have a ListView. The data behind it is fetched from the Internet, in sets of 10-30 items whenever the user scrolls all the way to the bottom. In order to indicate that it is loading more items, I used addFooterView() to add a simple view that displays a "Loading..." message and a spinner. Now, when I'm out of data (no more data to fetch), I want to hide that message. I tried to do:
loadingView.setVisibility(View.GONE);
Unfortunately, while that does hide the view, it leaves space for it. I.e. I end up with a big blank space where the "Loading" message used to be. How can I go about properly hiding this view?
I can't use removeFooterView() because I may need to show it again, in which case I can't call addFooterView() again because an adapter has already been set on the ListView, and you can't call addHeaderView() / addFooterView() after setting an adapter.
It seems that you are allowed to call addHeaderView() / addFooterView() after setAdapter() as long as you call one of those methods at least once before. That is a rather poor design decision from Google, so I filed an issue. Combine this with removeFooterView() and you have my solution.
+1 for the other two answers I got, they're valid (and arguably more correct) solutions. Mine, however, is the simplest, and I like simplicity, so I'll mark my own answer as accepted.
Try setting the footer's height to 0px or 1px before hiding it. Alternatively, wrap the footer view in a wrap_content height FrameLayout and hide/show the inner view, leaving the FrameLayout visible; the height should wrap properly then.
in my case addFooterView / removeFooterView() cause some artefacts.
And I found other solution. I used FrameLayout as FooterView. And when I want to add Footer I called mFrameFooter.addView(myFooter); and mFrameFooter.removeAllViews(); for remove.
FrameLayout frameLayout = new FrameLayout(this);
listView.addFooterView(frameLayout);
......
......
//For adding footerView
frameLayout.removeAllViews();
frameLayout.addView(mFooterView);
//For hide FooterView
frameLayout.removeAllViews();
The Droid-Fu library has a class designed for having a loading footer show and hide: ListAdapterWithProgress.
Works well in my project:
1.Add footer view first
mListView.addFooterView(mFooterView);
mListView.setAdapter(mAdapter);
2.Set visibility
mFooterView.setVisibility(View.GONE);
mFooterView.setPadding(0, 0, 0, 0);
3.Set invisibility
mFooterView.setVisibility(View.GONE);
mFooterView.setPadding(0, -1*mFooterView.getHeight(), 0, 0);
As #YoniSamlan pointed out, it can be achieved in a simple way. You have to specify
android:layout_height="wrap_content"
in the ViewGroup that contains the "Load More" button. Doesn't have to be FrameLayout, see below for a simple -working- example that uses a LinearLayout.
Both images show a screen that is scrolled all the way to the bottom. First one has a visible footer that wraps around the "load more" button. Second images shows that the footer collapses if you set button's visibility to GONE.
You can show again the footer (inside some callback) by changing the visibility:
loadMore.setVisibility(View.VISIBLE); // set to View.GONE to hide it again
Perform listView initialization as usual
// Find View, set empty View if needed
mListView = (ListView) root.findViewById(R.id.reservations_search_results);
mListView.setEmptyView(root.findViewById(R.id.search_reservations_list_empty));
// Instantiate footerView using a LayoutInflater and add to listView
footerView = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.load_more_footer_view, null, false);
// additionally, find the "load more button" inside the footer view
loadMore = footerView.findViewById(R.id.load_more);
loadMore.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
fetchData();
}
});
// add footer view to the list
mListView.addFooterView(footerView);
// after we're done setting the footerView, we can setAdapter
adapter = new ReservationsArrayAdapter(getActivity(), R.layout.list_item_reservations_search, reservationsList);
mListView.setAdapter(adapter);
load_more_footer_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="#+id/load_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="9dp"
android:gravity="center"
android:layout_gravity="center"
android:background="#drawable/transparent_white_border"
android:textColor="#android:color/white"
android:text="#string/LOAD_MORE"/>
It should be a bug of Android.
You don't need to remove or add footer view dynamically. You just need to create an unspecified height parent Layout (either inflate it from an xml file or create it programatically) and then add your view which you want to hide or show into it.
And you can set the view, but NOT the parent Layout, to VISIBLE or GONE or something else now. It works for me.
Used
footer.removeAllViews();
This does not remove footer but flushes children.
You again have to repopulate children. Can check by
footer.getChildCount()<2
I also found that is possible call onContentChanged() (if you use ListActivity) to force recreate ListView if I need add HeaderView to them after setAdapter() call, but it is very ugly hack.
I have created a ListView that handles this. It also has an option to use the EndlessScrollListener I've created to handle endless listviews, that loads data until there's no more data to load.
You can see these classes here:
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java
- Helper to make it possible to use the features without extending from SimpleListViewWithLoadingIndicator.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java
- Listener that starts loading data when the user is about to reach the bottom of the ListView.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java
- The EndlessListView. You can use this class directly or extend from it.
I have small hack way to resolve this problem for everywhere.
Put listview and footer view (just sub layout) in parent layout like LinnearLayout, remember that footerview below listview.
Controller this footer view gone and visibility like nomal view. And done!
first I am adding my footer to the listview,like this
listView.addFooterView(Utils.b);
Then on button click , I remove the view,
listView.removeFooterView(Utils.b);
I am adding the footer everytime when I am hitting the async,and theus the're no duplicate entry.I could aslo check for the count and so it like this,
if(listView.getFooterViewsCount() > 0){//if footer is added already do something}
When you want to remove the footer in ListView just call
listView.addFooterView(new View(yourContext));
It will add a dummy empty view which will not reserve any space
I want to add a new view to my scene that will contain content that will change programatically throughout the course of my application. When it does change, it needs to pop up on the screen for 3 seconds (or a click) then disappear.
This view will change in size according to its content WRAP_CONTENT, but ideally I'd like it centered horizontally and vertically on the screen.
I'm stuck on three parts:
1) what type of view should I use for this...I was thinking Relative, but all of my playing with it has yielded no good results for what I'm trying to do
2) with respect to #1 (trying relative view), I could not get it centered properly (tried using param.leftMargin and param.topMargin with varying values but could not get it to work on different devices with different resolutions
3) also with respect to #1, I couldn't make this float over everything else on my screen (need something like a z-index or the like).
any ideas, code examples would be wonderful.
TIA
Use a custom dialog, i.e. a LinearLayout with android:theme="#android:style/Theme.Dialog" and for the class, it would be something like
public class YourCustomDialog extends Dialog implements DialogInterface
where you can implement you custom logic of what to display. Such dialog is floating and modal on top of all other views then and you can also optionally set the background to blurry, etc.
This is a typical constructor of my custom dialog - the layout would be defined in an xml layout file, which in my case is my_custom_dialog.xml:
public MyCustomDialog(Context context) {
super(context, android.R.style.Theme);
Window window = getWindow();
window.requestFeature(Window.FEATURE_NO_TITLE);
window.setGravity(Gravity.BOTTOM);
window.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.empty));
setContentView(R.layout.my_custom_dialog);
// actually not necessary as it's already the default value:
window.setLayout(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
...
}