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 { 57 public void onItemSelected(int category, int position); 58 } 59 60 @Override 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 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 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 */ 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 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 */ 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 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 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 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 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 249 public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { 250 } 251 252 public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { 253 } 254 255 } 256