- moves tablet-style layouts to layout-large* directories - adds ContentActivity to host the ContentFragment when on phones - adds an OnItemSelectedListener interface to TitlesFragment, which MainActivity implements in order to receive callbacks on click events to the list item and then pass the selected item to the ContentFragment in the manner appropriate for the current configuration... Specifically, when in two-pane mode, it updates the ContentFragment directly, and when in single-pane mode, it starts the ContentActivity with intent data about the selected item, which then updates the ContentFragment - Change CameraSample.java to CameraActivity.java for name conventions - Moves all menu strings into string resources - Fixes camera sample to properly handle front-facing camera on other devices (was broken on nexus s and on g-slate) - Fixes camera sample to handle resume state after the camera has changed (for example, when switched to a different camera, it would crash on resume) - Moves various code around between classes as appropriate for the fragment handling the action. For example, move the ActionBar.TabListener implementation to the TitlesFragment (was originally impemented by the MainActivity) - Adds logic to support devices without camera and properly declare the camera in manifest as such - Maintains the state of hidden titles list across restarts Change-Id: I27a39a68dee37325c0c3607aa0b56ab6c134d026
256 lines
10 KiB
Java
256 lines
10 KiB
Java
/*
|
|
* Copyright (C) 2011 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.example.android.hcgallery;
|
|
|
|
import android.app.ActionBar;
|
|
import android.app.Activity;
|
|
import android.app.FragmentTransaction;
|
|
import android.app.ListFragment;
|
|
import android.content.ClipData;
|
|
import android.content.res.TypedArray;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Bundle;
|
|
import android.view.View;
|
|
import android.view.ViewTreeObserver;
|
|
import android.widget.AdapterView;
|
|
import android.widget.AdapterView.OnItemLongClickListener;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.FrameLayout.LayoutParams;
|
|
import android.widget.ListView;
|
|
import android.widget.TextView;
|
|
|
|
/**
|
|
* Fragment that shows the list of images
|
|
* As an extension of ListFragment, this fragment uses a default layout
|
|
* that includes a single ListView, which you can acquire with getListView()
|
|
* When running on a screen size smaller than "large", this fragment appears alone
|
|
* in MainActivity. In this case, selecting a list item opens the ContentActivity,
|
|
* which likewise holds only the ContentFragment.
|
|
*/
|
|
public class TitlesFragment extends ListFragment implements ActionBar.TabListener {
|
|
OnItemSelectedListener mListener;
|
|
private int mCategory = 0;
|
|
private int mCurPosition = 0;
|
|
private boolean mDualFragments = false;
|
|
|
|
/** Container Activity must implement this interface and we ensure
|
|
* that it does during the onAttach() callback
|
|
*/
|
|
public interface OnItemSelectedListener {
|
|
public void onItemSelected(int category, int position);
|
|
}
|
|
|
|
@Override
|
|
public void onAttach(Activity activity) {
|
|
super.onAttach(activity);
|
|
// Check that the container activity has implemented the callback interface
|
|
try {
|
|
mListener = (OnItemSelectedListener) activity;
|
|
} catch (ClassCastException e) {
|
|
throw new ClassCastException(activity.toString()
|
|
+ " must implement OnItemSelectedListener");
|
|
}
|
|
}
|
|
|
|
/** This is where we perform setup for the fragment that's either
|
|
* not related to the fragment's layout or must be done after the layout is drawn.
|
|
* Notice that this fragment does not implement onCreateView(), because it extends
|
|
* ListFragment, which includes a ListView as the root view by default, so there's
|
|
* no need to set up the layout.
|
|
*/
|
|
@Override
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
|
super.onActivityCreated(savedInstanceState);
|
|
|
|
ContentFragment frag = (ContentFragment) getFragmentManager()
|
|
.findFragmentById(R.id.content_frag);
|
|
if (frag != null) mDualFragments = true;
|
|
|
|
ActionBar bar = getActivity().getActionBar();
|
|
bar.setDisplayHomeAsUpEnabled(false);
|
|
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
|
|
|
// Must call in order to get callback to onCreateOptionsMenu()
|
|
setHasOptionsMenu(true);
|
|
|
|
Directory.initializeDirectory();
|
|
for (int i = 0; i < Directory.getCategoryCount(); i++) {
|
|
bar.addTab(bar.newTab().setText(Directory.getCategory(i).getName())
|
|
.setTabListener(this));
|
|
}
|
|
|
|
//Current position should survive screen rotations.
|
|
if (savedInstanceState != null) {
|
|
mCategory = savedInstanceState.getInt("category");
|
|
mCurPosition = savedInstanceState.getInt("listPosition");
|
|
bar.selectTab(bar.getTabAt(mCategory));
|
|
}
|
|
|
|
populateTitles(mCategory);
|
|
ListView lv = getListView();
|
|
lv.setCacheColorHint(Color.TRANSPARENT); // Improves scrolling performance
|
|
|
|
if (mDualFragments) {
|
|
// Highlight the currently selected item
|
|
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
|
// Enable drag and dropping
|
|
lv.setOnItemLongClickListener(new OnItemLongClickListener() {
|
|
public boolean onItemLongClick(AdapterView<?> av, View v, int pos, long id) {
|
|
final String title = (String) ((TextView) v).getText();
|
|
|
|
// Set up clip data with the category||entry_id format.
|
|
final String textData = String.format("%d||%d", mCategory, pos);
|
|
ClipData data = ClipData.newPlainText(title, textData);
|
|
v.startDrag(data, new MyDragShadowBuilder(v), null, 0);
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// If showing both fragments, select the appropriate list item by default
|
|
if (mDualFragments) selectPosition(mCurPosition);
|
|
|
|
// Attach a GlobalLayoutListener so that we get a callback when the layout
|
|
// has finished drawing. This is necessary so that we can apply top-margin
|
|
// to the ListView in order to dodge the ActionBar. Ordinarily, that's not
|
|
// necessary, but we've set the ActionBar to "overlay" mode using our theme,
|
|
// so the layout does not account for the action bar position on its own.
|
|
ViewTreeObserver observer = getListView().getViewTreeObserver();
|
|
observer.addOnGlobalLayoutListener(layoutListener);
|
|
}
|
|
|
|
@Override
|
|
public void onDestroyView() {
|
|
super.onDestroyView();
|
|
// Always detach ViewTreeObserver listeners when the view tears down
|
|
getListView().getViewTreeObserver().removeGlobalOnLayoutListener(layoutListener);
|
|
}
|
|
|
|
/** Attaches an adapter to the fragment's ListView to populate it with items */
|
|
public void populateTitles(int category) {
|
|
DirectoryCategory cat = Directory.getCategory(category);
|
|
String[] items = new String[cat.getEntryCount()];
|
|
for (int i = 0; i < cat.getEntryCount(); i++)
|
|
items[i] = cat.getEntry(i).getName();
|
|
// Convenience method to attach an adapter to ListFragment's ListView
|
|
setListAdapter(new ArrayAdapter<String>(getActivity(),
|
|
R.layout.title_list_item, items));
|
|
mCategory = category;
|
|
}
|
|
|
|
@Override
|
|
public void onListItemClick(ListView l, View v, int position, long id) {
|
|
// Send the event to the host activity via OnItemSelectedListener callback
|
|
mListener.onItemSelected(mCategory, position);
|
|
mCurPosition = position;
|
|
}
|
|
|
|
/** Called to select an item from the listview */
|
|
public void selectPosition(int position) {
|
|
// Only if we're showing both fragments should the item be "highlighted"
|
|
if (mDualFragments) {
|
|
ListView lv = getListView();
|
|
lv.setItemChecked(position, true);
|
|
}
|
|
// Calls the parent activity's implementation of the OnItemSelectedListener
|
|
// so the activity can pass the event to the sibling fragment as appropriate
|
|
mListener.onItemSelected(mCategory, position);
|
|
}
|
|
|
|
@Override
|
|
public void onSaveInstanceState (Bundle outState) {
|
|
super.onSaveInstanceState(outState);
|
|
outState.putInt("listPosition", mCurPosition);
|
|
outState.putInt("category", mCategory);
|
|
}
|
|
|
|
/** This defines how the draggable list items appear during a drag event */
|
|
private class MyDragShadowBuilder extends View.DragShadowBuilder {
|
|
private Drawable mShadow;
|
|
|
|
public MyDragShadowBuilder(View v) {
|
|
super(v);
|
|
|
|
final TypedArray a = v.getContext().obtainStyledAttributes(R.styleable.AppTheme);
|
|
mShadow = a.getDrawable(R.styleable.AppTheme_listDragShadowBackground);
|
|
mShadow.setCallback(v);
|
|
mShadow.setBounds(0, 0, v.getWidth(), v.getHeight());
|
|
a.recycle();
|
|
}
|
|
|
|
@Override
|
|
public void onDrawShadow(Canvas canvas) {
|
|
super.onDrawShadow(canvas);
|
|
mShadow.draw(canvas);
|
|
getView().draw(canvas);
|
|
}
|
|
}
|
|
|
|
// Because the fragment doesn't have a reliable callback to notify us when
|
|
// the activity's layout is completely drawn, this OnGlobalLayoutListener provides
|
|
// the necessary callback so we can add top-margin to the ListView in order to dodge
|
|
// the ActionBar. Which is necessary because the ActionBar is in overlay mode, meaning
|
|
// that it will ordinarily sit on top of the activity layout as a top layer and
|
|
// the ActionBar height can vary. Specifically, when on a small/normal size screen,
|
|
// the action bar tabs appear in a second row, making the action bar twice as tall.
|
|
ViewTreeObserver.OnGlobalLayoutListener layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
|
|
@Override
|
|
public void onGlobalLayout() {
|
|
int barHeight = getActivity().getActionBar().getHeight();
|
|
ListView listView = getListView();
|
|
FrameLayout.LayoutParams params = (LayoutParams) listView.getLayoutParams();
|
|
// The list view top-margin should always match the action bar height
|
|
if (params.topMargin != barHeight) {
|
|
params.topMargin = barHeight;
|
|
listView.setLayoutParams(params);
|
|
}
|
|
// The action bar doesn't update its height when hidden, so make top-margin zero
|
|
if (!getActivity().getActionBar().isShowing()) {
|
|
params.topMargin = 0;
|
|
listView.setLayoutParams(params);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/* The following are callbacks implemented for the ActionBar.TabListener,
|
|
* which this fragment implements to handle events when tabs are selected.
|
|
*/
|
|
|
|
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
|
|
TitlesFragment titleFrag = (TitlesFragment) getFragmentManager()
|
|
.findFragmentById(R.id.titles_frag);
|
|
titleFrag.populateTitles(tab.getPosition());
|
|
|
|
if (mDualFragments) {
|
|
titleFrag.selectPosition(0);
|
|
}
|
|
}
|
|
|
|
/* These must be implemented, but we don't use them */
|
|
|
|
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
|
|
}
|
|
|
|
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
|
|
}
|
|
|
|
}
|