Survey							
                            
		                
		                * Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Developing Mobile Applications Week 4 Alternative layouts, fragments Steve Jones Department of Computer Science University of Waikato [email protected] Alternative layouts, Fragments 1 Today Providing alternative layouts for different orientations • portrait + landscape Reusing components across activities and layouts Fragments Alternative layouts, Fragments 2 Alternative layouts Consider a standard list + detail UI design when the handset is in landscape orientation a single activity can show both list and detail side by side in portrait we need an activity to show the list and another to show the detail, which we’ve just seen Alternative layouts, Fragments 3 Alternative layouts We require different layouts depending upon orientation Defaults are provided in eg • res/layout/activity_property_finder.xml We can add other layout folders eg for when we are in landscape orientation • res/layout-land/activity_property_finder.xml • these override the defaults (which are now effectively for portrait) Alternative layouts, Fragments 4 Alternative layouts The default is what we already have in • res/layout/activity_property_finder.xml and we know it works for portrait mode Add the layout-land folder and create a new layout file • res/layout-land/activity_property_finder.xml Alternative layouts, Fragments 5 Alternative layouts <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ui_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/property_list" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginRight="10dp" android:layout_weight="3" /> <LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="4" android:orientation="vertical" > <TextView android:id="@+id/property_details_title" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/property_details_price" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> • res/layout-land/activity_property_finder.xml left to right Alternative layouts, Fragments takes up 3/7 of available width takes up 4/7 of available width same as our earlier details layout 6 Alternative layouts Now the system will load/apply the appropriate layout at run-time This includes when orientation changes dynamically • if onCreate() is being called again because the activity is being recreated, so is setContentView(R.layout.activity_property_finder) • now 1 of 2 possible layout defn files are applied depending upon current orientation What’s the problem……..? Alternative layouts, Fragments 7 Alternative layouts What’s the problem……..? • onItemClick needs to do different things dependent upon orientation (update details view or start details activity) • we get completely new ListView and TextView objects whenever the layout is created eg on rotation • we have do some work to retain state even though it’s really the same list and text views in both activities and orientations Enter fragments…. Alternative layouts, Fragments 8 Fragments The list and detail elements • will have the same implementation irrespective of orientation • we want to reuse them across activities in the application ie we want exactly the same list and exactly the same details, not copies or new ones configured to look like the original Alternative layouts, Fragments 9 Fragments Fragments are a middle ground between activities and UI components They normally have a UI (but don’t have to) and some behaviour/logic Fragments can be hosted in one or more activities So we might have • one list fragment • one detail fragment and use them across our two-panel and single-panel activities Alternative layouts, Fragments 10 Fragments : multiple activity layouts We can refactor the design the PropertyFinderActivity fills the screen the left panel is the fragment containing a list Alternative layouts, Fragments the right panel is the fragment displaying the details 11 Fragments : multiple activity layouts So we can refactor the design the PropertyFinderActivity fills the screen Alternative layouts, Fragments the panel is the fragment containing a list the PropertyDetailsActivity fills the screen the panel is the fragment displaying the details 12 Fragments : multiple activity layouts So we can refactor the design the same list fragment Alternative layouts, Fragments the same details fragment 13 Fragments We define a fragment as a Java class, just like an activity eg PropertyDetailsFragment.java public class PropertyDetailsFragment extends Fragment { fragment classes must have a blank constructor public PropertyDetailsFragment() { call to super() is not needed } @Override protected void onCreate(Bundle savedInstanceState) { } } Alternative layouts, Fragments fragments have lifecycle methods similar but not identical to Activity lifecycle methods fragment lifecycle methods also have to integrate with the containing activity’s lifecycle 14 Fragments : adding via XML Two ways to add fragments to an activity 1. via XML layout file eg our landscape layout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ui_container" android:layout_width="match_parent" a wrapper to hold the list fragment android:layout_height="match_parent" not strictly necessary, but allows us to : (i) add other android:orientation="horizontal" > views to this screen section later if needed; (ii) keep <FrameLayout orientation dependent layout attributes separate from the android:id="@+id/property_list_frame" fragment android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginRight="10dp" android:layout_weight="3" > declare a fragment element, identify exactly what type of fragment by giving the full package and class name <fragment android:id="@+id/property_list_fragment" android:name="org.stevej.android.propertyfinder5.PropertyListFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:tag="PropertyListFragment" /> tag is a string we can use to refer to the fragment in code </FrameLayout> Alternative layouts, Fragments 15 Fragments : adding via XML Two ways to add fragments to an activity 1. via XML layout file eg our landscape layout <FrameLayout android:id="@+id/property_details_frame" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginRight="10dp" android:layout_weight="4" > same pattern for the details fragment <fragment android:id="@+id/property_details_fragment" android:name="org.stevej.android.propertyfinder5.PropertyDetailsFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:tag="PropertyDetailsFragment" /> </FrameLayout> </LinearLayout> Alternative layouts, Fragments 16 Fragments : adding via XML Two ways to add fragments to an activity 1. via XML using layout XML file egwe ourare landscape layout Note: creating static fragments <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ui_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <fragment android:name=”org.stevej.android.propertyfinder5.ProprtyListFragment" android:id="@+id/property_list" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginRight="10dp" android:layout_weight="3" /> <fragment android:name=”org.stevej.android.propertyfinder5.ProprtyDetailsFragment” android:id="@+id/property_details” android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="4" android:orientation="vertical” /> </LinearLayout> This means that we can’t remove them at run-time. If we need to do that we create dynamic fragments in Java code Alternative layouts, Fragments 17 Fragments : adding via Java Two ways to add fragments to an activity 2. via Java code • /res/layout-land/activity_property_finder.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ui_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <FrameLayout android:id="@+id/property_list_frame" android:layout_width="0dp" android:layout_height="fill_parent" android:layout_weight="3" android:layout_marginRight="10dp" /> <FrameLayout android:id="@+id/property_details_frame" android:layout_width="0dp" android:layout_height="fill_parent" android:layout_weight="4" /> </LinearLayout> Alternative layouts, Fragments we’ll add the list fragment to this frame in Java we’ll add the details fragment to this frame in Java 18 Fragments : adding via Java Two ways to add fragments to an activity 2. via Java code • /res/layout-land/activity_property_finder.xml provides ability to manipulate the current set of fragments (add, remove, replace etc) FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(R.id.property_list_frame, new PropertyListFragment(), "PropertyListFragment"); ft.commit(); layout element that the fragment will be inserted into Alternative layouts, Fragments fragment instance fragment tag, used to retrieve fragment later 19 Fragments We will use the XML static fragment approach here Now we have 2 activities • • PropertyFinderActivity PropertyDetailsActivity 2 fragments • • PropertyListFragment PropertyDetailsFragment 4 states and transitions between them • • • • PropertyFinderActivity landscape PropertyFinderActivity portrait PropertyDetailsActivity landscape PropertyDetailsActivity portrait Our layout files handle inclusion of required fragments for both orientations Alternative layouts, Fragments 20 PropertyFinder behaviour But • a click on a list item needs to either update the details fragment (landscape) or launch the details activity (portrait) • we want to highlight the selected list item • we need to ensure list and detail fragments show correct items on orientation change, activity restart etc (keep state) • when the user has the details view in portrait and rotates the device, we want to show them the 2 panel view, not the details view in landscape Alternative layouts, Fragments 21 App logic/flow PropertyFinderActivity PropertyListFragment onListItemClick call property selected method title, price if portrait start details activity else call details fragment update method Intent with title, price data title, price PropertyDetailsActivity data extracted from Intent call details fragment update method Alternative layouts, Fragments PropertyDetailsFragment title, price update UI 22 Fragments : initialisation PropertyDetailsFragment Empty constructor No onCreate() method – we don’t need it because there is no data initialisation etc to do public class PropertyDetailsFragment extends Fragment { Unlike activities there is no setContentView() method. onCreateView automatically called – we inflate the public PropertyDetailsFragment() { fragment’s layout file into a View object and return it as } this fragment’s UI @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View details_view = inflater.inflate(R.layout.property_details, container, false); return details_view; } our method to change what’s shown in the UI getView() returns the top level view element in the fragment’s UI public void setContent(String title, String price) { TextView title_view = (TextView) getView().findViewById(R.id.property_details_title); TextView price_view = (TextView) getView().findViewById(R.id.property_details_price); title_view.setText(title); price_view.setText(price); } } Alternative layouts, Fragments 23 Fragments : fragment manager PropertyDetailsActivity If the user is in this activity in portrait, then they rotate the device to landscape – the system recreates this activity But we don’t need it (we have the details fragment in landscape) so finish this activity immediately @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { finish(); return; } if we are in portrait this activity was setContentView(R.layout.activity_property_details); started from a list click with data setUpActionBar(); provided with the intent (slides 31-32) Intent intent = getIntent(); String title = intent.getStringExtra("property_title"); String price = intent.getStringExtra("property_price"); the activity UI elements are provided by the fragment get hold of the fragment and call its method to display the data PropertyDetailsFragment property_details_fragment = (PropertyDetailsFragment) getFragmentManager().findFragmentByTag("PropertyDetailsFragment"); property_details_fragment.setContent(title, price); } Alternative layouts, Fragments 24 Fragments : ListFragment PropertyListFragment is more complex… needs to • retain state (data, current selection) • communicate with the details fragment to show data for selected item • provide visual selection feedback • ensure correct list item is selected whenever list becomes visible (including orientation change) Alternative layouts, Fragments 25 Fragments : retaining state PropertyListFragment initialisation • very similar to our list activity a list in a fragment is really common – there’s a built in class for it provides a listview etc public class PropertyListFragment extends ListFragment { private ArrayList<Property> properties = new ArrayList<Property>(); private PropertyListAdapter property_list_adapter; public PropertyListFragment() {} @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); properties.add(new Property("Exclusive Townhouse", "$450,000", R.drawable.ic_launcher)); properties.add(new Property("Close to Uni", "$375,000", R.drawable.ic_launcher)); properties.add(new Property("A Large Family Home", "$400,000", R.drawable.ic_launcher)); property_list_adapter = new PropertyListAdapter(getActivity(), R.layout.property_list_item, R.id.property_title, properties); setRetainInstance(true); the key to retaining the state of this fragment } Alternative layouts, Fragments basically it makes the FragmentManager keep hold of this specific PropertyListFragment instance when it would otherwise be destroyed 26 (eg containing activity is destroyed) Fragments : retaining state PropertyListFragment initialisation • setRetainInstance(true) The system’s default behaviour is to destroy/recreate fragments along with their parent activity ie we get brand new instances of the fragment • onCreate() and onDestroy() methods of the fragment can be called multiple times (eg orientation changes) Setting this flag stops this behaviour Instance variables retain their values Alternative layouts, Fragments 27 Fragments : communication Handling item clicks for both portrait and landscape Keep logic about whether to launch details activity or modify details fragment out of the list fragment • it would need to work out the context in which it is being used – simple here, difficult in more complex UIs Common pattern is to handle this in parent activity via a listener interface • PropertySelectionListener Alternative layouts, Fragments 28 Fragments : communication PropertySelectionListener package org.stevej.android.propertyfinder5; public interface PropertySelectionListener { public void onPropertySelected(Property property); } our main activity provides an implementation public class PropertyFinderActivity extends Activity implements PropertySelectionListener { @Override if we don’t have the details frame public void onPropertySelected(Property property) { (fragment not present) if (findViewById(R.id.property_details_frame) == null) { Intent intent = new Intent(this, PropertyDetailsActivity.class); intent.putExtra("property_title", property.getTitle()); intent.putExtra("property_price", property.getPrice()); startActivity(intent); } else { PropertyDetailsFragment property_details_fragment = (PropertyDetailsFragment) getFragmentManager().findFragmentByTag("PropertyDetailsFragment"); property_details_fragment.setContent(property.getTitle(), property.getPrice()); } } Alternative layouts, Fragments 29 Fragments : communication Why not check orientation == portrait? public void onPropertySelected(Property property) { if (findViewById(R.id.property_details_frame) == null) { } else { } } Because if we had some other layout design (eg for 10in tablet) we may have the details fragment in either orientation eg could have • landscape: list + detail + images • portrait: list + detail Alternative layouts, Fragments 30 Fragments : communication The list fragment can use that listener public class PropertyListFragment extends ListFragment { private ArrayList<Property> properties = new ArrayList<Property>(); private PropertyListAdapter property_list_adapter; private PropertySelectionListener property_selection_listener; public PropertyListFragment() {} fragment lifecycle method, called automatically when the fragment is @Override attached to its containing activity public void onAttach(Activity activity) { super.onAttach(activity); property_selection_listener = (PropertySelectionListener) activity; } the activity implements the selection listener interface @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } Alternative layouts, Fragments 31 Fragments : ListFragment Handle click in list fragment public class PropertyListFragment extends ListFragment { private ArrayList<Property> properties = new ArrayList<Property>(); private PropertyListAdapter property_list_adapter; private PropertySelectionListener property_selection_listener; private int selected_item_position = 0; public PropertyListFragment() {} update record of currently selected item @Override public void onAttach(Activity activity) { super.onAttach(activity); property_selection_listener = (PropertySelectionListener) activity; } highlight the item @Override public void onListItemClick(ListView l, View v, int position, long id) { selected_item_position = position; getListView().setItemChecked(selected_item_position, true); property_selection_listener.onPropertySelected(property_list_adapter.getItem(position)); } Alternative layouts, Fragments call the listener method, providing the selected property 32 Fragments : ListFragment Reselect the item after eg orientation change fragment lifecycle method – automatically called once the fragment’s parent activity has been created @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); show the list content rather than a ‘loading’ icon setListShown(true); getListView().setAdapter(property_list_adapter); only one selection at a time getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); highlight selected item getListView().setItemChecked(selected_item_position, true); make sure selected item is in view getListView().smoothScrollToPosition(selected_item_position); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { property_selection_listener.onPropertySelected(property_list_adapter.getItem(selected_item_position)); } } if we are in landscape we can update the details fragment Alternative layouts, Fragments 33 Fragments : ListFragment highlighting Managing highlighting in the list • by default clicked items flash, but the highlighting isn’t persistent • we need to define a style that is used when a list item is activated (selected/clicked) property_list_item.xml <RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android style="@style/activated” android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="5dip" > Alternative layouts, Fragments 34 Fragments : ListFragment highlighting We need to define a style that is used when a list item is activated (selected/clicked) values-v11 because were are res/values-v11/styles.xml targeting Android 3.0 (which introduced the Holo theme) and up applied when the item is activated we want a style value from <resources> this theme <style name="activated" parent="android:Theme.Holo.Light"> <item name="android:background">?android:attr/activatedBackgroundIndicator</item> </style> </resources> we’ll modify the background of the list item Alternative layouts, Fragments syntax for looking up a built in attribute value value we want 35 PropertyFinder Now we have multi-activity, multi-fragment application that correctly handles state changes Next… loading real data • making network requests • handling long running tasks (like network requests) Alternative layouts, Fragments 36