Xem mẫu

76 CHAPTER 3 User interfaces return super.onMenuItemSelected(featureId, item); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { RestaurantFinderApplication application = (RestaurantFinderApplication) getApplication(); application.setCurrentReview(this.reviews.get(position)); Intent intent = new Intent(Constants.INTENT_ACTION_VIEW_DETAIL); intent.putExtra(Constants.STARTFROM_EXTRA, getIntent().getIntExtra( Constants.STARTFROM_EXTRA, 1)); startActivity(intent); Pass startFrom Get Application } object and set private void loadReviews(String location, String cuisine, state int startFrom) { Create loadReviews final ReviewFetcher rf = new ReviewFetcher(location, method cuisine, “ALL”, startFrom, ReviewList.NUM_RESULTS_PER_PAGE); Instantiate this.progressDialog = ReviewFetcher ProgressDialog.show(this, " Working...", " Retrieving reviews", true, false); I Show ProgressDialog new Thread() { public void run() { reviews = rf.getReviews(); } handler.sendEmptyMessage(0); 1) Update handler }.start(); } } This Activity has a menu item that allows the user to get the next page of results or change the list criteria. To support this we have to implement the onMenuItemSe-lected method B. If the MENU_GET_NEXT_PAGE menu item is selected, we then define a new intent to reload the screen with an incremented startFrom value (and we use the getExtras() and putExtras() intent methods to do this) C. After the menu-related methods, we see a special onListItemClick() method D. This method is used to respond when one of the list items in a ListView is clicked. Here we use the position of the clicked item to reference the particular Review item the user chose, and we set this into the Application for later usage in the Review-DetailActivity (which we will begin to implement in section 3.3) E. After we have the data set, we then call the next Activity (including the startFrom extra) . Lastly in the ReviewList class we have the loadReviews() method, which, strangely enough, loads reviews G. This method is significant for several reasons. First it sets up the ReviewFetcher class instance, which will be used to call out to the Google Base API over the network and return a List of Review objects H (again, net-working details are in chapter 6). Then it invokes the ProgressDialog.show() method to show the user we are retrieving data I. Finally it sets up a new ThreadJ, within which the ReviewFetcher is used, and the earlier Handler we saw in the first half of ReviewList is sent an empty message 1). If you refer back to when the Handler Download at Boykma.Com Licensed to Deborah Christiansen Working with views 77 was established, in listing 3.3, you can see that is where, when the message is received, we dismiss the ProgressDialog, populate the Adapter our ListView is using, and call setListAdapter() to update the UI. The setListAdapter()method will iterate the Adapter it is handed and display a returned View for every item. With the Activity created and set up and the Handler being used to update the Adapter with data, we now have a second screen in our application. The next thing we need to do is fill in some of the gaps surrounding working with handlers and different threads. These concepts are not view-specific but are worth a small detour at this point because you will want to use these classes when trying to perform tasks related to retrieving and manipulating data needed for the UI. 3.2.3 Multitasking with Handler and Message The Handler is the Swiss army knife of messaging and scheduling operations for Android. This class allows you to queue tasks to be run on different threads and allows you schedule tasks using Message and Runnable objects. The Android platform monitors the responsiveness of applications and kills those that are considered nonresponsive. An Application Not Responding (ANR) event is de-fined as no response to a user input for five seconds. (A user touches the screen, or press-es a key, or the like, and your application must respond). So does this mean your code always has to complete within five seconds? No, of course not, but the main UI thread does have to respond within that time frame. To keep the main UI thread snappy, any long-running tasks, such as retrieving data over the network or getting a large amount of data from a database or complicated calcula- tions, should be performed in a separate thread. Getting tasks into a separate thread, then getting results back to the main UI thread is where the Handle, and related classes, come into play. When a Handler is created, it is associ-ated with a Looper A Looper is a class that con-tains a MessageQueue and processes Message or Runnable objects that are sent via the Handler In the Handler usage, shown in listings 3.3 and 3.4, we created a Handler with a no-argu-ment constructor. With this approach, the Han-dler is automatically associated with the Looper of the current running thread, typically the main UI thread. The main UI thread, which is created by the process of the running application, is an instance of a HandlerThread, which is basically an Android Thread specialization that provides a Loope. The key parts involved in this arrange- ment are depicted in the diagram in figure 3.5. MainUIThread (HandlerThread) Handler myHandler = new Handler() { public void handleMessage (Message m) { updateUIHere(); } }; new Thread() { public void run() { doStuff(); Message m = myHandler.obtainMessage(); Bundle b = new Bundle(); b.putString("key", "value"); m.setData(b); myHandler.sendMessage(m); } }.start(); Looper MessageQueue Figure 3.5 Usage of the Handler class with separate threads, and the relationship of HandlerThread, Looper, and MessageQueue Download at Boykma.Com Licensed to Deborah Christiansen 78 CHAPTER 3 User interfaces When implementing a Handler you will have to provide a handleMessage(Message m) method. This method is the hook that lets you pass messages. When you create a new Thread, you can then call one of several sendMessage methods on Handler from within that thread’s run method, as our examples and diagram demonstrate. Calling sendMessage puts your message on the MessageQueue, which the Looper maintains. Along with sending messages into handlers, you can also send Runnable objects directly, and you can schedule things to be run at different times in the future. You send messages and post runnables. Each of these concepts supports methods such as sendEmptyMessage(int what), which we have already used, and the counterparts sendEmptyMessageAtTime(intwhat,longtime) and sendEmptyMessageDelayed(int what,long delay). Once it is in the queue, your message is processed as soon as pos-sible (unless you schedule or delay it using the respective send or post method). You will see more of Handler and Message in other examples throughout the book, and we will cover more detail in some instances, but the main point to remember when you see these classes is that they are used to communicate between threads and for scheduling. Getting back to our RestaurantFinder application and more directly view-oriented topics, we next need to elaborate on the ReviewAdapter our RestaurantFinder ReviewList screen now uses, after it is populated with data from a Message. This adapter returns a custom View object for each data element it processes. 3.2.4 Creating custom views Though you can often get away with simply using the views that are provided with Android, there may also be situations, like the one we are now facing, where you need a custom view to display your own object in a unique way. In the ReviewList screen we used an Adapter of type ReviewAdapter to back our ListVie. This is a custom Adapter that contains a custom View object, ReviewList-View A ReviewListView is what our ReviewListActivity displays for every row of data it contains. The Adapter and View are shown in listing 3.5. Listing 3.5 The ReviewAdapter and inner ReviewListView classes public class ReviewAdapter extends BaseAdapter { B Extend BaseAdapter private final Context context; Include Context and private final List reviews; List public ReviewAdapter(Context context, List reviews) { this.context = context; this.reviews = reviews; } Override basic public int getCount() { Adapter methods return this.reviews.size(); } @Override E Override Adapter public Object getItem(int position) { return this.reviews.get(position); } Download at Boykma.Com Licensed to Deborah Christiansen Working with views 79 @Override public long getItemId(int position) { return position; } Override basic Adapter methods Override Adapter getView E @Override public View getView(int position, View convertView, ViewGroup parent) { Review review = this.reviews.get(position); return new ReviewListView(this.context, review.name, review.rating); } private final class ReviewListView extends LinearLayout { private TextView name; private TextView rating; Define custom inner View class public ReviewListView(Context context, String name, String rating) { super(context); setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); Set layout in code params.setMargins(5, 3, 5, 0); this.name = new TextView(context); this.name.setText(name); this.name.setTextSize(16f); this.name.setTextColor(Color.WHITE); this.addView(this.name, params); this.rating = new TextView(context); this.rating.setText(rating); this.rating.setTextSize(16f); this.rating.setTextColor(Color.GRAY); this.addView(this.rating, params); } } } Instantiate TextView members I Add TextView to tree The first thing to note in ReviewAdapter is that it extends BaseAdapterB. Base-Adapter is an Adapter implementation that provides basic event-handling support. Adapter itself is an interface in the android.Widget package that provides a way to bind data to a View with some common methods. This is often used with collections of data, such as we saw with Spinner and ArrayAdapter in listing 3.1. Another common usage is with a CursorAdapte, which returns results from a database (something we will see in chapter 5). Here we are creating our own Adapte, because we want it to return a custom View Our ReviewAdapter class accepts two parameters in the constructor and sets those values to two simple member objects: Context and ListC. Then this class goes on to implement the straightforward required Adapter interface methods that re-turn a count, an item, and an ID (we just use the position in the collection as the ID) D. The next Adapter method we have to implement is the important one, getView(). This is where the Adapter will return any View we create for a particular item in the collection of data it is supporting. Within this method we get a particular Review object based on the position/ID, and then we create an instance of a custom ReviewListView object to return as the ViewE. Download at Boykma.Com Licensed to Deborah Christiansen 80 CHAPTER 3 User interfaces ReviewListView itself, which extends LinearLayout (something you will learn more about in section 3.2.4), is an inner class inside ReviewAdapter (since we will never use it outside of returning a view from ReviewAdapter) . Within it we see an example of setting layout and View details in code, rather than in XML. Here we set the orientation, parameters, and margin for our layout G. Then we populate the sim-ple TextView objects that will be children of our new View and represent data H. Once these are set up via code, we add them to the parent container (in this case the parent is our custom class ReviewListView) I. This is where the data binding hap-pens—the bridge to the View from data. Another important thing to note about this is that we have created not only a custom View but a composite one as well. That is, we are using simple existing View objects in a particular layout to construct a new type of reusable Vie, which shows the detail of a selected Review object on screen, as shown in figure 3.2. Our ReviewListView object, while custom, is admittedly (and intentionally) fairly simple. In many cases you will be able to create custom views by combining existing views in this manner. Nevertheless, you should also be aware that you can go deeper and extend the View class itself. Then you can implement core methods as needed. Using this approach you have access to the lifecycle methods of a View (not an Activ-ity as we have already covered, but an individual View). These include onMeasure(), onLayout(), onDraw(), onVisibilityChanged(), and others. Though we don’t need that level of control here, you should be aware that extending View gives you a great deal of power to create custom components. Now that you have seen how we get the data for our reviews and what the Adapter and custom View we are using look like, the next thing we need to do is take a closer look at a few more aspects of views, including layout. 3.2.5 Understanding layout One of the most significant aspects of creating your UI and designing your screens is understanding layout. In Android, screen layout is defined in terms of ViewGroup and LayoutParams objects. ViewGroup is a View that contains other views (has children) and also defines and provides access to the layout. On every screen all the views are placed in a hierarchical tree, so every element has children, and somewhere at the root is a ViewGroup. All the views on the screen sup-port a host of attributes that pertain to background color, color, and so on. We touched on many of these attributes in section 3.2.2 when we discussed the methods on the View class. Dimensions—width and height—and other properties such as rela-tive or absolute placement and margins are based on the LayoutParams a view requests and what the parent—based on its type, its own dimensions, and the dimen-sions of all of its children—can accommodate. The main ViewGroup classes are shown in the class diagram you saw in figure 3.4. The diagram in figure 3.6 expands on this class structure to show the specific Layout-Params inner classes of the view groups and layout properties each type provides. Download at Boykma.Com Licensed to Deborah Christiansen ... - --nqh--