I'm starting using Fragments, and I've done like the API guide but ... of course it'd be too easy ;)
When I launch the app, it crashes. After some research I've found this post Android fragment is not working
and the response of Stephen Wylie seems to correct the things for Ali, but .. I don't get it !
Where should I put the FrameLayout ? The "where_i_want_my_fragment" id... it's whatever I want, right ?
and finally where should I put the Java code ? in my activity (which is displaying 2 fragments by the way) .
Thanks !
Nico
EDIT : Let's just say what I want for design you would understand better I think.
I want a list fragment on left side which display a list of strings, and to the right side I want a fragment displaying info regarding the selected string in the list. And I wanna be able to swip with fingers movements the right side of my app (I dont know if it s better to swipe fragment or whatever.. It's the same layout but filled with differents datas)
Ok I just post my code because I really don't see why it doesn't do anything.
This is my activity_main.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:baselineAligned="false"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/list_frag"
android:name="main.courante.c.DateListFragment"
android:layout_width="fill_parent"
android:layout_height="match_parent" >
</FrameLayout>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fiche_frag"
android:name="main.courante.c.fiche_frag"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" >
</FrameLayout>
</LinearLayout>
Here is my main activity :
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DateListFragment fragment = new DateListFragment();
getFragmentManager().beginTransaction().add(R.id.list_frag, fragment).commit();
fiche_freg frag2 = new fiche_frag();
getFragmentManager().beginTransaction().add(R.id.fiche_frag,frag2).commit();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
Here is DateListFragment (no onCreateView because it 's automatically generated)
public class DateListFragment extends ListFragment {
private int mposition = 1;
private String[] mListItem = new String[] {
"Lundi 9 Juilllet",
"Mardi 10 Juillet",
"Mercredi maintenant"
};
#Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
this.setListAdapter(new ArrayAdapter<String>
(this.getActivity(),R.layout.frag_list_view ,mListItem));
this.getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
}
and here is fiche_frag :
public class fiche_frag extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
return inflater.inflate(R.layout.checks_matin,container,false);
}
R.layout.checks_matin works fine alone.
I thank you already and again for your help. I'm a beginner in android environnement and I find it difficult to englobe every notions for the UI at once... !!
You understand the basics. The FrameLayout goes wherever you want your fragment(s) to go. I've done it where my whole screen was one single FrameLayout before and I swapped up to five different fragments in and out of it.
If you have two Fragments that you want to display simultaneously, you could make your main layout with two FrameLayouts. However, this means you are locked into having both there all the time (or an empty space if you remove one.
If you want two fragments that don't have to be on the screen at the same time you use a single FrameLayout and write code to swap the fragments as required.
Code to instantiate fragments should always be in the controlling activity (if they are dynamic).
Without code and a more specific problem, the above is the best answer I can give you.
EDIT
An example main layout to put two fragments side by side:
<?xml version="1.0" encoding="utf-8"?>
...
<LinearLayout
android:id="#+id/frames"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#id/hline1"
android:layout_below="#id/horizontalline"
android:orientation="horizontal" >
<FrameLayout
android:id="#+id/leftpane"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight=".4" />
<FrameLayout
android:id="#+id/rightpane"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1" >
</FrameLayout>
</LinearLayout>
...
To add your fragment to one of the framelayouts:
FragmentClass fragment = new FragmentClass();
getFragmentManager().beginTransaction().add(R.id.leftpane, fragment).commit();
If you want to swap fragments in one of the framelayouts (say the left pane), you can do it like this:
FragmentClass fragment = new FragmentClass();
getFragmentManager().beginTransaction().replace(R.id.leftpane, fragment).commit();
I suggested instantiating from the XML because it sounded like you were going to have two fragments and not make any changes. If you are going to swap them in and out, then it would be appropriate to add a tag to each one so you can find them again if you want to display them again.
Related
I am using fragments to update a text view I have so when the person clicks a button the text view moves on to the next question. I'm not sure if I am doing the correct work in one fragment instead of the other. My current screen looks like this:
I will probably have to add some more buttons/widgets to this but should I be adding it into the XML for the fragment or the fragment container?
Here is XML for fragment actions:
<?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:id="#+id/fragment_question_layout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".FragmentActions"
>
<!-- this is where fragments will be shown-->
<FrameLayout
android:id="#+id/question_container1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="#+id/questions_yes1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="#string/yes" />
<Button
android:id="#+id/questions_no1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="#string/no" />
</LinearLayout>
</LinearLayout>
And here is the fragment details:
<?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:id="#+id/button_layout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
tools:context=".FragmentDetails">
<!--Blank Fragment Layout-->
<TextView
android:id="#+id/questions_text_view1"
android:layout_width="match_parent"
android:layout_height="91dp"
android:gravity="center"
android:textAlignment="center"
/>
</FrameLayout>
Updated FragmentDetails
public class FragmentDetails extends Fragment {
private final String TAG = getClass().getSimpleName();
private List<Integer> mQuestionIds;
private int mListIndex;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Inflate the fragment layout
View rootView = inflater.inflate(R.layout.fragment_details, container, false);
//Get a reference to the textView in the fragment layout
final TextView textView = (TextView) rootView.findViewById(R.id.questions_text_view1);
if (mQuestionIds != null) {
textView.setText(mQuestionIds.get(mListIndex));
//Increment the position in the question lisy as long as index is less than list length
if (mListIndex < mQuestionIds.size() - 1) {
mListIndex++;
setmQuestionIds(QuestionList.getQuestions());
setmListIndex(mListIndex);
} else {
//end of questions reached
textView.setText("End of questions");
}
//Set the text resource to display the list item at that stored index
textView.setText(mQuestionIds.get(mListIndex));
}
else {
//Log message that list is null
Log.d(TAG, "No questions left");
}
//return root view
return rootView;
}
public void setmQuestionIds (List < Integer > mQuestionIds) {
this.mQuestionIds = mQuestionIds;
}
public void setmListIndex ( int mListIndex){
this.mListIndex = mListIndex;
}
}
Fragment Actions activity
public class FragmentActions extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_actions);
Button yes = findViewById(questions_yes1);
// Only create new fragments when there is no previously saved state
if (savedInstanceState == null) {
//Create Question Fragment
final FragmentDetails fragmentDetails = new FragmentDetails();
fragmentDetails.setmQuestionIds(QuestionList.getQuestions());
yes.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//set the list of question Ids for the head fragent and set the position to the second question
//Fragment manager and transaction to add this fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.question_container1, fragmentDetails)
.commit();
}
});
}
}
}
If your Buttons remain the same while the TextView changes, you may add your Buttons to the fragment container.
Remember that, your fragments will be presented inside the FrameLayout of the fragment container. You gotta keep your Buttons, outside the FrameLayout.
Or if you want to have different Buttons for different fragments (Questions, in your case), you can also add the Buttons to the fragments. But in that case, you gotta add them separately to each of the fragments.
I guess there's no right answer to your question. You could try different approaches.
Maybe you could implement the buttons in the fragment container, as #smmehrab pointed out. I see this as a more difficult solution, because when you click on an item from the container you can manage the views of the container, not the fragment's views. You would get NullPointer if I recall correctly. This happens because the context when the button is clicked in the fragment container is different than the context when clicking from within the fragment. So you should implement an interface on the fragment container that listens to clicks, and the fragment catches the click. You could do this, and I actually am doing it in my current app, but I have no choice.
You could instead use Motion Layout (which extends from Constraint Layout) as the root view of your fragment, instead of CardView. This way you could set all the fragment's views with a flat hierarchy (flat hierarchies improves rendering time, so that's an improvement, and you can use CardView as one child) and set the buttons right there, in the Motion Layout (remember, the motion layout would be the fragment's root view). You could set the click listener right there and implement animations between different textViews.
I'm sure there are plenty of other solutions, take this only as a contribution.
If you're unfamiliar with Motion Layout you can just google it, android official documentation about it is great.
I have a FrameLayout with a few buttons, and some ImageViews. When I add a fragment, it shows on top of the ImageViews as expected but below the buttons but I don't know why.
I searched through a lot of the SO posts but couldn't find problems similar to mine.
I have a custom onClickListener that adds the fragment
Custom clickListener class:
public void onClick(View v) {
// a context is passed to the listener
// this gets rootview id
int id= ((ViewGroup)((Activity)context).getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0).getId();
MyFragment myFragment = new MyFragment();
FragmentTransaction ft = ((Activity) context).getFragmentManager().beginTransaction();
ft.add(id, myFragment, "F1");
ft.commit();
}
The View I am adding the fragment to:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/startscreen"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/gamelogo"
android:id="#+id/logo"
android:scaleType="fitStart"
android:adjustViewBounds="true"
/>
</FrameLayout>
Activity:
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.startmenu);
final FrameLayout r = (FrameLayout) findViewById(R.id.startscreen);
addViews(r);
}
public void addViews(FrameLayout r){
// add some buttons to r
// add custom OnClickListener to one of the buttons
// add some ImageViews
// add animation to one button
}
FrameLayout is designed to block out an area on the screen to display a single item. Generally, FrameLayout should be used to hold a single child view, because it can be difficult to organize child views in a way that's scalable to different screen sizes without the children overlapping each other.
That's from the Docs, did you considered to change the layout?
I figured that I have to add the fragment to the parent of the base FrameLayout. I don't know why I would have to do that though.
I have a simple Android app. The layout folder shows an activity_main.xml file and a fragment_main.xml file. In that fragment.xml file I have placed a button that I've named buttonTest.
<RelativeLayout 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:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context="com.snapvest.thunderbird.app.MainActivity$PlaceholderFragment">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TEST"
android:id="#+id/buttonTest"
android:layout_gravity="center_horizontal" />
</LinearLayout>
in the MainActivity.java file I'm trying to gain access to that buttonTest.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment())
.commit();
}
// Get access to the buttons
final Button buttonTest = (Button)findViewById(R.id.buttonTest);
}
It compiles just fine. But when I run it the buttonTest variable comes back as a null pointer.
Why is it not finding the buttonTest object?
I think I found the solution. MainActivity.java sets up the activity_main.xml which then uses fragment_main.xml as its (initially) single, primary fragment. That's why we are supposed to actually put all of our UI for the activity primarily in fragment_main.xml (and actually put NOTHING in activity_main.xml).
Near the bottom of MainActivity.java there is a section that initializes the fragment_main.xml. And it is THERE that I need to gain access to the buttons and other objects of the UI for the fragment. Such as:
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
// Get access to the buttons
final Button buttonAvailableOptions = (Button)rootView.findViewById(R.id.buttonAvailableOptions);
return rootView;
}
}
Most of that, above, is automatically added by Android Studio. The part starting with "Get access to the button" is mine and is where you set up any access to or processing of the buttons and other UI objects.
The reason why findViewById() is failing to find the button is because setContentView() primes findViewById() to only look in the layout.xml file you gave to setContentView(), and your button is not located in R.layout.activity_main. If your button is located in the Fragment (which it appears to be) then you should be accessing and manipulating it from the Fragment, not the Activity. This ensures that your Activity and Fragment logic stay decoupled, allowing you to re-use the Fragment with different Activities.
You can use like this.
final Button buttonAvailableOptions = getActivity().findViewById(R.id.buttonAvailableOptions);
I am having a custom DialogFragment which contains a layout, some UI elements, and a Fragment holder layout. What I need to do is inflate a Content fragment into the holder layout and provide a navigation inside that. On clicking a button inside the added fragment the view will navigate to another view. The fragment will be replaced by another one in the same holder i.e. the contentFragment1 will show some data and on clicking a preview button there will replace contentFragment1 with contentFragment2.
I read somewhere that you cannot replace a fragment hardcoded to the xml with another one.
So I am trying to add the contentFragment1 to the viewholder from the onActivityCreated() of the dialog fragment. But I am getting an error that the resource pointed by R.id.fragmentHolder not found. What could be the possible reason?
Here is my code for the DialogFragment:
public class MyDialog extends DialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog customDialog = new Dialog(getActivity());
customDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
customDialog.setContentView(R.layout.reports_dialog);
return customDialog;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
android.app.FragmentTransaction fragmentTransaction =getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.myFragmentHolder, new ReportsListFragment());
fragmentTransaction.commit();
super.onActivityCreated(savedInstanceState);
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="#android:color/transparent" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="This is my header text view for the Dialog"
android:textSize="18sp" />
<RelativeLayout
android:id="#+id/myFragmentHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/headerlayout" >
</RelativeLayout>
After trying a lot, I came to a conclusion that onCreateDialog() doesn't have a view, it just sets a view on calling setView().
That is why on adding dynamic(framelayout tag) or static fragment(fragment tag) in the layout of the dialogfragment gives no parent view or duplicate id error.
To achieve the above, one should use onCreateView with a framelayout tag which can be inflated dynamically. Title and alert buttons are then added to the layout.
R.id.myFragmentHolder is inflated to the dialog's layout, and getFragmentManager() returns the manager for the activity, so it can't find the view.
With nested fragments in API level 17 you can use getChildFragmentManager().
Just be sure that the reports_dialog layout containts a layout whose id myFragmentHolder like this one
<FrameLayout
android:id="#+id/fragment_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Just for reference here, as Brandon mention the correct answer is to use the getChildFragmentManager(), keeping in mind, that android will also restore the state of fragments.
The correct code, to add your first fragment, is:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// if the saved instance isn't null, the fragment state will be restored by android
if (savedInstanceState == null) {
getChildFragmentManager().beginTransaction().add(R.id.myFragmentHolder, new ReportsListFragment()).commit();
}
}
after the view has been added. Later use replace if only one fragment should be shown at the same time.
I would also recommend to call transaction.addToBackStack(null); if the Android back button should be supported.
id of fragment holder in layout is fragmentHolder and you are using myFragmentHolder in code try to replace this by:
fragmentTransaction.add(R.id.fragmentHolder, new ReportsListFragment());
For large screens I have two fragments, they shall be either top/bottom or left/right. The problem is that one fragment is taking (almost) all space is taking up by one fragment. For my activity I have two main.xml (one for portrait one for landscape). But they are basically similar (apart from orientation).
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" android:id="#+id/frag_cont" >
</LinearLayout>
In onCreate I add my fragments to that layout:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
OverviewFragment of = new OverviewFragment();
FragmentTransaction tof = getFragmentManager().beginTransaction();
tof.add(R.id.frag_cont, of);
DetailFragmentInitial df = new DetailFragmentInitial();
tof.add(R.id.frag_cont, df);
tof.commit();
}
The two fragments look kind of similar in terms of onCreateView()
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.overview, container, false);
}
Finally the two xml files for these fragments look like this, first the smaller fragment:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_weight="1">
<ImageView
android:id="#+id/imageLogo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/logo"
android:layout_centerInParent="true" />
<TextView
android:id="#+id/textB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text"
android:layout_above="#id/imageLogo"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
And now the greedy one taking the space:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:layout_weight="10000" >
<ListView
android:id="#+id/ListView01"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
I assumed that when I set both layout_weight to 1 then both will take the same amount of space, but even with the setting 1000:1 the bigger one takes about 2/3 of the space. Leaving the weight away will give the bigger fragment all the screen and the other one is not visiable at all. Any ideas?
Edit:
I followed the approach given below. Much better now in terms of space. On screen rotate the overview fragment (basically list view) is empty, the other fragment is still okay.
In OverviewFragment.java I repopulate the fragment in onActivityCreated():
public void onSaveInstanceState(Bundle bundle)
{
bundle.putSerializable("list", mList);
super.onSaveInstanceState(bundle);
}
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
if(savedInstanceState != null)
{
mList = (ArrayList<myObject>) savedInstanceState.getSerializable("list");
}
else
{
mAppList = new ArrayList<myObject>();
new getApps().execute();
}
ListView lv = (ListView) getActivity().findViewById(R.id.ListView01);
lv.setOnItemClickListener(new TableItemSelected());
mItemAdapter = new ItemAdapterOverview(getActivity().getApplicationContext(), mList);
lv.setAdapter(mItemAdapter);
}
when rotating the screen the onSaveInstanceState() is called first and then onActivityCreated() twice. The first call the saveInstanceState is not null, the second time it is. Anyway in both cases I should see my list, either build up from scratch or the saved list. Why isn't it and why is that method called twice?
Not entirely sure whether this will be causing your issue (I suspect it does), but using FragmentTransaction.add(..) doesn't add the Fragment into the container, it essentially replaces the container with that Fragment. The issue here is that you're adding both Fragments to the same container so they will take up the same space and cause issues.
A better way to do this would be to add two container views into your frag_cont LinearLayout (e.g. FrameLayouts), and give them two separate ids (e.g. frag_one and frag_two). Then in your onCreate(..), change it to read:
FragmentTransaction tof = getFragmentManager().beginTransaction();
tof.add(R.id.frag_one, of);
DetailFragmentInitial df = new DetailFragmentInitial();
tof.add(R.id.frag_two, df);
tof.commit();
This will make it so that the layout with id frag_one is filled by the OverviewFragment and the one with id frag_two replaced by the DetailFragmentInitial. You can then use the views in main.xml to tweak the sizes of the fragments as you see fit.