What I want to do
In a BottomSheetDialogFragment, I want to inflate a view that always stick at the bottom of the screen, no matter what state (collapsed / expanded) the BottomSheetBehavior is in.
What I have done
In a subclass of BottomSheetDialogFragment, I inflate a view from XML and add it as a child of CoordinatorLayout (which is BottomSheetDialogFragment's parent's parent):
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setupBottomBar(getView());
}
private void setupBottomBar (View rootView) {
CoordinatorLayout parentView = (CoordinatorLayout) ((FrameLayout)rootView.getParent()).getParent();
parentView.addView(LayoutInflater.from(getContext()).inflate(R.layout.item_selection_bar, parentView, false), -1);
}
The code runs without error.
And when I use Layout Inspector to look at the View hierarchy, the view structure is also correct:
You can also download the layout inspector result here, and open it using your own Android Studio.
The problem
However, even though it is inserted as the last child of the CoordinatorLayout, it is still being blocked by the BottomSheetDialogFragment.
When I slowly scroll the BottomSheetDialogFragemnt downwards (from collapsed state to hidden state), I can finally see the view that I want to inflate behind the fragment.
Why is this happening?
The answer
As #GoodDev pointed out correctly, it is because the root view (design_bottom_sheet) has been set a Z translation by BottomSheetDialog.
This provides an important information that - not only sequence in a View hierarchy will determine its visibility, but also its Z translation.
The best way is to get the Z value of design_bottom_sheet and set it to the bottom bar layout.
private void setupBottomBar (View rootView) {
CoordinatorLayout parentView = (CoordinatorLayout) (rootView.getParent().getParent());
View barView = LayoutInflater.from(getContext()).inflate(R.layout.item_selection_bar, parentView, false);
ViewCompat.setTranslationZ(barView, ViewCompat.getZ((View)rootView.getParent()));
parentView.addView(barView, -1);
}
EDIT 2
Ok, now I see your requirement, try this one:
private void setupBottomBar (View rootView) {
CoordinatorLayout parentView = (CoordinatorLayout) ((FrameLayout)rootView.getParent()).getParent();
View view = LayoutInflater.from(getContext()).inflate(R.layout.item_selection_bar, parentView, false);
// using TranslationZ to put the view on top of bottom sheet layout
view.setTranslationZ(100);
parentView.addView(view, -1);
}
EDIT:
OK, I check your layout and check the BottomSheetDialogFragment source code, found the reason:
In BottomSheetDialogFragment using BottomSheetDialog dialog, the method setContentView in BottomSheetDialog using wrapInBottomSheet to put the content view in R.id.design_bottom_sheet layout. So you need override the BottomSheetDialogFragment's public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) to fix your problem.
Or, change your setupBottomBar method to:
private void setupBottomBar (View rootView) {
FrameLayout frame = (FrameLayout)rootView.getParent();
frame.addView(LayoutInflater.from(getContext()).inflate(R.layout.item_selection_bar, frame, false), -1);
}
and in your item_selection_bar layout file, change height and layout_gravity:
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content">
BottomSheetDialogFragment doc says: Modal bottom sheet. This is a version of DialogFragment that shows a bottom sheet using BottomSheetDialog instead of a floating dialog.
So the BottomSheetDialogFragment is a Dialog, Dialog is a floating view, so will cover the Activity content when BottomSheetDialogFragment is showing.
#goodev has give a nice answer.
Your problem
View's Z position causes this problem. Although the TextView is the last position you still can not see it.
How to solve
You can set design_sheet_bottom's Z to TextView.
private void setupBottomBar (View rootView) {
CoordinatorLayout parentView = (CoordinatorLayout) ((FrameLayout)rootView.getParent()).getParent();
View view = LayoutInflater.from(getContext()).inflate(R.layout.item_selection_bar, parentView, false);
view.setZ(((View)rootView.getParent()).getZ());
parentView.addView(view, -1);
}
And I think above way is very boring, can you put your two view RecyclerView and TextView into a layout ? Then you can inflate theme together in onCreateView() method.
Related
I have this requirement where I have a fragment Instance and I want to dynamically add a view on the top of the fragment irrespective of the root viewGroup of the Fragment.
Something like this
:
So is it possible to get the root view of the fragment from its instance? I know for activity I can do this: activity.findViewById(android.R.id.content);
Also since a fragment can have any type of view group as its rootview like LinearLayout, RecyclerView, RelativeLayout, FrameLayout, Is there a generic way to add an overlay view on the top of the fragment?
If it is not possible then should I just add a dummy view on top of the fragment in xml layout and then use that view to add the overlay?
The getView() method returns a View that contains the Fragment's root layout. From there you could either cast it to ViewGroup and leave it at that to add a simple view, use an if-else block with instanceof to determine its type:
First:
ViewGroup viewGroup = (ViewGroup) fragment.getView();
Second:
View view = fragment.getView();
if (view instanceof FrameLayout) {
FrameLayout root = (FrameLayout) view;
} else if (view instanceof RelativeLayout) {
}
//etc...
Third:
Edit: Removed this option. It actually wont work.
You could use a Child Fragment but if you are only interested in the Fragments root view, just retain the View you are returning in the fragments onCreateView() method
This might be a little bit hard to explain, so the best way I can think of, is providing you a Video showing up the issue.
In the Video I show myself scrolling listview, and after 5 seconds, a View is created and added inside that holder in the bottom. In that moment, listview is refreshed.
http://tinypic.com/player.php?v=vpz0k8%3E&s=8#.U0VrIvl_t8E
The issue is the following:
I've an Activity with a layout that consists of a:
Fragment (above RelativeLayout), match parent, match parent.
RelativeLayout, as wrap content.
The fragment displays a ListView with animations for every row.
If I add a View on the "RelativeLayout", it makes the fragment to readjust to the new size, as it's set above this RelativeLayout, so every Row is rebuilt again.
Do you guys think in any way to avoid this?
EDIT: Sourcecode:
https://bitbucket.org/sergicast/listview-animated-buggy
Don't start the animation if the layout process for the added footer view is running. The end of the layout process can be determined using the ViewTreeObserver (the start obviously starts with adding the footer view):
hand.postDelayed(new Runnable() {
#Override
public void run() {
ViewTreeObserver viewTreeObserver = holder.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
holder.getViewTreeObserver().removeGlobalOnLayoutListener(this);
mIgnoreAnimation = false;
}
});
mIgnoreAnimation = true;
holder.addView(viewToAdd);
}
}, 5000);
Add this method to your Activity:
public boolean ignoreAnimation() {
return mIgnoreAnimation;
}
And check it in your Fragment:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = FragmentTest.this.getActivity();
TextView tv = new TextView(context);
tv.setText("Pos: " + position);
tv.setTextSize(35f);
if (runAnimation()) {
Animation anim = AnimationUtils.loadAnimation(context, R.anim.animation);
tv.startAnimation(anim);
}
return tv;
}
private boolean runAnimation() {
Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) {
return ! ((MainActivity)activity).ignoreAnimation();
}
return true;
}
Of course the whole Activity - Fragment communication can be improved considerably but the example gives you the idea how to solve the problem in general.
While it prevents the animation from being started, it doesn't prevent the ListView from being refreshed although the user won't notice. If you are concerned about performance you can improve the Adapter code by re-using the views:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = FragmentTest.this.getActivity();
TextView tv = null;
if (convertView != null && convertView instanceof TextView) {
tv = (TextView) convertView;
}
else {
tv = new TextView(context);
}
Yes, I can think of a possible way to solve this.
Your problem is:
You have set layout params of your holder to wrap_content. By default, when it has no content, it is "zero-sized" somewhere in the bottom and invisible to you (not invisible in terms of Android, though, sic!)
When you add a View to this holder, the framework understands, that the size of your holder container is different now. But this container is a child of another container - your root RelativeLayout, which, in turn, contains another child - your <fragment>.
Thus, framework decides, the root container alongside with its children should get laid out again. That's why your list gets invalidated and redrawn.
To fix the issue with list getting invalidated and redrawn, simply specify some fixed layout parameters to your holder. For example:
<RelativeLayout
android:id="#+id/holder"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" >
</RelativeLayout>
That will prevent the list from being redrawn. But in that case you'll get your holder displayed from the very beginning.
Yes. This is the expected behavior of RelativeLayout
You are adding the ListView Fragment and TextView into a RelativeLayout, So whenever there is a change in the child view dimension, will affect the other child in the RelativeLayout.
So here when you add a new TexView , the other child Fragment is affected even though its height is match_parent.
You can fix this only by changing the parent layout to LinearLayout.
I need to implement a scroll view as shown:
That is, in "idle" state image "1" is visible in full size and image "2" is visible partially (thus giving a clue to the user that he can scroll the content). After scrolling scrool view must not stay in intermediate state and scrolling must be completed (like iOS's Scroll View does when "Paging Enabled" is turned on):
I refused to use HorizontalScrollView, because it has nothing similar to "Paging Enabled" property.
After googling, I came across android.support.v4.view.ViewPager. It's scrolling behavior is perfectly what I want, but I have no good idea how to support "partially visible" next image in ViewPager? Technically, what should I return in the
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)?
For the present, my code is
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.stage_select_image_layout, container, false);
ImageView imageView = (ImageView) view.findViewById(R.id.stage_select_image_layout_image);
imageView.setImageResource(m_imageResourceId);
return view;
}
But it results in "exactly one image per page" behavior, not what I want (see the very first figure).
This does the trick:
ViewPager pager = ...;
pager.setOffscreenPageLimit(2);
pager.setPageMargin(-200);
The easiest thing to do is, is in your PagerAdapter, implement an override for getPageWidth. The return value is a percent that the view takes up of the total space.
#Override
public float getPageWidth(int position) {
return 0.75f;
}
Use the widthFactor attribute of the ViewPager's LayoutParams. It should scale it so you can see a little of the next page.
I am adding a layout using addContentView().
How can i remove this layout on a Button click ?
Assuming contentView is the view that was added via window.addContentView()
((ViewGroup) contentView.getParent()).removeView(contentView);
try it
View youAddedView;
ViewGroup rootView = (ViewGroup) findViewById(android.R.id.content);
for (int i = 0; i < rootView.getChildCount(); i++) {
if(rootView.getChildAt(i) == yourAddedView) {
// do anything here
}
}
If you already have the reference to the view you can simply just do :
ViewGroup rootView = (ViewGroup) findViewById(android.R.id.content);
rootView.removeView(viewToRemove);
Instead of looping through the ViewGroup.
Unfortunately there's no way of removing a content view that was added with addContentView(). The best you can you do is to call setVisibility(View.GONE) on it, to hide it.
That is why the activity's onContentChanged() only gets called when the content view is set or added to an activity.
you can do two things over here you can set the visibility to gone on the click event of the button.
OR
you can set the layout parameter to layout width and height to 0dp
It will hide your layout display
I'm new to developing with android. I have a grid contained in a LinearLayout and each item which makes up the grid is a button. I want this LinearLayout to be invisible when the user pushes any of these buttons.
This is my 'home' layout shell:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<TextView/>
<LinearLayout> //<-- this is the layout I want to hide
<TextView/>
<GridView/>
</LinearLayout>
</LinearLayout>
And this is the onClick method which I've set up in MyArrayAdapter (used to inflate buttons)
#Override
public void onClick(View v) {
View convertView = activity.getLayoutInflater().inflate(R.layout.layout_home, null);
LinearLayout ll_options = (LinearLayout) convertView.findViewById(R.id.ll_options);
ll_options.setVisibility(View.INVISIBLE);
}
I think it should work but when I test it, nothing happens.
I found a similar question but it doesn't solve my problem.
Why are you inflating a layout here?:
View convertView = activity.getLayoutInflater().inflate(R.layout.layout_home, null);
Just do:
View v = activity.findViewById(R.id.ll_options);
v.setVisibility(View.INVISIBLE);
You create a new view which is not in the visible view hierarchy until you add it there, and then you hide that. So you hide something invisible.
Instead, try:
#Override
public void onClick(View v) {
findViewById(R.id.ll_options).setVisibility(View.INVISIBLE);
}
which should IMO work. It searches for the ll_options view inside the visible (global) view hierarchy of your activity and hides that.
EDIT:
Where is your button? Is it in the same layout-file?
You inflate a new layout and hide the LinearLayout there, but this new layout is never used. Be sure you have access to the contentView in your listener.