I'm planning to convert an existing android application to fragments layout.
The idea is to have the classical two panel layout (item list on left, and details on right).
Actually the application is composed by 4 activites:
A ChoiceListActivity with all the available options
3 different activities, one for each operation available on the tool.
Now i started to work on the conversion and i created a FragmentActivity classs, that is the main class:
public class MainFragment extends FragmentActivity {
private static final String TAG = "MainFragment";
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_main);
if(findViewById(R.id.fragment_container)!=null){
Log.i(TAG, "No Tablet");
Intent i = new Intent(MainFragment.this, main.ChoiceActivity.class);
startActivity(i);
} else {
Log.i(TAG, "Tablet");
}
}
}
And i created a ChoiceListFragment:
`
public class ChoiceListFragment extends ListFragment {
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
Toast.makeText(getActivity(), getListView().getItemAtPosition(position).toString(), Toast.LENGTH_LONG).show();
super.onListItemClick(l, v, position, id);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String[] options = getResources().getStringArray(R.array.listitems);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(inflater.getContext(), R.layout.list_item, options);
setListAdapter(adapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
}
That fragment will be the left side of the panel.
My problem is for the right side. The idea is that for every element of the list the corresponding activity (or fragment?) will be shown.
So what is the correct way?
Is a good idea to start an activity in the right fragment when the user select an item?
Or i must switch between fragments programmatically? And how to do that (i found many tutorials, but they use always the same activity for the right panel changing some data inside it)?
I have created the following class for the right fragment (but i'm not sure that i'm doing it correctly):
public class RightFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.main, container, false);
}
}
I noticed that i can eventually change the layout using the LayoutInflater object during onCreate method, but this simply siwtch the layout on the screen, The objects declared in the layout aren't initialized (nor eventListener added, etc). So how to do that?
Maybe i should Create an Intent and use startActivity to launch the existing activities, or this is a bad idea into a fragment?
Actually the xml layout is:
<?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="match_parent"
android:orientation="horizontal" >
<fragment
android:id="#+id/choicelist_fragment"
android:name="main.fragments.ChoiceListFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="#+id/right_fragment"
android:name="main.fragments.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
</LinearLayout>
Ok i found myself the solution, it was not clear on a first moment, but reading some documentation, looking at many tutorials maybe i understand how it works.
First of all i removed the second fragment *right_fragment* from the layout (check the question), i replaced it with an empty FrameLayout called *activity_container* that will be the container of my fragments.
The idea behind is simply use the FragmentManager to replace the fragment inside the container.
So i updated the onListItemClick method into the ChoiceListFragment, and depending on what is the list item tapped, it creates a new Fragment and replace it into the *activity_container*. The updated method is similar to the following:
public void onListItemClick(ListView l, View v, int position, long id) {
String itemName = getListView().getItemAtPosition(position).toString();
switch(position){
case OPTION_ONE: getFragmentManager().beginTransaction().replace(R.id.activity_container, new OptionOneFragment()).commit();
break;
case RESISTOR_VALUE:
getFragmentManager().beginTransaction().replace(R.id.activity_container, new OptionTwoFragment()).commit();
break;
default:
Toast.makeText(getActivity(), getListView().getItemAtPosition(position).toString(), Toast.LENGTH_LONG).show();
break;
}
super.onListItemClick(l, v, position, id);
}
In that way every component of the application has its own fragment, handled by a different class.
You're on the right track. On small screens, clicking a list item starts a DetailActivity, which is a simple wrapper around a DetailFragment. On a larger screen, clicking the list item would replace the right hand side with a new instance of DetailFragment.
If you are using eclipse and ADT, I would suggest taking a look at the MasterDetailFlow template, which can be accessed by creating a new Android project or a new Android Activity.
Related
My app has quite a few separate activity/fragment pairs, and relies on the Android universal back button for much of its navigation. This button works fine, EXCEPT when I'm trying to return from a DetailView activity back to a list of search results.
Here's what the search results code looks like:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saveInstanceState) {
View v = inflater.inflate(R.layout.results_fragment, container, false);
ListView lv;
lv = (ListView)v.findViewById(R.id.listViewResults);
lv.setAdapter(SearchResultsAdapter);
lv.setEmptyView(v.findViewById(R.id.emptyElement));
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
ItemType selectedItem;
selectedItem = (ItemType)adapter.getItemAtPosition(position);
Intent i = new Intent(getActivity(), DetailViewActivity.class);
i.putExtra(DetailViewFragment.RESULT_ID, resultIdNumber);
startActivity(i);
}
});
// ... some other stuff
return v;
}
The DetailView is simply a collection of images and text.
The search returns expected results, and selecting the item shows the correct DetailViewFragment.
It seems like a very typical architecture, so I'm not sure why navigation back to the results page should be so problematic. I tried setting breakpoints to determine if the results activity ever restarted, but apparently it did not.
If you want to make something when the back button is pressed, you have to override it:
#Override
public void onBackPressed()
{
// code here
finish(); // to end activity:
}
I have an activity with an AutoCompleteTextView (text).
When i select an item the code below is executed:
text.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Bundle args= new Bundle();
for (Student s: studentsBook.getStudentsList()){
if (s.getName().equals(((TextView)view).getText().toString())){
args.putSerializable("Student",s);
break;
}
}
theLayout.setVisibility(View.GONE); //removing elements (button, textviews...)
addButton.setVisibility(View.GONE); //removing elements
//simply adding a fragment through supportfragmentmanager and fragment transactions
//Fragment receives arguments (args) which contain a string to be showed.
//A tag: "DataFragment" is provided in order to get the fragment back in other parts of code.
//getMainView returns the container in which the fragment has to be created/showed.
dataFragment=(StudentDataFragment)addFragment(StudentDataFragment.class,R.layout.student_data_fragment,getMainView().getId(),args,"DataFragment");
}
});
The fragment has only this method:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
myLayout=(ViewGroup)inflater.inflate(R.layout.student_data_fragment,container,false);
student=(Student)getArguments().getSerializable("Student");
tv=((TextView)myLayout.findViewById(R.id.textView17));
tv.setText(student.toString());
return myLayout;
}
I can't see the string, it seems that the gui gets not updated, but if an orientation change happens the string appears..
I also managed the back button to remove the fragment if present and set visible the elements "gone". The code runs successfully but no gui refresh appears to be run.
No threads are involved in this situation, so i think we are in the UI-thread right?
Solved, just messing up the content view of the activity in other parts of code
For my app, I'm trying to show a list, and as soon as this list ends, the second one shall begin. The Lists are being displayed using a ListAdapter, which again is part of a fragment. Everything works very well, the lists appear correctly, but I can't figure out a way to put one list under the other. I thought this shouldn't be all too hard.
Summary:
What I have:
A FragmentPagerAdapter with 3 Fragments
Two Fragments, which contain one ListView each
My searches:
Apart from multiple searches on this site, this guy came closest to what I'm seeking:
This guy here Fragmenttransaction in 1 tab of a Fragmentpageradapterhas had the same problem, but it wasn't satisfyingly answered, so I thought I can make a valid question here.
My question:
How can I place two ListViews in one Fragment? The big deal is that for example if the first ListView is bigger than the screen, I don't want the second ListView to show up before the first is completely scrolled down.
Current output:
Currently, both ListViews are in the same position, meaning that one ListView is on top of the other, making both unreadable
I thought that I can maybe use a specified layout for the FragmentTransaction. But I just can't figure out how.
This is the Fragment where I combine my top and bottom ListViews
public class LeaguePageTransactionsAdapter extends Fragment{
Global global_var;
ListView list, list_flat;
List <League> leagues = null, leaguesFlat = null;
ListAdapter adapter = null, adapter_flat = null;
View rootView;
FragmentTransaction fragmentTransaction;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.league_page, container, false);
fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(rootView.getId(), new LeaguePageTop(), "TopFragment");
fragmentTransaction.add(rootView.getId(), new LeaguePageBottom(), "BottomFragment");
fragmentTransaction.commit();
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
}
}
This is corresponding the xml layout file.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
This is one of my two ListViews
public class LeaguePageTop extends Fragment{
ListView list;
List <League> leagues = null;
ListAdapter adapter = null;
View rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.league_page_top, container, false);
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
list = (ListView) rootView.findViewById(R.id.listView1);
try {
leagues = Leagues_Parser.parse(getActivity().getAssets().open("league_raw.xml"), 0);
} catch (IOException e) {
e.printStackTrace();
}
adapter = new LeagueAdapter (getActivity(), R.layout.list_row, leagues);
list.setAdapter(adapter);
list.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
Global.mViewPager.setCurrentItem(1, true);
}
});
}
}
This is the corresponding xml file
<?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="match_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
Thank you very much for reading and thinking about it!
The reason is because you have set the LinearLayout in the XML file you have shown to match_parent which will occupy all available space and then comes your next ListView with it's own LinearLayout (which I presume is set to match_parent as well) and hence there is no space for it to display. FrameLayout and LinearLayout follows an eldest child first approach meaning that the first layout occupies as much space as it requests. Set the LinearLayout you have to wrap_content and I think that should solve your fragment.
Firstly use a view filpper.you can load your first list view in fragment and in your adapterview on item click listener you can flip view by flipper.setdisplayedchild(1) which will show your desired second list.
Currently there are two fragment : one for the area for adding image view, text view.
The other is a list fragment
I would like to include both in one fragment , that means the area fragment is at the top of the list fragment , however, they are two class so how to include them , or I need to re-arrange the code to one class?
Also, how do I change the list fragment to fragment ?(Since setadapter and onclick event are not available in fragment class).Thanks.
code example : the List fragment part
public class SlidingMenuListFragment extends ListFragment {
protected List<SlidingMenuListItem> slidingMenuList;
private SlidingMenuBuilderBase slidingMenuBuilderBase;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// We set here a custom layout which uses holo light theme colors.
return inflater.inflate(R.layout.sliding_menu_holo_light_list, null);
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// We get a list from our specially created list data class.
slidingMenuList = SlidingMenuList.getSlidingMenu(getActivity());
if (slidingMenuList == null)
return;
// We pass our taken list to the adapter.
SlidingMenuListAdapter adapter = new SlidingMenuListAdapter(
getActivity(), R.layout.sliding_menu_holo_light_list_row, slidingMenuList);
setListAdapter(adapter);
}
// We could define item click actions here, but instead we want our builder
// to be responsible for that.
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
l.setSelection(position);
SlidingMenuListItem item = slidingMenuList.get(position);
slidingMenuBuilderBase.onListItemClick(item);
}
// We can not provide a builder as an argument inside a fragment
// constructor, so that is why we have separate method for that.
public void setMenuBuilder(SlidingMenuBuilderBase slidingMenuBuilderBase) {
this.slidingMenuBuilderBase = slidingMenuBuilderBase;
}
Since Android 4.2, you can use Nested Fragments.
And to older versions, you should use the Support Library
I am working through the Big Nerd Ranch guide for android programming, and I am at the challenge for Chapter 16. The challenge is to make an EmptyView for a ListView, and then make a button on the EmptyView that adds stuff. I got the EmptyView to work but I can't figure out where I should make my button. Here is my code.
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v= super.onCreateView(inflater, parent, savedInstanceState);
inflater.inflate(R.layout.list_frame_layout, parent);
return v;
}
and here is my XML.
<?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" >
<ListView
android:id="#android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</ListView>
<LinearLayout android:id="#android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="#string/empty_no_crime" />
<Button
android:id="#+id/empty_new_crime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/empty_new_crime">
</Button>
</LinearLayout>
</FrameLayout>
The book is telling us to use fragments, hence the inflate. I figure the code should be
mNewCrime=(Button)getView().findViewById(R.id.empty_new_crime)
but that isn't working. Any ideas?
Edit*: Hmmm, apparently this also really isn't working that well. When I do add stuff, the EmptyView does not go away, it just gets pushed down while items are listed. Any ideas on how to make the EmptyView go away as soon as I add things?
I had trouble with this challenge at first as well. I over thought it! You have probably solved this issue by now but I thought it would be useful to post an answer for others. The following worked for me:
Create a new XML file specifying the "empty" and "list" views as you have done already.
Modify your existing onCreateView method to inflate the new modified layout which contains the "empty" and "list" views you have defined in your XML.
Create a new button and setup the onClickListener for the button.
Here is the code:
#TargetApi(11)
#Override
// We override the onCreateView to set the subtitle by default if we are rocking >3.0
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
{
super.onCreateView(inflater, parent, savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
if(mSubtitleVisible){
getActivity().getActionBar().setSubtitle(R.string.subtitle);
}// End inner if
}// End if
View v = inflater.inflate(R.layout.empty_layout, parent, false);
mNewCrimeButton = (Button)v.findViewById(R.id.add_crime);
//Define an click event listener for the button and launch the new crime fragment when clicked
mNewCrimeButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Crime crime = new Crime();
//Get the crimelab from the activity and add the crime
CrimeLab.get(getActivity()).addCrime(crime); //getActivity returns the activity this fragment is attached to
Intent i = new Intent(getActivity(), CrimePagerActivity.class);
startActivityForResult(i,0);
}//End onClick
});
return v;
}// End onCreateView
This should work with your existing xml layout. I hope this helps.
I too struggled initially with this, essentially solving it the same way the above poster did. However my problem was a bit different. I was getting bombed out of the application on startup, because my code that set up the onClick listener looked like this:
Button mCrimeButton = (Button)v.findViewById(R.id.crime_button);
mCrimeButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
initiateCrimeRecord();
}
});
It wasn't until I moved the declaration of mCrimeButton up to the class level making it an instance variable of the class that I was able to successfully execute the app:
public class CrimeListFragment extends ListFragment {
private static final String TAG = "CrimeListFragment";
private ArrayList<Crime> mCrimes;
private boolean mSubtitleVisible;
private Button mCrimeButton;
*
*
*
#TargetApi(11)
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_empty_crime_list, parent, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if ( mSubtitleVisible) {
getActivity().getActionBar().setSubtitle(R.string.subtitle);
} else {
getActivity().getActionBar().setSubtitle(null);
}
}
// Set the button up on the empty view
mCrimeButton = (Button)v.findViewById(R.id.crime_button);
mCrimeButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
initiateCrimeRecord();
}
});
return v;
}
I then went back and noticed that in all the other examples in the book, the widgets that get manipulated are declared as private instances of the class. Why is this? Android doesn't allow you to just get a local instance to attach the listener?