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
17package com.example.android.hcgallery;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.app.FragmentTransaction;
22import android.app.ListFragment;
23import android.content.ClipData;
24import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.drawable.Drawable;
28import android.os.Bundle;
29import android.view.View;
30import android.view.ViewTreeObserver;
31import android.widget.AdapterView;
32import android.widget.AdapterView.OnItemLongClickListener;
33import android.widget.ArrayAdapter;
34import android.widget.FrameLayout;
35import android.widget.FrameLayout.LayoutParams;
36import android.widget.ListView;
37import 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 */
47public 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