CS499 – Mobile Application Development Fall 2013 Fragments
Supporting Different Screens Device orientation changes characteristics of display Tablets have larger displays than phones Need ways to support multiple UI panes/user behaviors at the same time May not need “one activity per screenfull of data””
Example - Email On phone – 2 activites: List email messages View selected message On tablet – 2 fragments embedded into a single activity
Supporting different screens Can use the resources to deal with this (in limited ways) In drawables - four generalized densities: low (ldpi), medium (mdpi), high (hdpi), extra high (xhdpi) In layouts, can do similar things - four generalized sizes: small, normal, large, xlarge In layouts, can also differentiate between portrait and landscape orientations
Supporting different screens For example, this project includes a default layout and an alternative layout for large screens: MyProject/ res/ layout/ main.xml layout-large/ main.xml The file name must be exactly the same (main.xml), but their contents are different in order to provide an optimized UI for the corresponding screen size.
Supporting different screens http://developer.android.com/training/multiscreen/index.html
Fragments Introduced in 3.0 Represent a behavior/portion of UI within an Activity Multiple fragments can be embedded in a Activity to create a multi-pane UI A single fragment can be reused across multiple activities http://developer.android.com/training/basics/fragments/index.html
Fragment Lifecycle Fragments have their own lifecycles and receive their own events But Activity lifecycle interacts with associated fragment’s lifecycle: When activity pauses, its fragments are paused. When activity is destroyed, its fragments are destroyed
Fragment Lifecycle Similar to Activity lifecycle states Resumed Paused Fragment is visible in running activity Paused Another activity is in the foreground and has focus The containing activity is still visible Stopped The fragment is not visible
Lifecycle callbacks onCreate() onStart() onResume() onPause() onStop() Initial creation of the fragment onStart() Fragment is visible to the user onResume() Fragment is visible to the user and actively running onPause() Fragment is visible, but does not have focus onStop() Fragment is no longer visible onDestroy() Fragment is no longer in use
Lifecycle callbacks onAttach() onCreateView() onActivityCreated Fragment is first attached to its activity onCreateView() Fragment instantiates its user inteface view onActivityCreated Fragment’s activity created and Fragment’s view hierarchy instantiated onDestroyView() View previously cretaed by onCreateView() detached from the fragment onDetatch() Fragment no longer attached to its activity
Fragment & Activity Lifecycles onCreate() onStart() onResume() onPause() onStop() onDestroy() onAttach() onCreateView() onDetach() onActivityCreated()
Fragment Layout Fragments usually, but not always, have a UI Layout can be inflated/implemented in onCreateView() onCreateView() must return the View at the root of the Fragment’s layout The returned View will be added to the containing Activity Container represented as a ViewGroup within the containing Activities view hierarchy
Fragment Layout Two ways to add Fragments to an Activity's layout Declare statically in the Activity’s layout file Add it programmatically to a ViewGroup in the Activity's layout
Example App: Static Fragments
Going to create two versions Version 1 – create activity with two fragments Screen will not change with orientation Version 2 – extend version 1 so that using both fragments in landscape mode but only one (at a time) in portrait
Main Activity MainActivity.java activity_main.xml public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } MainActivity.java <LinearLayout 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:orientation = "horizontal"> <fragment android:name = "cs499.examples.fragmentstatic.Fragment1" android:id = "@+id/fragment1" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="match_parent" /> android:name = "cs499.examples.fragmentstatic.Fragment2" android:id = "@+id/fragment2" android:layout_weight="0" </LinearLayout> activity_main.xml
Fragments fragment1.xml fragment2.xml <LinearLayout 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:orientation="vertical" android:background="#0000FF“ > <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/fruits_list" android:drawSelectorOnTop="false" /> </LinearLayout> fragment1.xml <LinearLayout 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:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/selectedopt" android:text="Please Select a Fruit" /> </LinearLayout> fragment2.xml
Fragment1.java modifies fragment2 public class Fragment1 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Context c = getActivity().getApplicationContext(); View vw = inflater.inflate(R.layout.fragment1, container, false); final String[] fruits={"Apple", "Mango", "Orange", "Grapes","Banana" }; ListView fruitsList = (ListView) vw.findViewById(R.id.fruits_list); ArrayAdapter<String> arrayAdpt = new ArrayAdapter<String>(c,android.R.layout.simple_list_item_1,fruits); fruitsList.setAdapter(arrayAdpt); fruitsList.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { TextView selectedOpt = (TextView)getActivity().findViewById(R.id.selectedopt); selectedOpt.setText("You have selected "+ ((TextView)view).getText().toString()); } }); return vw; modifies fragment2
Fragment2.java public class Fragment2 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment2, container, false); }
No change on orientation change
Version 2 When in landscape mode, show both fragments. When in portrait mode, show fragment 1. Selecting an item starts a new activity that shows fragment 2. Want to move seamlessly between these modes.
Modify activity_main.xml: When a device switches to landscape mode, the layout file from res/layout-land is used to display view. So, Create folder in /res/layout-land Copy activity_main.xml (with two fragments) in new folder Remove second fragment from activity_main in /res/layout
in Fragments1.java: fruitsList.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { TextView selectedOpt = (TextView)getActivity().findViewById(R.id.selectedopt); selectedOpt.setText("You have selected "+ ((TextView)view).getText().toString()); } else { Intent intent = new Intent(getActivity().getApplicationContext(), ShowItemActivity.class); intent.putExtra("item", ((TextView)view).getText().toString()); startActivity(intent); } });
ShowItemActivity.java Be sure to add this activity to manifest public class ShowItemActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { finish(); return; } setContentView(R.layout.fragment2); Bundle extras = getIntent().getExtras(); if (extras != null) { String selectedItem = extras.getString("item"); TextView textView = (TextView) findViewById(R.id.selectedopt); textView.setText("You have selected "+selectedItem); Be sure to add this activity to manifest
Creating/Managing Fragments in Java Uses a FragmentManager access to the fragments available in an activity perform FragmentTransaction – required to add, remove and replace fragments FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); if (null==fm.findFragmentByTag(TAG1)) { Frag1Activity fragment1 = new Frag1Activity(); ft.add(R.id.fragment_container,fragment,”TAG1”); } ft.commit();
Create same app Use Fragment1.xml and Fragment2.xml Change activity_main.xml: <LinearLayout 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:orientation="horizontal"> <LinearLayout android:id="@+id/fragment1" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="match_parent" /> android:id="@+id/fragment2" android:layout_weight="0" </LinearLayout>
public class Fragment1 extends Fragment { protected static final String FRAG2 = "2"; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Context c = getActivity().getApplicationContext(); View vw = inflater.inflate(R.layout.fragment1, container, false); final String[] fruits={"Apple", "Mango", "Orange", "Grapes","Banana" }; ListView fruitsList = (ListView) vw.findViewById(R.id.fruits_list); ArrayAdapter<String> arrayAdpt = new ArrayAdapter<String>(c,android.R.layout.simple_list_item_1,fruits); fruitsList.setAdapter(arrayAdpt); final FragmentManager fragmentManager = getFragmentManager(); fruitsList.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (null != fragmentManager.findFragmentByTag(FRAG2)) { TextView selectedOpt = (TextView)getActivity().findViewById(R.id.selectedopt); selectedOpt.setText("You have selected "+((TextView)view).getText().toString()); } else { Intent intent = new Intent(getActivity().getApplicationContext(),ShowItemActivity.class); intent.putExtra("item", ((TextView) view).getText().toString()); startActivity(intent); } } }); return vw; } Fragment1.java
MainActivity.java Use same Fragment2.java and public class MainActivity extends Activity { private static final String FRAG1 = "1"; private static final String FRAG2 = "2"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){ ft.add(R.id.fragment1, new Fragment1(),FRAG1); ft.add(R.id.fragment2,new Fragment2(),FRAG2); } else { if (null != fm.findFragmentByTag(FRAG2)) ft.remove(fm.findFragmentByTag(FRAG2)); } ft.commit(); Use same Fragment2.java and ShowItemActivity.java as static version
Dynamic UIs Can replace fragments (in addition to add/delete) to react. To allow the user to navigate backward through the fragment transactions, you must call addToBackStack() before you commit theFragmentTransaction.
Updating Fragments // Create fragment and give it an argument specifying the article it should show ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack so the user can navigate back transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit();
Special Fragment ListFragment – builtin listview DialogFragment PreferenceFragment