diff --git a/samples/HoneycombGallery/AndroidManifest.xml b/samples/HoneycombGallery/AndroidManifest.xml index 6e6163bf0..704a1c6a6 100644 --- a/samples/HoneycombGallery/AndroidManifest.xml +++ b/samples/HoneycombGallery/AndroidManifest.xml @@ -15,10 +15,16 @@ limitations under the License. --> + package="com.example.android.hcgallery" android:versionCode="2" + android:versionName="1.2"> - + + + + + + @@ -27,8 +33,7 @@ android:icon="@drawable/icon" android:logo="@drawable/logo" android:theme="@style/AppTheme.Light" - android:hardwareAccelerated="true" - android:debuggable="true"> + android:hardwareAccelerated="true"> - - + + + + diff --git a/samples/HoneycombGallery/res/layout-port/main.xml b/samples/HoneycombGallery/res/layout-large-port/main.xml similarity index 82% rename from samples/HoneycombGallery/res/layout-port/main.xml rename to samples/HoneycombGallery/res/layout-large-port/main.xml index a5e99707b..206c6ce82 100644 --- a/samples/HoneycombGallery/res/layout-port/main.xml +++ b/samples/HoneycombGallery/res/layout-large-port/main.xml @@ -21,14 +21,12 @@ android:id="@+id/frags"> + android:layout_height="@dimen/titles_size" /> diff --git a/samples/HoneycombGallery/res/layout-large/main.xml b/samples/HoneycombGallery/res/layout-large/main.xml new file mode 100644 index 000000000..8322e32d4 --- /dev/null +++ b/samples/HoneycombGallery/res/layout-large/main.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/samples/HoneycombGallery/res/layout/camera_sample.xml b/samples/HoneycombGallery/res/layout/camera_sample.xml index dcb88679c..9986f057b 100644 --- a/samples/HoneycombGallery/res/layout/camera_sample.xml +++ b/samples/HoneycombGallery/res/layout/camera_sample.xml @@ -15,10 +15,7 @@ limitations under the License. --> - - - - Hello Action Bar - + + diff --git a/samples/HoneycombGallery/res/layout/main.xml b/samples/HoneycombGallery/res/layout/main.xml index 4987920cf..a72442947 100644 --- a/samples/HoneycombGallery/res/layout/main.xml +++ b/samples/HoneycombGallery/res/layout/main.xml @@ -14,22 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - + - - + android:layout_height="match_parent"/> + diff --git a/samples/HoneycombGallery/res/layout/notification.xml b/samples/HoneycombGallery/res/layout/notification.xml index 3da757ddf..caae92887 100644 --- a/samples/HoneycombGallery/res/layout/notification.xml +++ b/samples/HoneycombGallery/res/layout/notification.xml @@ -21,8 +21,8 @@ android:orientation="horizontal"> diff --git a/samples/HoneycombGallery/res/menu/camera_menu.xml b/samples/HoneycombGallery/res/menu/camera_menu.xml index 55184dd8a..c961d3d8b 100644 --- a/samples/HoneycombGallery/res/menu/camera_menu.xml +++ b/samples/HoneycombGallery/res/menu/camera_menu.xml @@ -16,5 +16,7 @@ --> - + diff --git a/samples/HoneycombGallery/res/menu/main_menu.xml b/samples/HoneycombGallery/res/menu/main_menu.xml index 0e27cb539..3210aa71a 100644 --- a/samples/HoneycombGallery/res/menu/main_menu.xml +++ b/samples/HoneycombGallery/res/menu/main_menu.xml @@ -16,25 +16,20 @@ --> - - - - - - - + + + + + + diff --git a/samples/HoneycombGallery/res/menu/photo_context_menu.xml b/samples/HoneycombGallery/res/menu/photo_context_menu.xml index efe26c927..0039bc3ce 100644 --- a/samples/HoneycombGallery/res/menu/photo_context_menu.xml +++ b/samples/HoneycombGallery/res/menu/photo_context_menu.xml @@ -16,8 +16,8 @@ --> - + diff --git a/samples/HoneycombGallery/res/values/strings.xml b/samples/HoneycombGallery/res/values/strings.xml index 5c3b35526..65f7945db 100644 --- a/samples/HoneycombGallery/res/values/strings.xml +++ b/samples/HoneycombGallery/res/values/strings.xml @@ -25,4 +25,14 @@ Example notification text Photo selection - + + + Share + Camera + Toggle Titles + Day/Night + Show a dialog + Show a basic notification + Show a custom notification + Switch camera + \ No newline at end of file diff --git a/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraSample.java b/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraActivity.java similarity index 90% rename from samples/HoneycombGallery/src/com/example/android/hcgallery/CameraSample.java rename to samples/HoneycombGallery/src/com/example/android/hcgallery/CameraActivity.java index 22438585d..d87676cd6 100644 --- a/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraSample.java +++ b/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraActivity.java @@ -19,7 +19,8 @@ package com.example.android.hcgallery; import android.app.Activity; import android.os.Bundle; -public class CameraSample extends Activity { +/** Basic shell activity that hosts CameraFragment. */ +public class CameraActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraFragment.java b/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraFragment.java index 19c4b4735..3290f116d 100644 --- a/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraFragment.java +++ b/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraFragment.java @@ -16,12 +16,9 @@ package com.example.android.hcgallery; -import java.io.IOException; -import java.util.List; - -import android.app.Fragment; -import android.app.Activity; import android.app.ActionBar; +import android.app.Activity; +import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.hardware.Camera; @@ -38,12 +35,16 @@ import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; +import java.io.IOException; +import java.util.List; + public class CameraFragment extends Fragment { private Preview mPreview; Camera mCamera; int mNumberOfCameras; - int mCameraCurrentlyLocked; + int mCurrentCamera; // Camera ID currently chosen + int mCameraCurrentlyLocked; // Camera ID that's actually acquired // The first rear facing camera int mDefaultCameraId; @@ -52,20 +53,18 @@ public class CameraFragment extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - // Create a RelativeLayout container that will hold a SurfaceView, - // and set it as the content of our activity. + // Create a container that will hold a SurfaceView for camera previews mPreview = new Preview(this.getActivity()); // Find the total number of cameras available mNumberOfCameras = Camera.getNumberOfCameras(); - // Find the ID of the default camera + // Find the ID of the rear-facing ("default") camera CameraInfo cameraInfo = new CameraInfo(); for (int i = 0; i < mNumberOfCameras; i++) { Camera.getCameraInfo(i, cameraInfo); if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) { - mDefaultCameraId = i; + mCurrentCamera = mDefaultCameraId = i; } } setHasOptionsMenu(mNumberOfCameras > 1); @@ -95,9 +94,10 @@ public class CameraFragment extends Fragment { public void onResume() { super.onResume(); - // Open the default i.e. the first rear facing camera. - mCamera = Camera.open(mDefaultCameraId); - mCameraCurrentlyLocked = mDefaultCameraId; + // Use mCurrentCamera to select the camera desired to safely restore + // the fragment after the camera has been changed + mCamera = Camera.open(mCurrentCamera); + mCameraCurrentlyLocked = mCurrentCamera; mPreview.setCamera(mCamera); } @@ -128,7 +128,7 @@ public class CameraFragment extends Fragment { public boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { - case R.id.switch_cam: + case R.id.menu_switch_cam: // Release this camera -> mCameraCurrentlyLocked if (mCamera != null) { mCamera.stopPreview(); @@ -139,10 +139,9 @@ public class CameraFragment extends Fragment { // Acquire the next camera and request Preview to reconfigure // parameters. - mCamera = Camera - .open((mCameraCurrentlyLocked + 1) % mNumberOfCameras); - mCameraCurrentlyLocked = (mCameraCurrentlyLocked + 1) - % mNumberOfCameras; + mCurrentCamera = (mCameraCurrentlyLocked + 1) % mNumberOfCameras; + mCamera = Camera.open(mCurrentCamera); + mCameraCurrentlyLocked = mCurrentCamera; mPreview.switchCamera(mCamera); // Start the preview @@ -152,6 +151,7 @@ public class CameraFragment extends Fragment { Intent intent = new Intent(this.getActivity(), MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(intent); + return true; default: return super.onOptionsItemSelected(item); @@ -175,6 +175,7 @@ class Preview extends ViewGroup implements SurfaceHolder.Callback { Size mPreviewSize; List mSupportedPreviewSizes; Camera mCamera; + boolean mSurfaceCreated = false; Preview(Context context) { super(context); @@ -194,7 +195,7 @@ class Preview extends ViewGroup implements SurfaceHolder.Callback { if (mCamera != null) { mSupportedPreviewSizes = mCamera.getParameters() .getSupportedPreviewSizes(); - requestLayout(); + if (mSurfaceCreated) requestLayout(); } } @@ -205,11 +206,6 @@ class Preview extends ViewGroup implements SurfaceHolder.Callback { } catch (IOException exception) { Log.e(TAG, "IOException caused by setPreviewDisplay()", exception); } - Camera.Parameters parameters = camera.getParameters(); - parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height); - requestLayout(); - - camera.setParameters(parameters); } @Override @@ -227,11 +223,18 @@ class Preview extends ViewGroup implements SurfaceHolder.Callback { mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height); } + + if (mCamera != null) { + Camera.Parameters parameters = mCamera.getParameters(); + parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height); + + mCamera.setParameters(parameters); + } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - if (changed && getChildCount() > 0) { + if (getChildCount() > 0) { final View child = getChildAt(0); final int width = r - l; @@ -269,6 +272,8 @@ class Preview extends ViewGroup implements SurfaceHolder.Callback { } catch (IOException exception) { Log.e(TAG, "IOException caused by setPreviewDisplay()", exception); } + if (mPreviewSize == null) requestLayout(); + mSurfaceCreated = true; } public void surfaceDestroyed(SurfaceHolder holder) { diff --git a/samples/HoneycombGallery/src/com/example/android/hcgallery/ContentActivity.java b/samples/HoneycombGallery/src/com/example/android/hcgallery/ContentActivity.java new file mode 100644 index 000000000..f8e93d4d0 --- /dev/null +++ b/samples/HoneycombGallery/src/com/example/android/hcgallery/ContentActivity.java @@ -0,0 +1,48 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package com.example.android.hcgallery; + +import android.app.Activity; +import android.os.Bundle; + +/** This is a shell activity that hosts ContentFragment when the device screen + * is smaller than "large". + */ +public class ContentActivity extends Activity { + private int mThemeId = 0; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle extras = getIntent().getExtras(); + if (extras != null) { + // The activity theme is the only state data that the activity needs + // to restore. All info about the content displayed is managed by the fragment + mThemeId = extras.getInt("theme"); + } else if (savedInstanceState != null) { + // If there's no restore state, get the theme from the intent + mThemeId = savedInstanceState.getInt("theme"); + } + + if (mThemeId != 0) { + setTheme(mThemeId); + } + + setContentView(R.layout.content_activity); + + if (extras != null) { + // Take the info from the intent and deliver it to the fragment so it can update + int category = extras.getInt("category"); + int position = extras.getInt("position"); + ContentFragment frag = (ContentFragment) getFragmentManager().findFragmentById(R.id.content_frag); + frag.updateContentAndRecycleBitmap(category, position); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("theme", mThemeId); + } +} diff --git a/samples/HoneycombGallery/src/com/example/android/hcgallery/ContentFragment.java b/samples/HoneycombGallery/src/com/example/android/hcgallery/ContentFragment.java index 8c3323b96..b6754f9f5 100644 --- a/samples/HoneycombGallery/src/com/example/android/hcgallery/ContentFragment.java +++ b/samples/HoneycombGallery/src/com/example/android/hcgallery/ContentFragment.java @@ -17,7 +17,6 @@ package com.example.android.hcgallery; import android.app.ActionBar; -import android.app.Activity; import android.app.Fragment; import android.content.ClipData; import android.content.ClipData.Item; @@ -37,6 +36,8 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; import android.widget.ImageView; import android.widget.Toast; @@ -46,8 +47,16 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.StringTokenizer; +/** Fragment that shows the content selected from the TitlesFragment. + * When running on a screen size smaller than "large", this fragment is hosted in + * ContentActivity. Otherwise, it appears side by side with the TitlesFragment + * in MainActivity. */ public class ContentFragment extends Fragment { private View mContentView; + private int mCategory = 0; + private int mCurPosition = 0; + private boolean mSystemUiVisible = true; + private boolean mSoloFragment = false; // The bitmap currently used by ImageView private Bitmap mBitmap = null; @@ -55,11 +64,9 @@ public class ContentFragment extends Fragment { // Current action mode (contextual action bar, a.k.a. CAB) private ActionMode mCurrentActionMode; - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - } - + /** This is where we initialize the fragment's UI and attach some + * event listeners to UI components. + */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -67,65 +74,48 @@ public class ContentFragment extends Fragment { final ImageView imageView = (ImageView) mContentView.findViewById(R.id.image); mContentView.setDrawingCacheEnabled(false); + // Handle drag events when a list item is dragged into the view mContentView.setOnDragListener(new View.OnDragListener() { - public boolean onDrag(View v, DragEvent event) { + public boolean onDrag(View view, DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_ENTERED: - mContentView.setBackgroundColor( + view.setBackgroundColor( getResources().getColor(R.color.drag_active_color)); break; case DragEvent.ACTION_DRAG_EXITED: - mContentView.setBackgroundColor(Color.TRANSPARENT); + view.setBackgroundColor(Color.TRANSPARENT); break; case DragEvent.ACTION_DRAG_STARTED: return processDragStarted(event); case DragEvent.ACTION_DROP: - mContentView.setBackgroundColor(Color.TRANSPARENT); + view.setBackgroundColor(Color.TRANSPARENT); return processDrop(event, imageView); } return false; } }); - // Keep the action bar visibility in sync with the system status bar. That is, when entering - // 'lights out mode,' hide the action bar, and when exiting this mode, show the action bar. - - final Activity activity = getActivity(); - mContentView.setOnSystemUiVisibilityChangeListener( - new View.OnSystemUiVisibilityChangeListener() { - public void onSystemUiVisibilityChange(int visibility) { - ActionBar actionBar = activity.getActionBar(); - if (actionBar != null) { - mContentView.setSystemUiVisibility(visibility); - if (visibility == View.STATUS_BAR_VISIBLE) { - actionBar.show(); - } else { - actionBar.hide(); - } - } - } - }); - - // Show/hide the system status bar when single-clicking a photo. This is also called - // 'lights out mode.' Activating and deactivating this mode also invokes the listener - // defined above, which will show or hide the action bar accordingly. - + // Show/hide the system status bar when single-clicking a photo. mContentView.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - if (mContentView.getSystemUiVisibility() == View.STATUS_BAR_VISIBLE) { - mContentView.setSystemUiVisibility(View.STATUS_BAR_HIDDEN); + public void onClick(View view) { + if (mCurrentActionMode != null) { + // If we're in an action mode, don't toggle the action bar + return; + } + + if (mSystemUiVisible) { + setSystemUiVisible(false); } else { - mContentView.setSystemUiVisibility(View.STATUS_BAR_VISIBLE); + setSystemUiVisible(true); } } }); // When long-pressing a photo, activate the action mode for selection, showing the // contextual action bar (CAB). - mContentView.setOnLongClickListener(new View.OnLongClickListener() { public boolean onLongClick(View view) { if (mCurrentActionMode != null) { @@ -134,7 +124,7 @@ public class ContentFragment extends Fragment { mCurrentActionMode = getActivity().startActionMode( mContentSelectionActionModeCallback); - mContentView.setSelected(true); + view.setSelected(true); return true; } }); @@ -142,6 +132,99 @@ public class ContentFragment extends Fragment { return mContentView; } + /** This is where we perform additional setup for the fragment that's either + * not related to the fragment's layout or must be done after the layout is drawn. + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Set member variable for whether this fragment is the only one in the activity + Fragment listFragment = getFragmentManager().findFragmentById(R.id.titles_frag); + mSoloFragment = listFragment == null ? true : false; + + if (mSoloFragment) { + // The fragment is alone, so enable up navigation + getActivity().getActionBar().setDisplayHomeAsUpEnabled(true); + // Must call in order to get callback to onOptionsItemSelected() + setHasOptionsMenu(true); + } + + // Current position and UI visibility should survive screen rotations. + if (savedInstanceState != null) { + setSystemUiVisible(savedInstanceState.getBoolean("systemUiVisible")); + if (mSoloFragment) { + // Restoring these members is not necessary when this fragment + // is combined with the TitlesFragment, because when the TitlesFragment + // is restored, it selects the appropriate item and sends the event + // to the updateContentAndRecycleBitmap() method itself + mCategory = savedInstanceState.getInt("category"); + mCurPosition = savedInstanceState.getInt("listPosition"); + updateContentAndRecycleBitmap(mCategory, mCurPosition); + } + } + + if (mSoloFragment) { + String title = Directory.getCategory(mCategory).getEntry(mCurPosition).getName(); + ActionBar bar = getActivity().getActionBar(); + bar.setTitle(title); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // This callback is used only when mSoloFragment == true (see onActivityCreated above) + switch (item.getItemId()) { + case android.R.id.home: + // App icon in Action Bar clicked; go up + Intent intent = new Intent(getActivity(), MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // Reuse the existing instance + startActivity(intent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onSaveInstanceState (Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("listPosition", mCurPosition); + outState.putInt("category", mCategory); + outState.putBoolean("systemUiVisible", mSystemUiVisible); + } + + /** Toggle whether the system UI (status bar / system bar) is visible. + * This also toggles the action bar visibility. + * @param show True to show the system UI, false to hide it. + */ + void setSystemUiVisible(boolean show) { + mSystemUiVisible = show; + + Window window = getActivity().getWindow(); + WindowManager.LayoutParams winParams = window.getAttributes(); + View view = getView(); + ActionBar actionBar = getActivity().getActionBar(); + + if (show) { + // Show status bar (remove fullscreen flag) + window.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN); + // Show system bar + view.setSystemUiVisibility(View.STATUS_BAR_VISIBLE); + // Show action bar + actionBar.show(); + } else { + // Add fullscreen flag (hide status bar) + window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + // Hide system bar + view.setSystemUiVisibility(View.STATUS_BAR_HIDDEN); + // Hide action bar + actionBar.hide(); + } + window.setAttributes(winParams); + } + boolean processDragStarted(DragEvent event) { // Determine whether to continue processing drag and drop based on the // plain text mime type. @@ -176,7 +259,7 @@ public class ContentFragment extends Fragment { updateContentAndRecycleBitmap(category, entryId); // Update list fragment with selected entry. TitlesFragment titlesFrag = (TitlesFragment) - getFragmentManager().findFragmentById(R.id.frag_title); + getFragmentManager().findFragmentById(R.id.titles_frag); titlesFrag.selectPosition(entryId); return true; } @@ -185,7 +268,15 @@ public class ContentFragment extends Fragment { return false; } + /** + * Sets the current image visible. + * @param category Index position of the image category + * @param position Index position of the image + */ void updateContentAndRecycleBitmap(int category, int position) { + mCategory = category; + mCurPosition = position; + if (mCurrentActionMode != null) { mCurrentActionMode.finish(); } @@ -203,6 +294,9 @@ public class ContentFragment extends Fragment { ((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap); } + /** Share the currently selected photo using an AsyncTask to compress the image + * and then invoke the appropriate share intent. + */ void shareCurrentPhoto() { File externalCacheDir = getActivity().getExternalCacheDir(); if (externalCacheDir == null) { @@ -229,6 +323,7 @@ public class ContentFragment extends Fragment { * Compress and write the bitmap to disk on a separate thread. * @return TRUE if the write was successful, FALSE otherwise. */ + @Override protected Boolean doInBackground(Void... voids) { try { FileOutputStream fo = new FileOutputStream(tempFile, false); @@ -250,6 +345,7 @@ public class ContentFragment extends Fragment { * After doInBackground completes (either successfully or in failure), we invoke an * intent to share the photo. This code is run on the main (UI) thread. */ + @Override protected void onPostExecute(Boolean result) { if (result != Boolean.TRUE) { return; @@ -283,7 +379,7 @@ public class ContentFragment extends Fragment { public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { switch (menuItem.getItemId()) { - case R.id.share: + case R.id.menu_share: shareCurrentPhoto(); actionMode.finish(); return true; diff --git a/samples/HoneycombGallery/src/com/example/android/hcgallery/MainActivity.java b/samples/HoneycombGallery/src/com/example/android/hcgallery/MainActivity.java index 3cfaf6d15..06905b857 100644 --- a/samples/HoneycombGallery/src/com/example/android/hcgallery/MainActivity.java +++ b/samples/HoneycombGallery/src/com/example/android/hcgallery/MainActivity.java @@ -33,6 +33,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; @@ -43,92 +44,86 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.Window; import android.widget.RemoteViews; -public class MainActivity extends Activity implements ActionBar.TabListener { +/** This is the main "launcher" activity. + * When running on a "large" or larger screen, this activity displays both the + * TitlesFragments and the Content Fragment. When on a smaller screen size, this + * activity displays only the TitlesFragment. In which case, selecting a list + * item opens the ContentActivity, holds only the ContentFragment. */ +public class MainActivity extends Activity implements TitlesFragment.OnItemSelectedListener { - private static final int NOTIFICATION_DEFAULT = 1; - private static final String ACTION_DIALOG = "com.example.android.hcgallery.action.DIALOG"; - - private View mActionBarView; private Animator mCurrentTitlesAnimator; private String[] mToggleLabels = {"Show Titles", "Hide Titles"}; - private int mLabelIndex = 1; + private static final int NOTIFICATION_DEFAULT = 1; + private static final String ACTION_DIALOG = "com.example.android.hcgallery.action.DIALOG"; private int mThemeId = -1; + private boolean mDualFragments = false; + private boolean mTitlesHidden = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if(savedInstanceState != null && savedInstanceState.getInt("theme", -1) != -1) { - mThemeId = savedInstanceState.getInt("theme"); - this.setTheme(mThemeId); + if(savedInstanceState != null) { + if (savedInstanceState.getInt("theme", -1) != -1) { + mThemeId = savedInstanceState.getInt("theme"); + this.setTheme(mThemeId); + } + mTitlesHidden = savedInstanceState.getBoolean("titlesHidden"); } setContentView(R.layout.main); - Directory.initializeDirectory(); - ActionBar bar = getActionBar(); + bar.setDisplayShowTitleEnabled(false); - int i; - for (i = 0; i < Directory.getCategoryCount(); i++) { - bar.addTab(bar.newTab().setText(Directory.getCategory(i).getName()) - .setTabListener(this)); + ContentFragment frag = (ContentFragment) getFragmentManager() + .findFragmentById(R.id.content_frag); + if (frag != null) mDualFragments = true; + + if (mTitlesHidden) { + getFragmentManager().beginTransaction() + .hide(getFragmentManager().findFragmentById(R.id.titles_frag)).commit(); } - - mActionBarView = getLayoutInflater().inflate( - R.layout.action_bar_custom, null); - - bar.setCustomView(mActionBarView); - bar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_USE_LOGO); - bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - bar.setDisplayShowHomeEnabled(true); - - // If category is not saved to the savedInstanceState, - // 0 is returned by default. - if(savedInstanceState != null) { - int category = savedInstanceState.getInt("category"); - bar.selectTab(bar.getTabAt(category)); - } - } - - public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { - TitlesFragment titleFrag = (TitlesFragment) getFragmentManager() - .findFragmentById(R.id.frag_title); - titleFrag.populateTitles(tab.getPosition()); - - titleFrag.selectPosition(0); - } - - public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { - } - - public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_menu, menu); + // If the device doesn't support camera, remove the camera menu item + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) { + menu.removeItem(R.id.menu_camera); + } return true; } + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + // If not showing both fragments, remove the "toggle titles" menu item + if (!mDualFragments) { + menu.removeItem(R.id.menu_toggleTitles); + } else { + menu.findItem(R.id.menu_toggleTitles).setTitle(mToggleLabels[mTitlesHidden ? 0 : 1]); + } + return super.onPrepareOptionsMenu(menu); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.camera: - Intent intent = new Intent(this, CameraSample.class); + case R.id.menu_camera: + Intent intent = new Intent(this, CameraActivity.class); intent.putExtra("theme", mThemeId); startActivity(intent); return true; - case R.id.toggleTitles: + case R.id.menu_toggleTitles: toggleVisibleTitles(); return true; - case R.id.toggleTheme: + case R.id.menu_toggleTheme: if (mThemeId == R.style.AppTheme_Dark) { mThemeId = R.style.AppTheme_Light; } else { @@ -137,15 +132,15 @@ public class MainActivity extends Activity implements ActionBar.TabListener { this.recreate(); return true; - case R.id.showDialog: + case R.id.menu_showDialog: showDialog("This is indeed an awesome dialog."); return true; - case R.id.showStandardNotification: + case R.id.menu_showStandardNotification: showNotification(false); return true; - case R.id.showCustomNotification: + case R.id.menu_showCustomNotification: showNotification(true); return true; @@ -154,13 +149,13 @@ public class MainActivity extends Activity implements ActionBar.TabListener { } } + /** Respond to the "toogle titles" item in the action bar */ public void toggleVisibleTitles() { // Use these for custom animations. final FragmentManager fm = getFragmentManager(); final TitlesFragment f = (TitlesFragment) fm - .findFragmentById(R.id.frag_title); + .findFragmentById(R.id.titles_frag); final View titlesView = f.getView(); - mLabelIndex = 1 - mLabelIndex; // Determine if we're in portrait, and whether we're showing or hiding the titles // with this toggle. @@ -207,6 +202,8 @@ public class MainActivity extends Activity implements ActionBar.TabListener { @Override public void onAnimationEnd(Animator animator) { mCurrentTitlesAnimator = null; + mTitlesHidden = false; + invalidateOptionsMenu(); } }); @@ -226,6 +223,8 @@ public class MainActivity extends Activity implements ActionBar.TabListener { return; mCurrentTitlesAnimator = null; fm.beginTransaction().hide(f).commit(); + mTitlesHidden = true; + invalidateOptionsMenu(); } }); } @@ -234,8 +233,6 @@ public class MainActivity extends Activity implements ActionBar.TabListener { objectAnimator.start(); mCurrentTitlesAnimator = objectAnimator; - invalidateOptionsMenu(); - // Manually trigger onNewIntent to check for ACTION_DIALOG. onNewIntent(getIntent()); } @@ -313,22 +310,36 @@ public class MainActivity extends Activity implements ActionBar.TabListener { 0); } - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.getItem(1).setTitle(mToggleLabels[mLabelIndex]); - return true; - } - @Override public void onSaveInstanceState (Bundle outState) { super.onSaveInstanceState(outState); - ActionBar bar = getActionBar(); - int category = bar.getSelectedTab().getPosition(); - outState.putInt("category", category); outState.putInt("theme", mThemeId); + outState.putBoolean("titlesHidden", mTitlesHidden); + } + + /** Implementation for TitlesFragment.OnItemSelectedListener. + * When the TitlesFragment receives an onclick event for a list item, + * it's passed back to this activity through this method so that we can + * deliver it to the ContentFragment in the manner appropriate */ + public void onItemSelected(int category, int position) { + if (!mDualFragments) { + // If showing only the TitlesFragment, start the ContentActivity and + // pass it the info about the selected item + Intent intent = new Intent(this, ContentActivity.class); + intent.putExtra("category", category); + intent.putExtra("position", position); + intent.putExtra("theme", mThemeId); + startActivity(intent); + } else { + // If showing both fragments, directly update the ContentFragment + ContentFragment frag = (ContentFragment) getFragmentManager() + .findFragmentById(R.id.content_frag); + frag.updateContentAndRecycleBitmap(category, position); + } } + /** Dialog implementation that shows a simple dialog as a fragment */ public static class MyDialogFragment extends DialogFragment { public static MyDialogFragment newInstance(String title) { diff --git a/samples/HoneycombGallery/src/com/example/android/hcgallery/TitlesFragment.java b/samples/HoneycombGallery/src/com/example/android/hcgallery/TitlesFragment.java index 85a7b68ea..27266796c 100644 --- a/samples/HoneycombGallery/src/com/example/android/hcgallery/TitlesFragment.java +++ b/samples/HoneycombGallery/src/com/example/android/hcgallery/TitlesFragment.java @@ -16,62 +16,172 @@ package com.example.android.hcgallery; -import android.app.Fragment; -import android.app.FragmentManager; +import android.app.ActionBar; +import android.app.Activity; +import android.app.FragmentTransaction; import android.app.ListFragment; import android.content.ClipData; -import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.Paint; -import android.graphics.Typeface; import android.os.Bundle; -import android.text.TextPaint; -import android.util.TypedValue; import android.view.View; +import android.view.ViewTreeObserver; import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; -import android.widget.ImageView; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; import android.widget.ListView; import android.widget.TextView; -import android.widget.AdapterView.OnItemLongClickListener; -public class TitlesFragment extends ListFragment { +/** + * 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.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - lv.setCacheColorHint(Color.TRANSPARENT); - lv.setOnItemLongClickListener(new OnItemLongClickListener() { - public boolean onItemLongClick(AdapterView av, View v, int pos, long id) { - final String title = (String) ((TextView) v).getText(); + lv.setCacheColorHint(Color.TRANSPARENT); // Improves scrolling performance - // 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 (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(); - selectPosition(mCurPosition); + // 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(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; @@ -93,38 +203,53 @@ public class TitlesFragment extends ListFragment { } } - 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(); - setListAdapter(new ArrayAdapter(getActivity(), - R.layout.title_list_item, items)); - mCategory = category; + // 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); + } } - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - updateImage(position); + /* These must be implemented, but we don't use them */ + + public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { } - private void updateImage(int position) { - ContentFragment frag = (ContentFragment) getFragmentManager() - .findFragmentById(R.id.frag_content); - frag.updateContentAndRecycleBitmap(mCategory, position); - mCurPosition = position; + public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { } - public void selectPosition(int position) { - ListView lv = getListView(); - lv.setItemChecked(position, true); - updateImage(position); - } - - @Override - public void onSaveInstanceState (Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt("listPosition", mCurPosition); - outState.putInt("category", mCategory); - } }