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
Related
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.
I need to show my simple view on the top off all views. There is opened DialogFragment, my view should be over it.
I try
ViewGroup viewGroup = (ViewGroup) activity.getWindow().getDecorView();
viewGroup.addView(myView);
myView.bringToFront();
viewGroup.requestLayout();
viewGroup.invalidate();
But DialogFragment is still over myView.
Show your view as Another DialogFragment .
I want to have a fragment for each item in a listview, because I want to separate some logic out. I am using a view holder for each item. If the view doesn't exist, I create a new fragment and add it into the container.
holder.mMyFragment = new MyFragment(mActivity, this);
mActivity.getSupportFragmentManager().beginTransaction().add(R.id.my_container, holder.mMyFragment).commit();
Also for each item, I call holder.mMyFragment.setUi(dataSource, position) to set UI of the fragment based on the data source and position.
The problem I'm having is I initialize the UI elements of fragment in onCreateView method of the fragment class, but it's not called when I add the fragment to the item. So later when I call setUi() which uses some UI elements in fragment it complains a NullPointerException. Does anyone have a suggestion? Thanks!
"THERE IS A SOLUTION" for this.
The issue is, you cannot add fragment directly to the container(FrameLayout) with same "id" in listview as you have done in the above code.
The trick is, create listview(Recyclerview) of "LinearLayout". Then dynamically create FrameLayout in adapter and assign different id's for each. Inflate Fragment to FrameLayout and this FrameLayout to LinearLayout.
#Override
protected void onBindBasicItemView(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof VideoHomeViewHolder) {
VideoHomeViewHolder videoHomeViewHolder = (VideoHomeViewHolder) holder;
FrameLayout frameLayout = new FrameLayout(mContext);
frameLayout.setId(position+1); //since id cannot be zero
FragmentHelper.popBackStackAndReplace(mFragmentManager, frameLayout.getId(),
new ShowLatestVideosFragment(mShowLatestVideosItems.get(position)));
videoHomeViewHolder.linearLayout.addView(frameLayout);
}
}
I want to have a fragment for each item in a listview, because I want to separate some logic out.
You can't use fragment as list item views because the API doesn't allow you - View and Fragment aren't even related so there's no way you can use it like that. Make custom views and use adapter getViewTypeCount and getView to use different list item behavior.
Fragment are managed by Activity's FragmentManager or by other Fragments child FragmentManager; while list item views are managed by ListView & ListAdapter. You can use ListViews in Fragments, but not the other way around.
A simple way.
One problem:You should store add restore fragment state.
holder.mMyFragment = new MyFragment(mActivity, this);
int id = View.generateViewId();
findViewByTag("abc").setId(id);
mActivity.getSupportFragmentManager().beginTransaction().add(id, holder.mMyFragment).commit();
Hi I was facing the same problem and I found the way to do it.
My problem was similar to you:
"I want to have a fragment for each item in a listview, because I want to separate some logic out"
In my app I have to give the option to display custom items in vertical (listView) and horizontal (ViewPager) mode. Additionally I had to deal with 18 custom items and each one with different logic, so the best approach was reusing the fragments that I was using for ViewPager in ListView.
I got it but not in the way you were trying, I mean, I used my fragments like "ViewHolders":
Define fragment's widget like variables of class in each fragment.
Create a custom ArrayAdapter and override: getViewTypeCount(), getItemViewType(int position), getCount(), getItem(int position) getView(int position, View convertView, ViewGroup parent)
In getView I checked what kind of layout I needed before "inflate" the respective XML, create a fragment, assign widget from XML to fragment (with rootView.findViewById) and set "tag" with the new fragment.
What you can see at this point is that fragments in ListView never got attached to Activity but you got what you wanted: logic distributed in several parts and all benefits of ListView (reuse of rows, scroll, etc).
If you need I can post part of my code but you have to deal with "spanglish" ;).
UPDATED
All the problem is because when you create a Fragment to be used with ViewPager, usually all "layout and "setup" code is inside onCreateView method, I mean:
Get the view object you are going to use (View rootView = inflater.inflate(R.layout.fragment_question_horizontal_container, container, false);)
Get the widgets from above layout, define behaviors, fonts, etc: (answer = (EditText)rootView.findViewById(R.id.answer_question_text);)
Until this point there is nothing weird.
If you are going to use a fragment with the behavior described above you have to "emulate" the call to onCreateView, fill the data and attach it to the listView.
Here is the trick: split the code in onCreateView in some methods that doesn't care about who's calling them. An example of my onCreateView:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_pregunta_horizontal_container, container, false);
addAnswerLayout(rootView, R.layout.fragment_pregunta_texto, getActivity());
setUpComponents(rootView);
//those 2 methods are about my app logic so I'm not going to talk much about them
setUpQuestionState(savedInstanceState);
readSavedAnswer();
return rootView;
}
public void addAnswerLayout(View rootView, int optionId, Context context) {
mContext = context;
RelativeLayout relativeLayout = (RelativeLayout)rootView.findViewById(R.id.pregunta_container);
LayoutInflater inflater = ((Activity)mContext).getLayoutInflater();
View newView = inflater.inflate(optionId, relativeLayout, false);
relativeLayout.addView(newView);
}
public void setUpComponents(View rootView) {
//next line: some heritage that you can ignore
super.setUpComponents(rootView);
respuesta = (EditText)rootView.findViewById(R.id.pregunta_respuesta_texto);
respuesta.setTypeface(UiHelper.getInstance(getActivity()).typeface);
respuesta.setTextColor(mContext.getResources().getColor(R.color.drs_gris));
...
}
Now let's go to the CustomArrayAdapter for list view:
Define your customArrayAdapter something like this: PreguntasVerticalArrayAdapter extends ArrayAdapter<Pregunta> where "Pregunta" is a generic Fragment with the logic from above.
Override getViewTypeCount(), getItemViewType(int position), getCount(), getItem(int position) getView(int position, View convertView, ViewGroup parent).
The getView follow the same behavior: get the object for the given position in params, reuse a "viewholder" and fill the data. Here my getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
Pregunta pregunta = mData.get(position);
if (rowView == null)
rowView = createQuestionUI(pregunta, parent, position);
fillDataInRow((PreguntaUI)rowView.getTag(), pregunta, position);
return rowView;
}
private View createPreguntaUI(Pregunta pregunta, ViewGroup parent, int position) {
View rootView = null;
LayoutInflater inflater = (mPreguntasVerticalFragment.getActivity()).getLayoutInflater();
//you can ignore this part of the code ralted to Bundle.
Bundle args = new Bundle();
args.putLong(PreguntaUI.PREGUNTAUI_ID, pregunta.getIdpregunta());
args.putInt(PreguntaUI.PREGUNTAUI_INDEX, position);
args.putInt(PreguntaUI.PREGUNTAUI_TOTAL_QUESTIONS, getCount());
//internal method of "pregunta" to know what kind of question it is.
String tipo = pregunta.getTipo();
if (tipo.equalsIgnoreCase(PreguntaType.TEXT.toString())) {
rootView = inflater.inflate(R.layout.fragment_pregunta_vertical_container, parent, false);
Pregunta_texto pregunta_texto = new Pregunta_texto();
pregunta_texto.setArguments(args);
//LOOK AT THIS POINT!!!: I'm calling the same methods that I called in onCreateView fragment's method.
pregunta_texto.addAnswerLayout(rootView, R.layout.fragment_pregunta_texto,
mPreguntasVerticalFragment.getActivity());
pregunta_texto.setUpComponents(rootView);
pregunta_texto.setUpQuestionState(null);
pregunta_texto.readSavedAnswer();
//I'm adding the fragment to reuse it when I can
rootView.setTag(pregunta_texto);
}
else if ...
return rootView;
}
That is all... at this point, if you have enough experience dealing with CustomArrayAdapters and Fragments you probably got it! :D
From the Android Documentation : "A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities."
For your activity, do you want to add 2 fragments where the first one displays a listView (= ListFragment and the other one is in the right and is shown only when the user clicks on an item (from the first fragment or listView) ?
Instead of using ListFragment, you can use RecyclerView, Android has documentation on that:
https://developer.android.com/training/basics/fragments/creating.html#AddInLayout
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 am inflating interface from XML using
View add_phone = getLayoutInflater().inflate(R.layout.phone_info, null);
Now how can i access RelativeLayout from add_phone view? is there any methos like getChildCount() ?
yes , getChildCount(), works on a ViewGroup like LinearLayout, RelativeLayout etc..
ViewGroup add_phone = (ViewGroup) getLayoutInflater().inflate(R.layout.phone_info, null);
int childCount = add_phone.getChildCount();
you must make sure the inflated layout has viewGroup as parent view, otherwise you will get class cast exception. viewGroup can be anything like LinearLayout, RelativeLayout etc..
You can find child views of a view through
View.findViewById(int id)
In your case, that translates to
RelativeLayout child = (RelativeLayout)add_phone.findViewById(R.layout.phone_info)
As long as you have unique id's for the child elements in add_phone, this should return the correct element.