1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.hcgallery;
18 
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.FragmentTransaction;
22 import android.app.ListFragment;
23 import android.content.ClipData;
24 import android.content.res.TypedArray;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.drawable.Drawable;
28 import android.os.Bundle;
29 import android.view.View;
30 import android.view.ViewTreeObserver;
31 import android.widget.AdapterView;
32 import android.widget.AdapterView.OnItemLongClickListener;
33 import android.widget.ArrayAdapter;
34 import android.widget.FrameLayout;
35 import android.widget.FrameLayout.LayoutParams;
36 import android.widget.ListView;
37 import android.widget.TextView;
38 
39 /**
40  * Fragment that shows the list of images
41  * As an extension of ListFragment, this fragment uses a default layout
42  * that includes a single ListView, which you can acquire with getListView()
43  * When running on a screen size smaller than "large", this fragment appears alone
44  * in MainActivity. In this case, selecting a list item opens the ContentActivity,
45  * which likewise holds only the ContentFragment.
46  */
47 public class TitlesFragment extends ListFragment implements ActionBar.TabListener {
48     OnItemSelectedListener mListener;
49     private int mCategory = 0;
50     private int mCurPosition = 0;
51     private boolean mDualFragments = false;
52 
53     /** Container Activity must implement this interface and we ensure
54      * that it does during the onAttach() callback
55      */
56     public interface OnItemSelectedListener {
onItemSelected(int category, int position)57         public void onItemSelected(int category, int position);
58     }
59 
60     @Override
onAttach(Activity activity)61     public void onAttach(Activity activity) {
62         super.onAttach(activity);
63         // Check that the container activity has implemented the callback interface
64         try {
65             mListener = (OnItemSelectedListener) activity;
66         } catch (ClassCastException e) {
67             throw new ClassCastException(activity.toString()
68                     + " must implement OnItemSelectedListener");
69         }
70     }
71 
72     /** This is where we perform setup for the fragment that's either
73      * not related to the fragment's layout or must be done after the layout is drawn.
74      * Notice that this fragment does not implement onCreateView(), because it extends
75      * ListFragment, which includes a ListView as the root view by default, so there's
76      * no need to set up the layout.
77      */
78     @Override
onActivityCreated(Bundle savedInstanceState)79     public void onActivityCreated(Bundle savedInstanceState) {
80         super.onActivityCreated(savedInstanceState);
81 
82         ContentFragment frag = (ContentFragment) getFragmentManager()
83                 .findFragmentById(R.id.content_frag);
84         if (frag != null) mDualFragments = true;
85 
86         ActionBar bar = getActivity().getActionBar();
87         bar.setDisplayHomeAsUpEnabled(false);
88         bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
89 
90         // Must call in order to get callback to onCreateOptionsMenu()
91         setHasOptionsMenu(true);
92 
93         Directory.initializeDirectory();
94         for (int i = 0; i < Directory.getCategoryCount(); i++) {
95             bar.addTab(bar.newTab().setText(Directory.getCategory(i).getName())
96                     .setTabListener(this));
97         }
98 
99         //Current position should survive screen rotations.
100         if (savedInstanceState != null) {
101             mCategory = savedInstanceState.getInt("category");
102             mCurPosition = savedInstanceState.getInt("listPosition");
103             bar.selectTab(bar.getTabAt(mCategory));
104         }
105 
106         populateTitles(mCategory);
107         ListView lv = getListView();
108         lv.setCacheColorHint(Color.TRANSPARENT); // Improves scrolling performance
109 
110         if (mDualFragments) {
111             // Highlight the currently selected item
112             lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
113             // Enable drag and dropping
114             lv.setOnItemLongClickListener(new OnItemLongClickListener() {
115                 public boolean onItemLongClick(AdapterView<?> av, View v, int pos, long id) {
116                     final String title = (String) ((TextView) v).getText();
117 
118                     // Set up clip data with the category||entry_id format.
119                     final String textData = String.format("%d||%d", mCategory, pos);
120                     ClipData data = ClipData.newPlainText(title, textData);
121                     v.startDrag(data, new MyDragShadowBuilder(v), null, 0);
122                     return true;
123                 }
124             });
125         }
126 
127         // If showing both fragments, select the appropriate list item by default
128         if (mDualFragments) selectPosition(mCurPosition);
129 
130         // Attach a GlobalLayoutListener so that we get a callback when the layout
131         // has finished drawing. This is necessary so that we can apply top-margin
132         // to the ListView in order to dodge the ActionBar. Ordinarily, that's not
133         // necessary, but we've set the ActionBar to "overlay" mode using our theme,
134         // so the layout does not account for the action bar position on its own.
135         ViewTreeObserver observer = getListView().getViewTreeObserver();
136         observer.addOnGlobalLayoutListener(layoutListener);
137     }
138 
139     @Override
onDestroyView()140     public void onDestroyView() {
141       super.onDestroyView();
142       // Always detach ViewTreeObserver listeners when the view tears down
143       getListView().getViewTreeObserver().removeGlobalOnLayoutListener(layoutListener);
144     }
145 
146     /** Attaches an adapter to the fragment's ListView to populate it with items */
populateTitles(int category)147     public void populateTitles(int category) {
148         DirectoryCategory cat = Directory.getCategory(category);
149         String[] items = new String[cat.getEntryCount()];
150         for (int i = 0; i < cat.getEntryCount(); i++)
151             items[i] = cat.getEntry(i).getName();
152         // Convenience method to attach an adapter to ListFragment's ListView
153         setListAdapter(new ArrayAdapter<String>(getActivity(),
154                 R.layout.title_list_item, items));
155         mCategory = category;
156     }
157 
158     @Override
onListItemClick(ListView l, View v, int position, long id)159     public void onListItemClick(ListView l, View v, int position, long id) {
160         // Send the event to the host activity via OnItemSelectedListener callback
161         mListener.onItemSelected(mCategory, position);
162         mCurPosition = position;
163     }
164 
165     /** Called to select an item from the listview */
selectPosition(int position)166     public void selectPosition(int position) {
167         // Only if we're showing both fragments should the item be "highlighted"
168         if (mDualFragments) {
169             ListView lv = getListView();
170             lv.setItemChecked(position, true);
171         }
172         // Calls the parent activity's implementation of the OnItemSelectedListener
173         // so the activity can pass the event to the sibling fragment as appropriate
174         mListener.onItemSelected(mCategory, position);
175     }
176 
177     @Override
onSaveInstanceState(Bundle outState)178     public void onSaveInstanceState (Bundle outState) {
179         super.onSaveInstanceState(outState);
180         outState.putInt("listPosition", mCurPosition);
181         outState.putInt("category", mCategory);
182     }
183 
184     /** This defines how the draggable list items appear during a drag event */
185     private class MyDragShadowBuilder extends View.DragShadowBuilder {
186         private Drawable mShadow;
187 
MyDragShadowBuilder(View v)188         public MyDragShadowBuilder(View v) {
189             super(v);
190 
191             final TypedArray a = v.getContext().obtainStyledAttributes(R.styleable.AppTheme);
192             mShadow = a.getDrawable(R.styleable.AppTheme_listDragShadowBackground);
193             mShadow.setCallback(v);
194             mShadow.setBounds(0, 0, v.getWidth(), v.getHeight());
195             a.recycle();
196         }
197 
198         @Override
onDrawShadow(Canvas canvas)199         public void onDrawShadow(Canvas canvas) {
200             super.onDrawShadow(canvas);
201             mShadow.draw(canvas);
202             getView().draw(canvas);
203         }
204     }
205 
206     // Because the fragment doesn't have a reliable callback to notify us when
207     // the activity's layout is completely drawn, this OnGlobalLayoutListener provides
208     // the necessary callback so we can add top-margin to the ListView in order to dodge
209     // the ActionBar. Which is necessary because the ActionBar is in overlay mode, meaning
210     // that it will ordinarily sit on top of the activity layout as a top layer and
211     // the ActionBar height can vary. Specifically, when on a small/normal size screen,
212     // the action bar tabs appear in a second row, making the action bar twice as tall.
213     ViewTreeObserver.OnGlobalLayoutListener layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
214         @Override
215         public void onGlobalLayout() {
216             int barHeight = getActivity().getActionBar().getHeight();
217             ListView listView = getListView();
218             FrameLayout.LayoutParams params = (LayoutParams) listView.getLayoutParams();
219             // The list view top-margin should always match the action bar height
220             if (params.topMargin != barHeight) {
221                 params.topMargin = barHeight;
222                 listView.setLayoutParams(params);
223             }
224             // The action bar doesn't update its height when hidden, so make top-margin zero
225             if (!getActivity().getActionBar().isShowing()) {
226               params.topMargin = 0;
227               listView.setLayoutParams(params);
228             }
229         }
230     };
231 
232 
233     /* The following are callbacks implemented for the ActionBar.TabListener,
234      * which this fragment implements to handle events when tabs are selected.
235      */
236 
onTabSelected(ActionBar.Tab tab, FragmentTransaction ft)237     public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
238         TitlesFragment titleFrag = (TitlesFragment) getFragmentManager()
239                 .findFragmentById(R.id.titles_frag);
240         titleFrag.populateTitles(tab.getPosition());
241 
242         if (mDualFragments) {
243             titleFrag.selectPosition(0);
244         }
245     }
246 
247     /* These must be implemented, but we don't use them */
248 
onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)249     public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
250     }
251 
onTabReselected(ActionBar.Tab tab, FragmentTransaction ft)252     public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
253     }
254 
255 }
256