使用Android Fragments创建动态UI

Creating Dynamic UI with Android Fragments Leverage the power of Android Fragments to develop dynamic user interfaces for your apps Jim Wilson BIRMINGHAM - MUMBAI Creating Dynamic UI with Android Fragments Copyright © 2013 Packt Publishing All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book. Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information. First published: September 2013 Production Reference: 1180913 Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK. ISBN 978-1-78328-309-5 www.packtpub.com Cover Image by Asher Wishkerman (wishkerman@hotmail.com) Credits Author Jim Wilson Reviewers Robert Dale Johnson III Alex Lockwood Acquisition Editor Edward Gordon Anthony Albuquerque Commissioning Editor Poonam Jain Technical Editors Pratik More Anusri Ramchandran Project Coordinator Michelle Quadros Proofreader Jonathan Todd Indexer Priya Subramani Graphics Sheetal Aute Production Coordinator Kyle Albuquerque Cover Work Kyle Albuquerque About the Author Jim Wilson is president of JW Hedgehog, Inc., a consulting firm specializing in solutions for the Android, iOS, and Microsoft platforms. Jim has nearly 30 years of software engineering experience, with the past 13 years heavily focused on creating mobile device and location-based solutions. After nearly a decade as a Microsoft Device Application Development MVP, Jim now focuses on developing Android and iOS device applications. Jim's passion is teaching and mentoring software developers. He is a regular contributor of Android-related training materials to Pluralsight (http://training. jwhh.com), a leading provider of online developer training. Jim has authored more than 30 articles on device application development, and has developed mobility and smart client curriculums for several organizations. You can find Jim speaking at a variety of industry conferences, including AnDevCon, Microsoft Professional Developers Conference, Tech Ed, VS Live, Mobile and Embedded Developers Conference, and many others. Jim and his wife, along with several cats, live in Celebration, Florida (just 3 miles from Walt Disney World). Check out Jim's blog (http://blog.jwhh.com) where he talks about a variety of mobile software development issues as well as the fun of life just 3 miles from the "House of Mouse". You can reach Jim at androidtraining@jwhh.com. Acknowledgments First and foremost I want to thank my beloved wife, Bonnie. Without her support and patience through the many long nights and six (often seven) day work weeks, this project (and so many others) would never have happened. Our life together has grown into more than I could have ever hoped or dreamed. I love you. Thank you to my dear friend Rev. Dr. William A. Lewis of Community Presbyterian Church in Celebration. Your friendship and guidance have opened my eyes up to a life of joy and purpose beyond imagination. To all the folks at Pluralsight, thank you for creating an organization that offers people like me the opportunity to dig deep into technology and share the knowledge gained with others. About the Reviewers Robert Dale Johnson III is a Software Engineer who specializes in Android, Joomla, and BD-J (BluRay Disc – Java) development. He graduated in 2008 from California State University Northridge with a BS in Computer Science. He started his career working with BD-J for Deluxe Digital Studios (DDS), Panasonic, and Deluxe Digital Distribution (D3), where he worked on movie titles such as Avatar, Oceans, Spinal Tap, and Conquest of the Planet of the Apes along with many other titles and web-enabled BluRay features. During his time at D3, Robert made the transition from BD-J to Android development where he was a principal developer on the StarzPlay, EncorePlay, and MovieplexPlay apps. He also worked on the NookVideo app developed for non-Nook devices. During his time with D3 Robert moved to Nashville, TN and eventually found Aloompa LLC where he found a home as a Senior Android Developer developing applications for festivals throughout the country such as Coachella, Stagecoach, Governsball, Bannaroo, and many more. Along with his fulltime professional pursuits, Robert is a seasoned freelancer with many projects in his repertoire (see his personal website www.rdjiii.info) and has started a software consulting company, Contrahere Solutions LLC (see www.contrahere.com). You can reach Robert by going to one of the websites previously mentioned or by e-mail anytime at robert.dale.johnson.iii@gmail. com. Robert is also an avid racquetball player who travels across the country playing in tournaments as a Team Ektelon player. He is a motorcycle enthusiast and loves to dabble in tech such as Arduino and RaspberryPi in his spare time. I would like to thank my son Xander Johnson for being the best son I could ever wish for. His love and appreciation drives me to become the best that I can, pushing me forward with a smile on my face and joy in my heart. Xander, I love you and thank you for everything you have and will do to make me a better person. Alex Lockwood is an experienced developer/consultant in the Android community, and an active user on StackOverflow. His blog can be found at http://www.androiddesignpatterns.com. www.PacktPub.com Support files, eBooks, discount offers and more You might want to visit www.PacktPub.com for support files and downloads related to your book. Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at service@packtpub.com for more details. At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters and receive exclusive discounts and offers on Packt books and eBooks. TM http://PacktLib.PacktPub.com Do you need instant solutions to your IT questions? PacktLib is Packt's online digital book library. Here, you can access, read and search across Packt's entire library of books. Why Subscribe? • Fully searchable across every book published by Packt • Copy and paste, print and bookmark content • On demand and accessible via web browser Free Access for Packt account holders If you have an account with Packt at www.PacktPub.com, you can use this to access PacktLib today and view nine entirely free books. Simply use your login credentials for immediate access. Table of Contents Preface 1 Chapter 1: Fragments and UI Modularization 5 The need for a new approach to UI creation 5 The broad platform support of fragments 6 Fragments simplify common Android tasks 7 The relationship between fragments and activities 7 Making the shift to fragments 8 The old thinking – activity-oriented 8 Defining the activity appearance 9 Displaying the activity UI 10 The new thinking – fragment-oriented 11 Creating the fragment layout resources 11 Creating the Fragment class 14 Converting the activity to use fragments 16 Summary 18 Chapter 2: Fragments and UI Flexibility 19 Creating UI flexibility 19 Dynamic fragment layout selection 20 Adding an alternate layout resource 22 Managing fragment layout by screen size 25 Eliminating redundancy 27 Design fragments for flexibility 30 Avoiding tight coupling 30 Abstracting fragment relationships 30 Encapsulating fragment operations 33 Loosely connecting the pieces 34 Fragments protect against the unexpected 36 Evolving layout resource files 36 Creating the book description activity 37 Table of Contents [ ii ] Making the MainActivity class adaptive 38 Summary 40 Chapter 3: Fragment Lifecycle and Specialization 41 Understanding the fragment lifecycle 42 Understanding fragment setup and display 42 Avoiding method name confusion 44 Understanding fragment hide and teardown 45 Maximizing available resources 46 Managing a fragment state 46 Special purpose fragment classes 47 ListFragment 47 Associating data with the list 47 Separating data from display 48 DialogFragment 52 Styles 53 Layout 54 DialogFragment display 55 Event handling 56 Dialog identity 57 Summary 61 Chapter 4: Working with Fragment Transactions 63 Intentional screen management 63 Dynamically managing fragments 64 Deferred execution of transaction changes 65 Adding and removing fragments 66 Supporting the back button 68 Creating an adaptive application layout 70 Updating the layout to support dynamic fragments 70 Adapting to device differences 72 Dynamically loading a fragment at startup 73 Transitioning between fragments 74 Eliminating redundant handling 75 Creating the fragment on-the-fly 76 Managing asynchronous creation 76 Putting it all together 79 Summary 80 Chapter 5: Creating Rich Navigation with Fragments 81 A brave new world 81 Making navigation fun with swipe 82 Implementing swipe navigation 84 Managing the swipe fragments 84 Putting the swipe UI into place 86 Android Studio and swipe navigation 89 Table of Contents [ iii ] Improving navigation with the ActionBar 90 Navigating randomly with tabs 90 Managing tab selection 91 Connecting the fragments to the tabs 94 Providing direct access with drop-down list navigation 95 Managing fragment selection 96 Providing the navigation choices 98 Android Studio and drop-down list navigation 100 Summary 100 Index 101 Preface Long gone are the days of the mobile apps with a static UI squished onto a tiny screen. Today's users expect mobile apps to be dynamic and highly interactive. They expect an app to look fantastic when they're looking at it on their medium-resolution smartphone, and that same app needs to look just as fantastic when they switch over to using it on their high-resolution tablet. Apps need to provide rich navigation features. Also, apps need to be adaptive and responsive. Trying to meet these demands using Android's traditional activity-centric UI design model is difficult. As developers, we need more control than that afforded by activities. We need a new approach: fragments give us that new approach. In this book, you'll learn how to use fragments to meet the challenges of creating dynamic UIs in the modern world of mobile app development. What this book covers Chapter 1, Fragments and UI Modularization, introduces fragments, UI modularization, and the role fragments play in developing a modularized UI. This chapter demonstrates the creation of simple fragments and using fragments statically within activities. Chapter 2, Fragments and UI Flexibility, builds on the concepts introduced in the previous chapter to provide solutions to specific differences in device layouts. This chapter explains how to use adaptive activity layout definitions to provide support for a wide variety of device form factors, with a small set of fragments that automatically rearrange based on the current device's UI requirements. Preface [ 2 ] Chapter 3, Fragment Lifecycle and Specialization, discusses the relationship of the lifecycle of fragments to that of activities, and demonstrates the appropriate programming actions at the various points in the lifecycle. Leveraging this knowledge, the special purpose fragment classes ListFragment and DialogFragment are introduced to demonstrate their behavior and provide a deeper understanding of how their behavior in the activity lifecycle differs from that of standard fragments. Chapter 4, Working with Fragment Transactions, explains how to create multiple app screens within a single activity, by dynamically adding and removing fragments using fragment transactions. Topics covered include, implementing back button behavior and dynamically adapting multi-fragment UIs to differences in device characteristics. Chapter 5, Creating Rich Navigation with Fragments, brings everything together by building on the previous chapters to show how to use fragments to enhance the user's experience through rich navigation features. This chapter demonstrates how to implement a number of navigation features, including screen browsing with swipe-based paging, direct screen access with the drop-down list navigation, and random screen viewing with tabs. What you need for this book To follow the examples in this book, you should have a basic knowledge of Android programming and a working Android development environment. This book focuses primarily on Android Studio as the Android development environment, but other tools such as Eclipse with the ADT plugin, JetBrains' IntelliJ IDEA, or a similar Android-enabled development tool can be used. Who this book is for This book is for anyone with a basic understanding of Android programming, who would like to improve the appearance and usability of their applications. Whether you're looking to create a more interactive user experience, create more dynamically adaptive UIs, provide better support for tablets and smartphones in a single app, reduce the complexity of managing your app UIs, or just trying to expand your UI design philosophy, this book is for you. Preface [ 3 ] Conventions In this book, you will find a number of styles of text that distinguish between different kinds of information. Here are some examples of these styles, and an explanation of their meaning. Code words in text are shown as follows: "An application initially calls the startActivity method to display an instance of Activity1. Activity1." A block of code is set as follows: First View Second View Third View New terms and important words are shown in bold. Words that you see on the screen, in menus or dialog boxes for example, appear in the text like this: "Select layout as the Resource type." Warnings or important notes appear in a box like this. Tips and tricks appear like this. Reader feedback Feedback from our readers is always welcome. Let us know what you think about this book—what you liked or may have disliked. Reader feedback is important for us to develop titles that you really get the most out of. To send us general feedback, simply send an e-mail to feedback@packtpub.com, and mention the book title via the subject of your message. If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, see our author guide on www.packtpub.com/authors. Preface [ 4 ] Customer support Now that you are the proud owner of a Packt book, we have a number of things to help you to get the most from your purchase. Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. Errata Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you find a mistake in one of our books—maybe a mistake in the text or the code—we would be grateful if you would report this to us. By doing so, you can save other readers from frustration and help us improve subsequent versions of this book. If you find any errata, please report them by visiting http://www.packtpub. com/submit-errata, selecting your book, clicking on the errata submission form link, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata will be uploaded on our website, or added to any list of existing errata, under the Errata section of that title. Any existing errata can be viewed by selecting your title from http://www.packtpub.com/support. Piracy Piracy of copyright material on the Internet is an ongoing problem across all media. At Packt, we take the protection of our copyright and licenses very seriously. If you come across any illegal copies of our works, in any form, on the Internet, please provide us with the location address or website name immediately so that we can pursue a remedy. Please contact us at copyright@packtpub.com with a link to the suspected pirated material. We appreciate your help in protecting our authors, and our ability to bring you valuable content. Questions You can contact us at questions@packtpub.com if you are having a problem with any aspect of the book, and we will do our best to address it. Fragments and UI Modularization This chapter introduces fragments, UI modularization, and the role fragments play in developing a modularized UI. The chapter demonstrates creating simple fragments and using fragments statically within activities. Let us have a look at the topics to be covered: • The need for UI modularization • Fragments are the foundation of modularization • Support for fragments across Android versions • Creating fragments By the end of this chapter, we will be able to create and use fragments within a static activity layout. The need for a new approach to UI creation Chances are that the first class you learned to use when you became an Android developer was the Activity class. After all, the Activity class provided your app with a user interface. By organizing your user interface components onto an activity, the activity became the canvas on which you were painting your application masterpiece. In the early days of Android, building an application's user interface directly within an activity worked reasonably well. The majority of early applications had a relatively simple user interface and the number of different Android device form factors was small. In most cases, with the help of a few layout resources, a single activity worked fine across different device form factors. Fragments and UI Modularization [ 6 ] Today, Android devices come in a wide variety of form factors with incredible variation in their size and shape. Combine this with the rich, highly interactive user interfaces of modern Android applications, and the creation of a single activity that effectively manages the user interface across such divergent form factors becomes extremely difficult. A possible solution is to define one activity to provide the user experience for a subset of device form factors; for example, smartphones. Then define another activity for a different subset of form factors such as tablets. The problem with this approach is that activities tend to have a lot of responsibilities beyond simply rendering the user interface. With multiple activities performing essentially the same tasks, we must either duplicate the logic within each of the activities, or increase the complexity of our program by finding ways to share the logic across the activities. The approach of using different activities for different form factors also substantially increases the number of activities in the program, easily doubling or tripling the number of activities required. We need a better solution. We need a solution that allows us to modularize our application user interface into sections that we can arrange as needed within an activity. Fragments are that solution. Android fragments allow us to partition the user interface into functional groupings of user interface components and logic. An activity can load and arrange the fragments as needed for a given device form factor. The fragments take care of the form factor details while the activity manages the overall user interface issues. The broad platform support of fragments The Fragment class was added to Android at API Level 11 (Android 3.0). This was the first version of Android that officially supported tablets. The addition of tablet support exacerbated an already difficult problem; developing Android applications was becoming increasingly difficult because of the wide variety of Android device form factors. Fortunately, fragments provide a solution to the problem. With fragments, we can much more easily create applications that support a variety of form factors, because we can partition our user interfaces into effective groupings of components and their associated logic. There was one problem with fragments. Up until very recently, the majority of Android devices had an API Level below 11 and therefore didn't support fragments. Fortunately, Google released the Android Support Library, available at http://developer.android.com/tools/extras/support-library.html, which makes fragments available to any device running API Level 4 (Android 1.6) or above. With the Android Support Library, fragments are now available to virtually every Android device in use. Chapter 1 [ 7 ] Applications created with Android Studio automatically include the Android Support Library, and therefore support fragments on virtually all SDK versions in use. If you will be using a development tool other than Android Studio to create applications that target devices running on a SDK level below 11, see the Android Developers Blog post, Fragments For All, available at http://android-developers. blogspot.com/2011/03/fragments-for-all.html, for directions on manually adding the Android Support Library to your projects. Fragments simplify common Android tasks Fragments not only simplify the way we create our application user interfaces but they also simplify many of the built-in Android user interface tasks. User interface concepts such as tabbed displays, list displays, and dialog boxes have all historically had distinctly different approaches. When we think about it, though, they are all variations on a common concept, that is, combining user interface components and logic into a functional group. Fragments formalize this concept, and therefore allow us to take a consistent approach to these formerly disparate tasks. We talk about each of these issues in detail as well as some of the specialized fragment classes such as the DialogFragment class and the ListFragment class later in this book. The relationship between fragments and activities Fragments do not replace activities but rather supplement them. A fragment always exists within an activity. An activity instance can contain any number of fragments but a given fragment instance can only exist within a single activity. A fragment is closely tied to the activity on which it exists and the lifetime of that fragment is tightly coupled to the lifetime of the containing activity. We'll talk much more about the close relationship between the lifetime of a fragment and the containing activity in Chapter 3, Fragment Lifecycle and Specialization. One thing we don't want to do is make the common mistake of overusing fragments. So often when someone learns about fragments, they make the assumption that every activity must contain fragments, and that's simply not the case. As we go through this book, we'll discuss the features and capabilities of fragments and a variety of scenarios where they work well. We'll always want to keep those in mind as we're building our applications. In those situations where fragments add value, we definitely want to use them. However, it is equally important that we avoid complicating our applications by using fragments in those cases where they do not provide value. Fragments and UI Modularization [ 8 ] Making the shift to fragments Although fragments are a very powerful tool, fundamentally they do something very simple. Fragments group user interface components and their associated logic. Creating the portion of your user interface associated with a fragment is very much like doing so for an activity. In most cases, the view hierarchy for a particular fragment is created from a layout resource; although, just as with activities, the view hierarchy can be programmatically generated. Creating a layout resource for a fragment follows the same rules and techniques as doing so for an activity. The key difference is that we're looking for opportunities to partition our user interface layout into manageable subsections when working with fragments. The easiest way to get started working with fragments is for us to walk through converting a traditional activity-oriented user interface to use fragments. The old thinking – activity-oriented To get started, let's first look at the appearance and structure of the application we're going to convert. This application contains a single activity that, when run, looks like the following screenshot: The activity displays a list of five book titles in the top portion of the activity. When the user selects one of those books titles, the description of that book appears in the bottom portion of the activity. Chapter 1 [ 9 ] Defining the activity appearance The appearance of the activity is defined in a layout resource file named activity_main.xml that contains the following layout description: Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub. com/support and register to have the files e-mailed directly to you. This layout resource is reasonably simple and is explained as follows: • The overall layout is defined within a vertically-oriented LinearLayout element containing the two ScrollView elements • Both of the ScrollView elements have a layout_weight value of 1 that causes the top-level LinearLayout element to divide the screen equally between the two ScrollView elements • The top ScrollView element, with the id value of scrollTitles, wraps a RadioGroup element containing a series of the RadioButton elements, one for each book • The bottom ScrollView element, with the id value of scrollDescription, contains a TextView element that displays the selected book's description Displaying the activity UI The application's activity class, MainActivity, inherits directly from the android.app.Activity class. To display the activity's user interface, we override the onCreate method and call the setContentView method passing the R.layout.activity_main layout resource ID. Chapter 1 [ 11 ] public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // load the activity_main layout resource setContentView(R.layout.activity_main); } // Other methods elided for clarity } The new thinking – fragment-oriented The activity-oriented user interface we currently have would be fine if all Android devices had the same form factor. As we've discussed, that's not the case. We need to partition the application user interface so that we can switch to a fragment-oriented approach. With proper partitioning, we can be ready to make some simple enhancements to our application to help it adapt to device differences. Let's look at some simple changes we can make that will partition our user interface. Creating the fragment layout resources The first step in moving to a fragment-oriented user interface is to identify the natural partitions in the existing user interface. In the case of this application, the natural partitions are reasonably easy to identify. The list of book titles is one good candidate, and the book description is the other. We'll make them each a separate fragment. Defining the layout as a reusable list For the list of book titles, we have the option to define the fragment to contain either the ScrollView element that's nearest to the top (has an id value of scrollTitles) or just the RadioGroup element within that ScrollView element. When creating a fragment, we want to structure it such that the fragment is most easily reused. Although the RadioGroup element is all we need to display the list of titles, it seems likely that we'll always want the user to be able to scroll the list of titles if necessary. With this being the case, it makes sense to include the ScrollView element in this fragment. Fragments and UI Modularization [ 12 ] To create a fragment for the book list, we define a new layout resource file called fragment_book_list.xml. We copy the top ScrollView element and its contents from the activity_main.xml resource file to the fragment_book_list.xml resource file. The resulting fragment_book_list.xml resource file is as follows: This gives us a layout resource consistent with the book title portion of the user interface as it appeared in the activity layout resource. This is a good start. Minimize assumptions An effective fragment-oriented user interface is constructed with layout resources that minimize assumptions about where and how the fragment is used. The fewer assumptions we make about a fragment's use, the more reusable the fragment becomes. Chapter 1 [ 13 ] The layout in the fragment_book_list.xml resource file as we now have it is very limiting because it includes significant assumptions. For example, the root ScrollView element includes a layout_height attribute with a value of 0. This assumes that the fragment will be placed within a layout that calculates the height for the fragment. A layout_height attribute value of 0 prevents the ScrollView element from properly rendering when we use the fragment within any of the many layouts that require the ScrollView element to specify a meaningful height. A layout_height attribute value of 0 prevents the fragment from properly rendering even when doing something as simple as placing the fragment within a horizontally oriented LinearLayout element. The layout_weight attribute has similar issues. In general, a good practice is to design the fragment to fully occupy whatever space it is placed within. This gives the layout in which the fragment is used the most control over the placement and sizing of the fragment. To do this, we'll remove the layout_weight attribute from the ScrollView element and change the layout_height attribute value to match_parent. Because the ScrollView element is now the root node of the layout resource, we also need to add the android namespace prefix declaration. The following code snippet shows the updated ScrollView element: With the updated ScrollView element, the fragment layout can now adapt to almost any layout it's referenced within. Fragments and UI Modularization [ 14 ] Encapsulating the display layout For the book description, we'll define a layout resource file called fragment_book_desc.xml. The fragment layout includes the contents of the activity layout resource's bottom ScrollView element (has an id value of scrollDescription). Just as in the book list fragment, we'll remove the layout_weight attribute, set the layout_height attribute to match_parent, and add the android namespace prefix declaration. The fragment_book_desc.xml layout resource file appears as follows: Creating the Fragment class Just like when creating an activity, we need more than a simple layout definition for our fragment; we also need a class. Wrapping the list in a fragment All fragment classes must extend the android.app.Fragment class either directly or indirectly. For projects that rely on the Android Support Library to provide fragment support for pre-API Level 11 (Android 3.0) devices, use the android.support.v4.app.Fragment class in place of the android.app.Fragment class. Chapter 1 [ 15 ] We'll call the class for the fragment that manages the book list, BookListFragment. The class will directly extend the Fragment class as follows: Import android.app.Ftragment; public class BookListFragment extends Fragment { … } During the creation of a fragment, the Android framework calls a number of methods on that fragment. One of the most important of these is the onCreateView method. The onCreateView method is responsible for returning the view hierarchy represented by the fragment. The Android framework attaches that returned view hierarchy for the fragment to the appropriate place in the activity's overall view hierarchy. In a case like the BookListFragment class where the Fragment class inherits directly from the Fragment class, we must override the onCreateView method and perform the work necessary to construct the view hierarchy. The onCreateView method receives three parameters. We'll focus on just the first two for now: • inflater: This is a reference to a LayoutInflater instance that is able to read and expand layout resources within the context of the containing activity • container: This is a reference to the ViewGroup instance within the activity's layout where the fragment's view hierarchy is to be attached The LayoutInflater class provides a method called inflate that handles the details of converting a layout resource into the corresponding view hierarchy and returns a reference to the root view of that hierarchy. Using the LayoutInflater.inflate method, we can implement our BookListFragment class' onCreateView method to construct and return the view hierarchy corresponding to the R.layout.fragment_book_list layout resource as shown in the following code: @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View viewHierarchy = inflater.inflate(R.layout.fragment_book_list, container, false); return viewHierarchy; } Fragments and UI Modularization [ 16 ] You'll notice in the preceding code we include the container reference and a Boolean value of false in the call to the inflate method. The container reference provides the necessary layout parameters for the inflate method to properly format the new view hierarchy. The parameter value of false indicates that container is to be used only for the layout parameters. If this value were true, the inflate method would also attach the new view hierarchy to the container view group. We do not want to attach the new view hierarchy to the container view group in the onCreateView method because the activity will handle that. Providing the display fragment For the book description fragment, we'll define a class called BookDescFragment. This class is identical to the BookListFragment class except the BookDescFragment class uses the R.layout.fragment_book_desc layout resource as follows: public class BookDescFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View viewHierarchy = inflater.inflate(R.layout.fragment_book_desc, container, false); return viewHierarchy; } } Converting the activity to use fragments With the fragments defined, we can now update the activity to use them. To get started, we'll remove all the book titles and description layout information from the activity_main.xml layout resource file. The file now contains just the top-level LinearLayout element and comments to show where the book titles and description belong as follows: Chapter 1 [ 17 ] Using the fragment element, we can add a fragment to the layout by referencing the fragment's class name with the name attribute. For example, we reference the book list fragment's class, BookListFragment, as follows: We want our activity user interface to appear the same using fragments as it did before we converted it to use fragments. To do this, we add the same layout_width, layout_height, and layout_weight attribute values to the fragment elements as were on the ScrollView elements in the original layout. With that, the complete layout resource file for the activity, activity_main.xml, now looks like the following code: Fragments and UI Modularization [ 18 ] If you are working with Android Studio, you might find a tools:layout attribute on the fragment element. This attribute is used by Android Studio to provide a preview of the layout within the graphical designer. It has no effect on your application's appearance when the application is run. When the application is run, the user interface will now appear exactly as it did when it was defined entirely within the activity. If we're targeting Android devices running API Level 11 (Android 3.0) or later, there is no need to make any changes to the Activity class because the Activity class is simply loading and displaying the layout resource at this point. Activities and backward compatibility When using the Android Support Library to provide pre-API Level 11 (Android 3.0) fragment support, we have one additional step. In this case, we have to make one small, but important change to our activity. We must change the MainActivity class' base class from the Activity class to the android.support.v4.app.FragmentActivity class. Because the pre-API Level 11 Activity class doesn't understand fragments, we use the FragmentActivity class from the Android Support Library to add fragment support to our MainActivity class. Summary The shift from the old thinking of being activity-oriented to the new thinking of being fragment-oriented opens our applications up to rich possibilities. Fragments allow us to better organize both the appearance of the user interface and the code we use to manage it. With fragments, our application user interface has a more modular approach that frees us from being tied to the specific capabilities of a small set of devices and prepares us to work with the rich devices of today, and the wide variety of new devices to come tomorrow. In the next chapter, we'll build on the modularized user interface we've created with fragments to enable our application to automatically adapt to differences in the various device form factors with only minimal changes to our application. Fragments and UI Flexibility This chapter builds on the concepts introduced in the previous chapter to provide solutions to addressing specific differences in device layouts. The chapter explains the use of adaptive Activity layout definitions to create apps that automatically rearrange their user interface in response to differences in device form factors. With adaptive Activity layout definitions, applications are able to support a wide variety of devices using just a few properly designed fragments. In this chapter, we will cover the following topics: • Simplifying the challenge of supporting device differences • Dynamic resource selection • Coordinating fragment content • The role of FragmentManager • Supporting fragments across activities By the end of this chapter, we will be able to implement a user interface that uses fragments to automatically adapt to differences in device layouts and coordinates user actions across the involved fragments. Creating UI flexibility Utilizing fragments in our user interface design provides a good foundation for creating applications that more easily adapt to device differences, but we must go a little further to create truly flexible UIs. We must design our application such that the fragments that make up the UI are easily rearranged in response to the characteristics of the device on which the app is currently running. Fragments and UI Flexibility [ 20 ] To achieve this, we must use some techniques to dynamically change the layout of individual fragments in response to the current device's characteristics. Once we employ such a technique, we must be sure that we implement our fragments in such a way that each fragment is able to function effectively independent of layout changes that might affect the behavior or even existence of other fragments within the activity. Dynamic fragment layout selection As we mentioned in the previous section, creating a flexible UI requires that the layout and positioning of fragments within an activity must be able to change in response to differences in device characteristics. We can include code in our application to dynamically arrange fragments in response to the form factor of the device on which our app is running, but in most cases, doing so is not only unnecessary but also undesirable. The deeper the dependencies between the user interface and application code, the more difficult maintaining and enhancing an application becomes. Although there will always be some degree of dependency between our user interface and application code, we want to minimize such dependencies and instead do as much of our user interface layout-related work within layout resources as possible. The easiest way to build flexibility into our application user interface is to take advantage of the Android resource system's built-in device adaptability. Android allows us to design different layout-related resources for our application with each optimized for and associated with a specific set of device characteristics. At runtime, the Android resource system takes care of automatically selecting and loading the appropriate resources for the current device. Although this feature can be used to dynamically modify the layout of any activity, we'll see that it is particularly effective when used in conjunction with fragments. To see Android resource selection in action, let's continue with our application from the previous chapter. As you'll recall, the layout for our activity is in the activity_main.xml resource file and looks like this: This layout stacks our fragments, BookListFragment and BookDescFragment, one on top of the other. Although that layout renders well on a smartphone held vertically in the portrait orientation, rotating the phone so that it's held horizontally in the landscape orientation creates a much less attractive appearance as seen here: The current layout is clearly not making the best use of the available screen space in this orientation. When the phone is orientated in landscape, the application would look much better if we position the two fragments side-by-side. Fragments and UI Flexibility [ 22 ] Adding an alternate layout resource We can add support for an alternative layout to our application by creating a new resource file with the fragments appropriately arranged. To create the resource file, we first add another folder under the res folder of the project tree called layout-land. The resource folder name creates the association between the resource file and the device characteristics, not any special behavior on the part of Android Studio. To create the new folder in Android Studio, perform the following steps: 1. Expand the src folder in the project explorer window. 2. Expand the main folder under src. 3. Right-click on the res folder under main. 4. Select New. 5. Select Android resource directory to open the New Resource Directory dialog. 6. Select layout as Resource type:. 7. Highlight Orientation under Available qualifiers: and click on the >> button to move it to Chosen qualifiers:. 8. Select Landscape under Screen orientation:. The New Resource Directory dialog will appear similar to the following screenshot: Chapter 2 [ 23 ] Now copy the activity_main.xml resource file from the layout resource folder to the layout-land resource folder. We now have two copies of the activity_main.xml resource file as shown in the following screenshot: We can now modify the activity_main.xml resource file in the layout-land folder to arrange the fragments to render properly when the phone is in landscape orientation. First, we switch the LinearLayout element from a vertical to a horizontal orientation. We then change the layout_width values for each fragment to 0dp and the layout_height values to match_parent. We can leave each of the fragment's layout_weight value as 1 so that LinearLayout spaces them equally left to right. The updated resource file looks like this: Fragments and UI Flexibility [ 24 ] Having done nothing more than adding this simple resource file to our project, the application now displays the list of titles and book description next to one another when run on a device held in a landscape orientation as shown in the following screenshot: During runtime, when the MainActivity class loads the R.layout.activity_main resource, the Android resource system returns the appropriate version of the activity_main.xml resource file for that orientation. When the user rotates the device to a different orientation, Android automatically recreates the activity and loads the appropriate resource for the new orientation. The Android environment detects a wide variety of device form factor characteristics. By taking advantage of fragments, we are able to create an application that easily adapts to device differences by simply providing different layout resource files that shift around the location of our fragments as if they are puzzle pieces. Without fragments, we would've had to provide the entire layout for the activity, radio buttons, text views, everything, in both of the layout files. We would then find ourselves having to maintain two complex, almost identical files. By using fragments, the individual pieces are self-contained and non-duplicated. Fragments modify the layout in an easy manner and simplify our application maintenance. Chapter 2 [ 25 ] Managing fragment layout by screen size The same technique we use to adapt our user interface to device orientation differences can be taken much further to work with differences in screen size. The Android resource system has device screen size awareness and therefore supports creating corresponding resource folders. The resource selection can be based on general screen size groups or specific screen size constraints. Differences in device screen size are one of the most common reasons for using layout resources to manage fragments. With this being the case, understanding how to use layout resources to deal with differences in screen size is essential to working effectively with fragments. Resource screen size groups The configuration information of each Android device includes the screen size group to which the device belongs. The four screen size groups are small, normal, large, or xlarge. For specific information on the size of screens in each group, see Range of screens supported in the Android documentation available at http://developer.android.com/guide/ practices/screens_support.html#range. Just as we created a specific layout resource file for landscape orientation, we can create a layout resource file targeting a particular screen size group. We associate the resource file with the desired screen size group by placing the file in the appropriately named resource folder. For example, we place a layout resource file designed for devices with a screen size group of large in the layout-large resource folder. Screen size groups date back to the early days of Android when there was little practical experience in dealing with the wide variety of device form factors that now exist. As time has gone on and the number of Android device form factors has grown, file size groups have turned out to be a less than ideal solution. Problems with screen size groups stem from two main issues: 1. The size range of the groups is not consistently applied, which results in the size range within the groups overlapping one another. One device with a 7-inch screen might be classified as large while another device with the same size screen might be classified as xlarge. Fragments and UI Flexibility [ 26 ] 2. Groups are sometimes too broad. For example, the large group includes devices with 5-inch screens and devices with 7-inch screens. These screen sizes tend to have very different layout requirements. A device with a 5-inch screen tends to work best with handset-style layouts like that for a smartphone, whereas a device with a 7-inch screen tends to work best with a tablet-style layout. Size groups are still in use because they are the best option available for dealing with screen size differences on pre-API Level 13 devices. Fortunately, less than half of the Android devices in use today are pre-API Level 13, and the ratio is shrinking rapidly. For information on the distribution of devices in use by API Level or by screen size group, see the Android developer, Dashboards, available at http://developer.android.com/about/dashboards. Resource screen size qualifiers At API Level 13 (Android 3.2), Android introduced a substantial improvement over screen size groups known as resource screen size qualifiers. Resource screen size qualifiers allow us to associate resources with specific screen size requirements. With screen size qualifiers, we have a very detailed level of control over which layout resources are associated with each device form factor. To avoid the complications inherent in the wide variety of screen pixel densities and physical screen sizes available, Android uses a canonicalized unit of measure called the density independent pixel (dp) when managing screen sizes. If you've been working with Android for any length of time, you are probably already familiar with density independent pixels as they are the preferred unit of measure when positioning and sizing views within an Android user interface. A dp always corresponds to the physical size of a pixel on a 160 dpi device and therefore provides a consistent unit of measure independent of the physical pixel size of the device. For example, one 7-inch display device may have a physical pixel count of 1280x720 while another 7-inch display device has a physical pixel count of 1920x1080, but both devices have a dp count of approximately 1000x600. The Android platform takes care of the details of mapping between density independent pixels and the physical pixels of a device. Chapter 2 [ 27 ] Android provides three types of screen size qualifiers: smallest width, available screen width, and available screen height: • Smallest width screen size qualifier: This is referred to as smallest screen width in the Android Studio New Directory Resource dialog. It corresponds to the number of device independent pixels at the screen's narrowest point independent of the device orientation. Changing the device orientation does not change the device's smallest width. We specify the name of a resource folder based on the device's smallest width by adding sw, followed by the desired screen size in device independent pixels, followed by dp. For example, a layout resource folder containing layout resource files for devices with a smallest width of at least 600 dp is named layout-sw600dp. • Available width screen size qualifier: This is referred to as screen width in the Android Studio New Directory Resource dialog. It corresponds to the number of device independent pixels measured left to right at the device's current orientation. Changing the device orientation changes the available width. We specify the name of a resource folder based on available width by adding w, followed by the width in density independent pixels, followed by dp. A layout resource folder containing resource files for a device with an available width of at least 600 dp is named layout-w600dp. • Available height screen size qualifier: This is referred to as screen height in the Android Studio New Directory Resource dialog. It corresponds to the number of device independent pixels measured top to bottom, but otherwise behaves identically to the available width screen size qualifier, and follows the same naming pattern except that h is used instead of w. A layout resource folder containing resource files for a device with an available height of at least 600 dp is named layout-h600dp. Eliminating redundancy As the number of form factors our application targets grow, managing the resource files within the different layout resource folders can become somewhat complicated due to the fact that we'll likely want to use the same layout resource file for different qualifiers. To demonstrate this problem, let's update our application to use the version of the activity_main.xml resource file we currently use for landscape-oriented devices on other devices. We'll use that same resource file on devices in the large screen size group and on devices with a current width of 600 dp or greater. Fragments and UI Flexibility [ 28 ] We first create two additional folders under our res folder: layout-large and layout-w600dp. We then copy the activity_main.xml file from the layout-land folder to the two folders we just created. Doing this is easy enough but we now have a maintenance headache. Every time we make a change to that layout, we have to be sure that we make it in all three folders. To avoid this resource file duplication, we can use layout aliasing. Layout aliasing Layout aliasing allows us to have just a single copy of each layout resource file. We can then provide the resource system with information as to which file to choose for each form factor. To get started, we'll rename the activity_main.xml resource file in the layout-land resource folder as activity_main_wide.xml. We then move the file to the layout resource folder and delete the layout-land folder. We now create a new resource folder called values-land under the res folder. To create the folder in Android Studio, follow the same steps as we used earlier to create the layout-land folder except set the Resource type: as values rather than layout. Within this folder, we create a new resource file, the name of which doesn't matter, but the file containing values for aliasing is often named refs.xml because it contains a list of references to other resources, so that's what we'll use. To create the file using Android Studio, perform the following steps: 1. Right-click on the values-land resource folder. 2. Select New. 3. Select the values resource file. 4. Specify refs.xml as the filename. In the refs.xml file, be sure that there is already a root element named resources. Within that element, add an item element with a type attribute value of layout. This indicates that we're providing an alias entry for a layout resource. We set the value of the name attribute to be the name of the default layout resource, which in our case is activity_main. We then give the item element a value of @layout/ activity_main_wide. The complete refs.xml resource file now appears as follows: @layout/activity_main_wide Chapter 2 [ 29 ] With this file in place, any call to load the layout resource R.layout.activity_main will instead load R.layout.activity_main_wide when the application is running on a device in landscape orientation. To add support for devices in the large group and those with a current width of at least 600 dp, we simply create two more resource folders, values-large and values-w600dp, and copy the refs.xml file from the values-land folder to each. The layout and values resource folders now appear as shown in the following screenshot: We now have support for all the desired form factors with no unnecessary duplication of layout resource files. We do have duplication of the refs.xml file, but it's a much simpler file than the layout resource file and is less likely to change. See Table 2 of the Android Providing Resources guide for the order of precedence Android follows when performing layout aliasing, available at http://developer.android.com/guide/topics/ resources/providing-resources.html. Fragments and UI Flexibility [ 30 ] Design fragments for flexibility With our user interface well-partitioned and adaptable, we need to be sure that each fragment functions effectively, as layout differences cause the behavior and possibly even the existence of other fragments within the activity to change. When an application user interface is divided into fragments, the fragments exist completely independent of one another rarely. Very often a user's interaction with one fragment has some effect on other fragments within the same activity. In the case of our application, this issue arises when a user selects a book within BookListFragment. In response to the user's selection, the application is responsible for displaying the corresponding description in BookDescFragment. Avoiding tight coupling One possible solution to coordinating fragment content is to allow the fragments to directly communicate with one another. To coordinate content within our application, we could pass the BookDescFragment reference into BookListFragment when we first create the activity. In response to each user selection within BookListFragment, BookListFragment would then directly update TextView contained within BookDescFragment. Although simple to implement, this solution is problematic because it tightly couples the two Fragment classes to each other. The BookListFragment fragment is only usable within activities that also contain the BookDescFragment fragment, and making changes to the layout of BookDescFragment may potentially break BookListFragment. We always want to keep in mind that a key goal of using fragments is to be well-partitioned and adaptable. Abstracting fragment relationships Instead of creating direct relationships between the fragments, we can take advantage of the abstraction provided by interfaces. By defining a simple callback interface to represent the act of a user making a book selection, we can completely eliminate tight coupling between fragments. The BookListFragment class can be written to provide notification of a user selection through the interface. By implementing the interface on the activity, the activity can then handle coordinating the user selection within BookListFragment with updating the displayed description within BookDeskFragment. Chapter 2 [ 31 ] Defining the callback interface The callback interface should include methods for any interaction with the fragment that may be meaningful to the activity containing the fragment. At the same time, the interface should not burden the activity with unnecessary details. The interface should be focused on application-level actions such as selecting a book rather than implementation-level actions such as tapping on a radio button. The implementation- level details should be isolated within the fragment. We should also be sure to design the interface without any preconceived ideas of what the activity will do with the notification. In the case of BookListFragment, the only action of interest to the activity is the user selecting a book. This tells us the interface needs just a single method; we'll call the interface method onSelectedBookChanged. We know in the case of this application, the goal is to display the selected book description, so one possibility is to have the onSelectedBookChanged method include a parameter for the book description. The problem with passing the book description is that doing so limits the use of BookListFragment to just this one use case, displaying the book description. Instead, by passing an identifier for the book, BookListFragment is available for any use case in which the user selects a book. For simplicity, in our example, we'll use an array index as the identifier; in a real scenario, the identifier would more likely be a key to locate the book information within a data store or service. We'll call our new interface OnSelectedBookChangeListener. The interface looks like this: public interface OnSelectedBookChangeListener { void onSelectedBookChanged(int bookIndex); } Making the fragment self-contained The BookListFragment class needs to hide the details of user selections and instead translate each selection to a book identifier, which in our case is an array index. We first need to update the BookListFragment class to handle the radio button selections by implementing the RadioGroup.OnCheckedChangeListener interface as follows: public class BookListFragment extends Fragment implements RadioGroup.OnCheckedChangeListener { @Override public void onCheckedChanged(RadioGroup radioGroup, int id) { } Fragments and UI Flexibility [ 32 ] // Other members elided for clarity } Within the BookListFragment class' onCreateView method, we set the radio group's click listener as the BookListFragment class as shown here: public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View viewHierarchy = inflater.inflate( R.layout.fragment_book_list, container, false); // Connect the listener to the radio group RadioGroup group = (RadioGroup) viewHierarchy.findViewById(R.id.bookSelectGroup); group.setOnCheckedChangeListener(this); return viewHierarchy; } There are a number of ways to determine the book index corresponding to the selected radio button such as setting the tag value on each radio button or using a lookup table. For simplicity, we'll create a simple method containing a switch statement like the following code: int translateIdToIndex(int id) { int index = -1; switch (id) { case R.id.dynamicUiBook: index = 0 ; break; case R.id.android4NewBook: index = 1 ; break; case R.id.androidSysDevBook: index = 2 ; break; case R.id.androidEngineBook: index = 3 ; break; case R.id.androidDbProgBook: index = 4 ; break; } return index; } Chapter 2 [ 33 ] Fragment notification A fragment can always access the activity on which it is placed using the getActivity method. Within the BookListFragment class' onClick method, we can use the getActivity method to access the activity, cast it to the OnSelectedBookChangeListener interface, and then call the onSelectedBookChanged method and pass it the book index for the selected radio button as shown in the following code: public void onCheckedChanged(RadioGroup radioGroup, int id) { // Translate radio button to book index int bookIndex = translateIdToIndex(id); // Get parent Activity and send notification OnSelectedBookChangeListener listener = (OnSelectedBookChangeListener) getActivity(); listener.onSelectedBookChanged(bookIndex); } The BookListFragment class now completely handles notifying the parent activity of each change in the user book selection. Encapsulating fragment operations Within the BookDescFragment class, we want to encapsulate any details about how the user interface is updated. We'll do this by providing a simple method that accepts the book index and handles the details of locating and displaying the book description. Before we can implement that method, we first need to update the BookDescFragment class' onCreateView method to retrieve the list of book descriptions, retrieve a reference to TextView identified by R.id.bookDescription, and assign both to class-level fields as shown here: public class BookDescFragment extends Fragment { String[] mBookDescriptions; TextView mBookDescriptionTextView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View viewHierarchy = inflater.inflate( R.layout.fragment_book_desc, container, false); // Load array of book descriptions mBookDescriptions = getResources(). Fragments and UI Flexibility [ 34 ] getStringArray(R.array.bookDescriptions); // Get reference to book description text view mBookDescriptionTextView = (TextView) viewHierarchy.findViewById(R.id.bookDescription); return viewHierarchy; } } We can now add a setBook method that accepts the book index, accesses the appropriate book description, and updates mBookDescriptionTextView. The setBook method appears as follows: public void setBook(int bookIndex) { // Lookup the book description String bookDescription = mBookDescriptions[bookIndex]; // Display it mBookDescriptionTextView.setText(bookDescription); } Loosely connecting the pieces Good use of interfaces and encapsulation greatly simplify using any component, and fragments are no different. With the work we've done on the BookListFragment and BookDescFragment classes, our activity can now coordinate user interaction in BookListFragment by updating BookDescFragment in three simple steps: 1. Implement the OnSelectedBookChangeListener interface. 2. Get a reference to the BookDescFragment class. 3. Call the BookDescFragment class' setBook method. Have a look at step 2 first. Unlike when working with views, an activity cannot directly reference the fragments contained within it. Instead, fragment handling is delegated to the FragmentManager class. Each activity has a unique instance of the FragmentManager class. The FragmentManager class handles access to and management of all fragments within that activity. An activity accesses its FragmentManager instance with the getFragmentManager method. Chapter 2 [ 35 ] When working with the Android Support Library, use the FragmentActivity class' getSupportFragmentManager method in place of the standard Activity class' getFragmentManager method to access the current FragmentManager instance. With FragmentManager, an activity can access the contained fragments by calling the FragmentManager.findFragmentById method and passing the desired fragment's ID value from the layout resource. FragmentManager is an important class with a number of powerful capabilities. We'll talk much more about FragmentManager in Chapter 4, Working with Fragment Transactions. By using FragmentManager to access BookDescFragment, we can now implement the BookListFragment.OnSelectedBookChangeListener interface on our activity to update the displayed description for each user selection in BookListFragment. public class MainActivity extends Activity implements OnSelectedBookChangeListener{ @Override public void onSelectedBookChanged(int bookIndex) { // Access the FragmentManager FragmentManager fragmentManager = getFragmentManager(); // Get the book description fragment BookDescFragment bookDescFragment = (BookDescFragment) fragmentManager.findFragmentById (R.id.fragmentDescription); // Display the book title if(bookDescFragment != null) bookDescFragment.setBook(bookIndex); } // other members elided for clarity } Fragments and UI Flexibility [ 36 ] Fragments protect against the unexpected The true test of user interface flexibility is in how well the user interface design and implementation hold up when encountering an unexpected change request. A well-designed fragment-based user interface allows us to create incredible dynamic user interfaces that can evolve and change with minimal impact on the code. As an example, let's make what could potentially be a major design change on our application. Currently, the application always shows the book list and description on the same activity. The only difference is whether the fragments are positioned vertically or horizontally relative to one another. Imagine we receive feedback from our users that they don't like the way the app appears when viewed on a portrait-oriented handset. When viewed on a portrait-oriented handset, they would like the list and description to appear on separate activities. In all other cases, they want the app to continue to show the list and description side-by-side. Evolving layout resource files We first create a duplicate copy of the activity_main.xml resource file in the layout resource folder named activity_book_desc.xml. To do this in Android Studio, perform the following steps: 1. Right-click on the activity_main.xml file in the project explorer window and select Copy. 2. Right-click on the layout folder and select Paste. 3. Change the filename to activity_book_desc.xml. Remove the fragment element for BookListFragment from the activity_book_ desc.xml file so it now shows only BookDescFragment as in the following code: In the activity_main.xml resource file, remove BookDescFragment so that it now appears as follows: We now have layout resources for each of the activities. Remember that these changes will not affect the appearance of the app in scenarios that use the activity_main_wide.xml resource file. Creating the book description activity To display the book description, we add a simple activity named BookDescActivity that uses the activity_book_desc.xml layout resource. The activity relies on an "Intent extra" to pass the book index. Since BookDescFragment contains all the logic necessary to display a book description, we can simply get a reference to BookDescFragment and set the book index just as we did in the MainActivity class as shown here: public class BookDescActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_desc); Fragments and UI Flexibility [ 38 ] // Retrieve the book index from the Activity Intent Intent intent = getIntent(); int bookIndex = intent.getIntExtra(""bookIndex"", -1); if (bookIndex != -1) { // Use FragmentManager to access BookDescFragment FragmentManager fm = getFragmentManager(); BookDescFragment bookDescFragment = (BookDescFragment) fm.findFragmentById(R.id.fragmentDescription); // Display the book title bookDescFragment.setBook(bookIndex); } } } Making the MainActivity class adaptive The MainActivity class has some extra work to do now because the specific fragments contained within it will vary. When running on a device with a screen that is at least 600 dp wide or when running on a device in the large screen group, the MainActivity class always contains an instance of BookDescFragment. On the other hand, when running on other devices, the presence of BookDescFragment will depend upon the device's current orientation. We could add code to the MainActivity class to test for all of these various scenarios or we could take a simpler approach, that is, check whether the activity contains an instance of the BookDescFragment class. Using this approach, we have the MainActivity class' onSelectedBookChanged method to check the validity of BookDescFragment returned by FragmentManager. If FragmentManager returns a valid reference, the method can call setBook on BookDescFragment just as it has been. If the returned reference is not valid, the onSelectedBookChanged method calls startActivity with an Intent instance containing the information to display BookDescActivity that includes bookIndex as an extra as shown in the following code: public void onSelectedBookChanged(int bookIndex) { // Access the FragmentManager FragmentManager fm = getFragmentManager(); // Get the book description fragment BookDescFragment bookDescFragment = (BookDescFragment) fm.findFragmentById(R.id.fragmentDescription); // Check validity of fragment reference if(bookDescFragment == null || !bookDescFragment.isVisible()){ Chapter 2 [ 39 ] // Use activity to display description Intent intent = new Intent(this, BookDescActivity.class); intent.putExtra(""bookIndex"", bookIndex); startActivity(intent); } else { // Use contained fragment to display description bookDescFragment.setBook(bookIndex); } } Notice the if statement that checks the validity of bookDescFragment. In most cases, a simple check for whether the reference is null is all we need. The one exception is the case of when the app is run on a handset device on which the user has viewed the app in landscape orientation and then rotated the device to portrait. In this situation, the BookDescFragment instance is not visible but the activity's FragmentManager instance may be caching a reference to an invisible instance remaining from the landscape layout. For this reason, we check both for a null reference and for visibility. We'll discuss the details of fragment lifecycle, creation, and caching over the next two chapters. We now have adaptability built into our app. The scenarios that use the activity_main_wide.xml resource file look as they always did. On portrait-oriented handset devices, our app provides the user interface with two separate activities: one for the book list and one for the book description. The application now appears on portrait-oriented handset devices as shown here: Fragments and UI Flexibility [ 40 ] Summary Fragments provide our applications with a level of user interface flexibility that would be difficult to achieve otherwise. By properly designing our application to use fragments and associating the fragment resources with the appropriate device characteristics, we're able to build apps that contain a rich user interface that automatically adapts to the wide variety of Android device form factors that exist. We get all of these capabilities while writing only minimal code. In the next chapter, we dig into the lifecycle of fragments and explore how we can leverage the fragment lifecycle to create more responsive user interfaces and leverage specialized Fragment classes. Fragment Lifecycle and Specialization This chapter discusses the relationship of the lifecycle of fragments to that of activities, and demonstrates the appropriate programming actions at the various points in the lifecycle. The special purpose fragment classes ListFragment and DialogFragment are introduced with coverage of their use and how their behavior in the activity lifecycle differs from that of standard fragments. The following topics are covered in this chapter: • Fragment setup/display event sequence • Fragment teardown/hide event sequence • Working with the ListFragment class • Working with the DialogFragment class • Interacting with a DialogFragment class as a traditional Dialog class • Wrapping an existing Dialog class in a DialogFragment class By the end of this chapter, we will be able to coordinate the setup and teardown of fragments within their host activities, and be able to effectively utilize the ListFragment and DialogFragment classes. Fragment Lifecycle and Specialization [ 42 ] Understanding the fragment lifecycle One of the challenges of developing Android applications is assuring that our applications effectively handle the lifecycle of the application's activities. During the lifetime of an application, a given activity may be created, destroyed, and recreated many times. A simple action such as a user rotating a device from a portrait to landscape orientation, or vice-versa, normally causes the visible activity to be completely destroyed and recreated using the appropriate resources for the new orientation. Applications that do not cooperate effectively with this natural lifecycle will often crash or behave in some other undesirable manner. As we know, each fragment instance exists within a single activity; therefore, that fragment must cooperate in some way with the activity lifecycle. In fact, not only do fragments cooperate with the activity lifecycle but also they are intimately connected. In both the setup and display phase and hide and teardown phase, fragments provide many of the same lifecycle-related callback methods as activities. In addition, fragments provide additional lifecycle-related callback methods that relate to the fragment's relationship to the containing activity. As our applications become more sophisticated and we work with more specialized implementations of the fragment class, understanding the fragment class' lifecycle and the relationship to the activity lifecycle is essential. If you are unfamiliar with the basics of Android's activity lifecycle callback methods, please see the Activity Lifecycle section of the Android Activity documentation at http://developer.android.com/ reference/android/app/Activity.html#ActivityLifecycle. Understanding fragment setup and display Fragment setup and display is a multiphase process involving the fragment's association with an activity, the fragments' creation, and the standard lifecycle events of moving the activity into the running state (also known as the resumed or active state). Understanding the behavior of the lifecycle events and the associated callback methods is essential for using fragments effectively. Once we have an understanding of the lifecycle events and the callback methods, we'll look at just how the event callback methods are used. Chapter 3 [ 43 ] The following diagram shows the sequence of lifecycle-related callback method calls that occur on fragments and activities during setup and display: Activity Fragment onCreate 1 6 onStart 7 onResume 9 8 onStart 10 onResume 2 5 onCreate4 onAttachFragment 3 onAttach onCreateView onActivityCreated Sequence repeat s for each ragmentF As you might expect, in most cases, the first step in the setup and display of a fragment occurs in the activity's onCreate method. In most cases, the activity calls the setContentView method from within the activity's onCreate callback method, which then loads the layout resource and triggers the activity's association with the contained fragments. Notice what happens next. Before the fragment is ever created, the fragment is attached to the activity. The fragment is first notified of the attachment and receives a reference to the activity through the onAttach callback method. The activity is then notified and receives a reference to the fragment through the onAttachFragment callback method. Although attaching the fragment to the activity prior to creating the fragment may seem unexpected, doing so is useful. In many cases, the fragment needs access to the activity during the creation process, because the activity often contains information that the fragment will display or that is otherwise important to the fragment creation process. Fragment Lifecycle and Specialization [ 44 ] With the fragment attached to the activity, the fragment then performs general creation work in the onCreate method and then constructs the contained view hierarchy in the onCreateView method. When an activity contains multiple fragments, Android calls the four methods: Fragment.onAttach, Activity.onAttachFragment, Fragment.onCreate, and Fragment.onCreateView in succession for one fragment before making any calls to these methods for the next fragment. This allows each fragment to complete the process of attachment and creation before the next fragment begins that process. Once the sequence of calling these four methods completes for all the fragments, the remaining setup and display callback methods are called individually in succession for each fragment. After the activity completes execution of its onCreate method, Android then calls each fragment's onActivityCreated method. The onActivityCreated method indicates that all views and fragments created by the activity's layout resource are now fully constructed and can be safely accessed. At this point, the fragment receives the standard lifecycle callbacks on the onStart and onResume methods, just after the activity methods of the same name are each called. Any work performed in the fragment's onStart and onResume methods is very much like the work performed in the corresponding methods within an activity. For many fragments, the only methods in this part of their lifecycle that are overridden are the onCreate and onCreateView methods, as we saw in the examples in the previous chapters. Avoiding method name confusion The activity and fragment classes have a number of commonly named callback methods, and most of these commonly named methods have a common purpose. One important exception is the onCreateView method. The purpose of this method is very different for each class. As mentioned previously, Android calls the Fragment class' onCreateView method to give the fragment an opportunity to create and return the fragment's contained view hierarchy. This method is commonly overridden within a fragment. The method of the same name in the Activity class is called repeatedly by the LayoutInflater class during the process of inflating a layout resource. Most activity implementations do not override this method. Chapter 3 [ 45 ] Understanding fragment hide and teardown Just as fragments behave in a similar way to activities during setup and display, they also behave in a similar way during hide and teardown, as shown in the following diagram: Activity Fragment 1 6 onSaveInstanceState onPause onDestroy 10 2 5 4 onSaveInstanceState3 onPause onStop onStop 7 9 8 onDestroy onDetach onDestroyView Sequence repeats for each ragmentF Initially during hide and teardown, fragments behave just as activities. When the user switches to another activity, each fragment's onPause, onSaveInstanceState, and onStop methods are called. For each method, the fragment implementation is called first, followed by the activity implementation. After the onStop method is called, fragments begin to behave a little differently than activities. Consistent with the separation of fragment creation from fragment view hierarchy creation, fragment view hierarchy destruction is separate from fragment destruction. Following the call to the activity's onStop method, the fragment's onDestroyView method is called indicating that the view hierarchy returned by the fragment's onCreateView method is being destroyed. The fragment's onDestroy method is then called followed by the fragment's onDetach method. At this point, the fragment has no association with an activity and any calls to the getActivity method will return null. For activities containing multiple fragments, Android calls the sequence of the three methods, onDestroyView, onDestroy, and onDetach, for an individual fragment, before beginning the sequence of calling these three methods for the next fragment. This groups the process of destroying and detaching each fragment similar to the way Android groups the process of attaching and creating each fragment. Once this sequence completes for all fragments, Android then calls the activity's onDestroy method. Fragment Lifecycle and Specialization [ 46 ] Maximizing available resources For the most part, lifecycle management for a fragment is very much like that of an activity. There is, however, one important exception: the two-phase nature of fragment creation and destruction. Fragments separate the creation and destruction of the fragment from the fragment's contained view hierarchy. This is because fragments have the ability to exist and be associated with an activity in the absence of the fragment's view hierarchy. There are many scenarios where an activity may contain multiple fragments, but have only a subset of those fragments visible at any point in time. In such a case, the contained fragments can all have their onAttach and onCreate methods called. But the call to each fragment's onCreateView method is delayed until the time comes for the app to make the contents of that fragment visible. Similarly, when the time comes to hide the contents of a fragment, only the fragment's onDestroyView method is called, not the onDestroy and onDetach methods. This behavior comes into play when fragments are dynamically managed within an activity. This behavior allows the overhead of associating a fragment with an activity and initializing the fragment's state to occur only once while being able to easily change the visibility of the fragment's view hierarchy. This is important when we explicitly manage the visibility of fragments using the FragmentTransaction class, and in certain action bar features that manage fragments. We'll talk about these issues in the next two chapters. Managing a fragment state For many fragment implementations, the most important callback method in the lifecycle sequence is the onSaveInstanceState method. Just as with an activity, this callback method provides the fragment with an opportunity to persist any state before the fragment is destroyed, such as when the user moves to another activity or when the user rotates the device to a different orientation. In both of these cases, the activity and contained fragments may be completely torn down and recreated. By persisting the fragment state in the onSaveInstanceState method, that state is latter passed back to the fragment in both the onCreate and onCreateView methods. When managing the state of a fragment, you want to be sure to separate work that is general to the fragment's overall existence from work specific to setting up the view hierarchy. Any expensive initialization work that's general to the fragment's existence such as connecting to a data source, complex calculations, or resource allocations should occur in the onCreate method rather than the onCreateView method. This way, if only the fragment's view hierarchy is destroyed and the fragment remains intact, you avoid unnecessarily repeating expensive initialization work. Chapter 3 [ 47 ] Special purpose fragment classes Now that we understand the lifecycle of fragments, we can look at some of the specialized versions of the Fragment class. As we go through each of these specialized classes, remember they all ultimately inherit from the Fragment class and therefore experience the same lifecycle behavior. Many of these specialized classes have an impact on what operations are safe to perform at the various points in the lifecycle, and some of these classes even add their own lifecycle methods. Understanding each of these classes and their interaction with the fragment lifecycle is essential for using the classes effectively. ListFragment One of the simplest fragment-derived classes to use and yet one of the most helpful is the ListFragment class. The ListFragment class provides a fragment that encapsulates a ListView and, as the name implies, is useful for displaying lists of data. Associating data with the list Unlike the base Fragment class, we're not required to override the onCreateView callback method for the ListFragment class. The ListFragment class provides a standard appearance and only requires that we associate some data. The ListFragment class does all the work of creating the view hierarchy and displaying that data. We associate data with the ListFragment class by calling the ListFragment class' setListAdapter method and passing a reference to an object that implements the ListAdapter interface. Android provides a number of classes that implement this interface such as ArrayAdapter, SimpleAdapter, and SimpleCursorAdapter. The specific class you use will depend on how your source data is stored. If none of the standard Android classes meet your specific requirements, you can create a custom implementation reasonably easy. For a discussion about creating a custom list adapter, see the Android tutorial Displaying the Quick Contact Badge at http:// developer.android.com/training/contacts-provider/ display-contact-badge.html. The call to setListAdapter requires that the view hierarchy for the ListFragment be completely constructed. As a result, we normally do not call the setListAdapter method any earlier than the onActivityCreated callback method. Fragment Lifecycle and Specialization [ 48 ] The ListFragment class wraps an instance of the ListView class, which is accessible through the getListView method. In most scenarios, we can feel free to interact with the contained ListView instance directly and take advantage of any features offered by the ListView class. The one very important exception is when we set the ListAdapter instance. Both the ListFragment and ListView classes expose a setListAdapter method, but we must be sure to use the ListFragment version of the method. The ListFragment class relies on certain initialization behaviors that occur within the ListFragment.setListAdapter method; therefore, the process of calling the setListAdapter method directly on the contained ListView instance bypasses this initialization behavior and may cause the application to become unstable. Separating data from display Up until now, our application has used a fixed layout of several RadioButton views to display the list of books. Using a fixed layout to display such options is not generally a good choice, because any changes to the book list require that we go in and directly modify the fragment layout. In practice, we would prefer to have a layout that is independent of the specific titles. We could write code to dynamically generate the RadioButton views, but there is an easier way. We can instead use the ListFragment class. By switching our application to use the ListFragment class, we can simply store the list of book titles in an array resource and associate the contents of that array resource with the ListFragment instance. In the event of adding more titles or needing to change one of the titles, we simply modify the array resource file. There is no need for us to make any changes to the actual fragment layout. Our application already has all the book titles stored as individual string resources, so we just need to add an array resource for them. We'll add the book titles array to the arrays.xml resource file within the values resource folder where we currently have an array resource defined to hold the list of book descriptions. Within the resources root element of the arrays.xml resource file, add a string- array element that includes a name attribute with a value of bookTitles. Within the string-array element, add an item for each book title that references the string resource for each title. We want to be sure that we list the book title array entries in the same order as the bookDescription array entries because we use the array index as the ID value for each book when we notify the activity of the user's book selection. The array resource entries for the book title and description arrays appear as follows: Chapter 3 [ 49 ] @string/dynamicUiTitle @string/android4NewTitle @string/androidSysDevTitle @string/androidEngineTitle @string/androidDbProgTitle @string/dynamicUiDescription @string/android4NewDescription @string/androidSysDevDescription @string/androidEngineDescription @string/androidDbProgDescription With the titles stored as an array resource, we can now easily create a ListFragment derived class to display the book titles. Creating the ListFragment derived class The first step is to add a new class to our project. To do this, we'll create a new class named BookListFragment2 that extends the ListFragment class as shown in the following code line: class BookListFragment2 extends ListFragment { } Next, we override the onActivityCreated method as follows: public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); String[] bookTitles = getResources().getStringArray(R.array.bookTitles); ArrayAdapter bookTitlesAdapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, bookTitles); setListAdapter(bookTitlesAdapter); } Fragment Lifecycle and Specialization [ 50 ] In the onActivityCreated method, we first call the base class implementation that is required by all classes that extend ListFragment. We then load the bookTitles array resource and associate it with an instance of the ArrayAdapter class named bookTitlesAdapter. The array adapter takes the context as the first parameter, which we get by accessing the activity, and takes the array as the third parameter. The second parameter is the ID of the resource to use to lay out each entry in the list. This resource can be a custom resource or one of the built-in Android resources. In our case, we're using the built-in Android layout resource android.R.layout. simple_list_item_1, which displays a single string value for each row within the ListView. The last step is to call the setListAdapter method and pass the bookTitlesAdapter method. Creating a custom layout resource for the ListFragment class is just like doing so for the ListView class, and is discussed in detail in the Android developer documentation: http://developer.android. com/reference/android/app/ListFragment.html. Handling ListFragment item selection For our application to work correctly, we need to inform the activity each time the user selects one of the titles. Because we use an interface to loosely couple our fragment with the activity, this turns out to be a pretty simple task. We first override the ListFragment class' onListItemClick method. The ListFragment class calls the onListItemClick method when the user selects an entry within the ListFragment instance. The onListItemClick method receives several selection-related parameters including the zero-based position of the selection. Our ListFragment is loaded from an array, so this position value corresponds to the selected title's array index. With the position parameter value corresponding directly to the array index, all we have to do to inform the activity of the user selection is get a reference to the activity, cast it to our OnSelectionChangeListener interface, and call the interface's onSelectedBookChanged method, passing the position parameter value as shown in this code: public void onListItemClick(ListView l, View v, int position, long id) { // Access the Activity and cast to the inteface OnSelectedBookChangeListener listener = (OnSelectedBookChangeListener) Chapter 3 [ 51 ] getActivity(); // Notify the Activity of the selection listener.onSelectedBookChanged(position); } All the activity classes in our application that will use our BookListFragment2 class already implement the OnSelectionChangeListener interface, so there is no change required to the activity classes. Updating the layout resources We now update the activity_main.xml resource file to use the BookListFragment2 class instead of the original BookListFragment class as shown in the following code: We need to make the same change in the activity_main_wide.xml file. Fragment Lifecycle and Specialization [ 52 ] Our program is now fully functional using the ListFragment class and appears as follows: Any changes that we need to make to the titles can now all be made in the resources file and require no changes to the user interface code. DialogFragment Up until now we've been looking at fragments as a new way to divide our application's user interface into subsections of the available display area. Although fragments are new, the concept of having an aspect of our application user interface as a subsection of the available display area is not new. Any time an application displays a dialog, the application is doing exactly that. Historically, the challenge of working with dialogs is that even though they are conceptually just another window within an application, we must handle many of the tasks related to dialogs differently than other aspects of our application user interface. Doing something as simple as handling a button click requires a dialog-specific interface, DialogInterface.OnClickListener, rather than the View.OnClickListener interface that we use when handling a click event from non-dialog related parts of our user interface code. An even more complicated issue is that of orientation changes. Dialogs automatically close in response to an orientation change and therefore can create inconsistent application behavior if a user changes device orientation while a dialog is visible. The DialogFragment class eliminates much of the special handling related to dialogs. With the DialogFragment class, displaying and managing a dialog becomes much more consistent with other aspects of our application user interface. Chapter 3 [ 53 ] Styles When an application displays an instance of the DialogFragment class, the window for the DialogFragment instance has up to three parts to it: layout area, title, and frame. A DialogFragment instance always contains the layout area, but we can control whether it includes the title and frame by setting the DialogFragment class' style using the setStyle method. The DialogFragment class supports four styles with an instance of the DialogFragment class having exactly one style applied. The following table shows the four available styles: Style Has Title Has Frame Accepts Input STYLE_NORMAL Yes Yes Yes STYLE_NO_TITLE No Yes Yes STYLE_NO_FRAME No No Yes STYLE_NO_INPUT No No No Notice that the styles remove features cumulatively. For example, STYLE_NO_TITLE indicates no title whereas STYLE_NO_FRAME indicates no frame and no title. If we do not call the setStyle method, Android creates the DialogFragment instance with the style set to STYLE_NORMAL. The style affects the remainder of the behavior of the DialogFragment class and therefore must be set in the onCreate callback method. An attempt to set the DialogFragment class' style any later in the lifecycle is ignored. If you wish to provide the dialog with a special theme, the theme's resource ID can also be passed to the setStyle method. To allow Android to select an appropriate theme based on the style, simply pass 0 as the theme resource ID. The following code sets the DialogFragment instance to have no title and use the Android-selected theme for that style: class MyDialogFragment extends DialogFragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setStyle(DialogFragment.STYLE_NO_TITLE, 0); } } Fragment Lifecycle and Specialization [ 54 ] Layout Populating the layout of an instance of the DialogFragment class is just like that of a standard fragment derived class. We simply override the onCreateView method and inflate the layout resource. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View theView = inflater.inflate(R.layout.fragment_my_dialog, container, false); return theView; } Creating a layout resource for use with a DialogFragment derived class works exactly as creating a layout resource for any other fragment derived class. To have our DialogFragment instance display a line of text and two buttons, we define the fragment_my_dialog.xml layout resource as shown in the following XML:




需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载





下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!