//
//src\com\example\android\hcgallery\CameraFragment.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 java.io.IOException;
import java.util.List;
import android.app.Fragment;
import android.app.Activity;
import android.app.ActionBar;
import android.content.Context;
import android.content.Intent;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
public class CameraFragment extends Fragment {
private Preview mPreview;
Camera mCamera;
int mNumberOfCameras;
int mCameraCurrentlyLocked;
// The first rear facing camera
int mDefaultCameraId;
@Override
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.
mPreview = new Preview(this.getActivity());
// Find the total number of cameras available
mNumberOfCameras = Camera.getNumberOfCameras();
// Find the ID of the 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;
}
}
setHasOptionsMenu(mNumberOfCameras > 1);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Add an up arrow to the "home" button, indicating that the button will go "up"
// one activity in the app's Activity heirarchy.
// Calls to getActionBar() aren't guaranteed to return the ActionBar when called
// from within the Fragment's onCreate method, because the Window's decor hasn't been
// initialized yet. Either call for the ActionBar reference in Activity.onCreate()
// (after the setContentView(...) call), or in the Fragment's onActivityCreated method.
Activity activity = this.getActivity();
ActionBar actionBar = activity.getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return mPreview;
}
@Override
public void onResume() {
super.onResume();
// Open the default i.e. the first rear facing camera.
mCamera = Camera.open(mDefaultCameraId);
mCameraCurrentlyLocked = mDefaultCameraId;
mPreview.setCamera(mCamera);
}
@Override
public void onPause() {
super.onPause();
// Because the Camera object is a shared resource, it's very
// important to release it when the activity is paused.
if (mCamera != null) {
mPreview.setCamera(null);
mCamera.release();
mCamera = null;
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (mNumberOfCameras > 1) {
// Inflate our menu which can gather user input for switching camera
inflater.inflate(R.menu.camera_menu, menu);
} else {
super.onCreateOptionsMenu(menu, inflater);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.switch_cam:
// Release this camera -> mCameraCurrentlyLocked
if (mCamera != null) {
mCamera.stopPreview();
mPreview.setCamera(null);
mCamera.release();
mCamera = null;
}
// Acquire the next camera and request Preview to reconfigure
// parameters.
mCamera = Camera
.open((mCameraCurrentlyLocked + 1) % mNumberOfCameras);
mCameraCurrentlyLocked = (mCameraCurrentlyLocked + 1)
% mNumberOfCameras;
mPreview.switchCamera(mCamera);
// Start the preview
mCamera.startPreview();
return true;
case android.R.id.home:
Intent intent = new Intent(this.getActivity(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
default:
return super.onOptionsItemSelected(item);
}
}
}
// ----------------------------------------------------------------------
/**
* A simple wrapper around a Camera and a SurfaceView that renders a centered
* preview of the Camera to the surface. We need to center the SurfaceView
* because not all devices have cameras that support preview sizes at the same
* aspect ratio as the device's display.
*/
class Preview extends ViewGroup implements SurfaceHolder.Callback {
private final String TAG = "Preview";
SurfaceView mSurfaceView;
SurfaceHolder mHolder;
Size mPreviewSize;
List mSupportedPreviewSizes;
Camera mCamera;
Preview(Context context) {
super(context);
mSurfaceView = new SurfaceView(context);
addView(mSurfaceView);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(Camera camera) {
mCamera = camera;
if (mCamera != null) {
mSupportedPreviewSizes = mCamera.getParameters()
.getSupportedPreviewSizes();
requestLayout();
}
}
public void switchCamera(Camera camera) {
setCamera(camera);
try {
camera.setPreviewDisplay(mHolder);
} 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
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We purposely disregard child measurements because act as a
// wrapper to a SurfaceView that centers the camera preview instead
// of stretching it.
final int width = resolveSize(getSuggestedMinimumWidth(),
widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(),
heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width,
height);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed && getChildCount() > 0) {
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null) {
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
}
// Center the child SurfaceView within the parent.
if (width * previewHeight > height * previewWidth) {
final int scaledChildWidth = previewWidth * height
/ previewHeight;
child.layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
} else {
final int scaledChildHeight = previewHeight * width
/ previewWidth;
child.layout(0, (height - scaledChildHeight) / 2, width,
(height + scaledChildHeight) / 2);
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where
// to draw.
try {
if (mCamera != null) {
mCamera.setPreviewDisplay(holder);
}
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
if (mCamera != null) {
mCamera.stopPreview();
}
}
private Size getOptimalPreviewSize(List sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null)
return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// Now that the size is known, set up the camera parameters and begin
// the preview.
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
mCamera.setParameters(parameters);
mCamera.startPreview();
}
}
//src\com\example\android\hcgallery\CameraSample.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.Activity;
import android.os.Bundle;
public class CameraSample extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
int themeId = this.getIntent().getExtras().getInt("theme");
this.setTheme(themeId);
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_sample);
}
}
//src\com\example\android\hcgallery\ContentFragment.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.Fragment;
import android.content.ClipData;
import android.content.ClipData.Item;
import android.content.ClipDescription;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.StringTokenizer;
public class ContentFragment extends Fragment {
private View mContentView;
// The bitmap currently used by ImageView
private Bitmap mBitmap = null;
// Current action mode (contextual action bar, a.k.a. CAB)
private ActionMode mCurrentActionMode;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mContentView = inflater.inflate(R.layout.content_welcome, null);
final ImageView imageView = (ImageView) mContentView.findViewById(R.id.image);
mContentView.setDrawingCacheEnabled(false);
mContentView.setOnDragListener(new View.OnDragListener() {
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_ENTERED:
mContentView.setBackgroundColor(
getResources().getColor(R.color.drag_active_color));
break;
case DragEvent.ACTION_DRAG_EXITED:
mContentView.setBackgroundColor(Color.TRANSPARENT);
break;
case DragEvent.ACTION_DRAG_STARTED:
return processDragStarted(event);
case DragEvent.ACTION_DROP:
mContentView.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.
mContentView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (mContentView.getSystemUiVisibility() == View.STATUS_BAR_VISIBLE) {
mContentView.setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
} else {
mContentView.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
}
}
});
// 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) {
return false;
}
mCurrentActionMode = getActivity().startActionMode(
mContentSelectionActionModeCallback);
mContentView.setSelected(true);
return true;
}
});
return mContentView;
}
boolean processDragStarted(DragEvent event) {
// Determine whether to continue processing drag and drop based on the
// plain text mime type.
ClipDescription clipDesc = event.getClipDescription();
if (clipDesc != null) {
return clipDesc.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
}
return false;
}
boolean processDrop(DragEvent event, ImageView imageView) {
// Attempt to parse clip data with expected format: category||entry_id.
// Ignore event if data does not conform to this format.
ClipData data = event.getClipData();
if (data != null) {
if (data.getItemCount() > 0) {
Item item = data.getItemAt(0);
String textData = (String) item.getText();
if (textData != null) {
StringTokenizer tokenizer = new StringTokenizer(textData, "||");
if (tokenizer.countTokens() != 2) {
return false;
}
int category = -1;
int entryId = -1;
try {
category = Integer.parseInt(tokenizer.nextToken());
entryId = Integer.parseInt(tokenizer.nextToken());
} catch (NumberFormatException exception) {
return false;
}
updateContentAndRecycleBitmap(category, entryId);
// Update list fragment with selected entry.
TitlesFragment titlesFrag = (TitlesFragment)
getFragmentManager().findFragmentById(R.id.frag_title);
titlesFrag.selectPosition(entryId);
return true;
}
}
}
return false;
}
void updateContentAndRecycleBitmap(int category, int position) {
if (mCurrentActionMode != null) {
mCurrentActionMode.finish();
}
if (mBitmap != null) {
// This is an advanced call and should be used if you
// are working with a lot of bitmaps. The bitmap is dead
// after this call.
mBitmap.recycle();
}
// Get the bitmap that needs to be drawn and update the ImageView
mBitmap = Directory.getCategory(category).getEntry(position)
.getBitmap(getResources());
((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);
}
void shareCurrentPhoto() {
File externalCacheDir = getActivity().getExternalCacheDir();
if (externalCacheDir == null) {
Toast.makeText(getActivity(), "Error writing to USB/external storage.",
Toast.LENGTH_SHORT).show();
return;
}
// Prevent media scanning of the cache directory.
final File noMediaFile = new File(externalCacheDir, ".nomedia");
try {
noMediaFile.createNewFile();
} catch (IOException e) {
}
// Write the bitmap to temporary storage in the external storage directory (e.g. SD card).
// We perform the actual disk write operations on a separate thread using the
// {@link AsyncTask} class, thus avoiding the possibility of stalling the main (UI) thread.
final File tempFile = new File(externalCacheDir, "tempfile.jpg");
new AsyncTask() {
/**
* Compress and write the bitmap to disk on a separate thread.
* @return TRUE if the write was successful, FALSE otherwise.
*/
protected Boolean doInBackground(Void... voids) {
try {
FileOutputStream fo = new FileOutputStream(tempFile, false);
if (!mBitmap.compress(Bitmap.CompressFormat.JPEG, 60, fo)) {
Toast.makeText(getActivity(), "Error writing bitmap data.",
Toast.LENGTH_SHORT).show();
return Boolean.FALSE;
}
return Boolean.TRUE;
} catch (FileNotFoundException e) {
Toast.makeText(getActivity(), "Error writing to USB/external storage.",
Toast.LENGTH_SHORT).show();
return Boolean.FALSE;
}
}
/**
* 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.
*/
protected void onPostExecute(Boolean result) {
if (result != Boolean.TRUE) {
return;
}
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
shareIntent.setType("image/jpeg");
startActivity(Intent.createChooser(shareIntent, "Share photo"));
}
}.execute();
}
/**
* The callback for the 'photo selected' {@link ActionMode}. In this action mode, we can
* provide contextual actions for the selected photo. We currently only provide the 'share'
* action, but we could also add clipboard functions such as cut/copy/paste here as well.
*/
private ActionMode.Callback mContentSelectionActionModeCallback = new ActionMode.Callback() {
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
actionMode.setTitle(R.string.photo_selection_cab_title);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.photo_context_menu, menu);
return true;
}
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.share:
shareCurrentPhoto();
actionMode.finish();
return true;
}
return false;
}
public void onDestroyActionMode(ActionMode actionMode) {
mContentView.setSelected(false);
mCurrentActionMode = null;
}
};
}
//src\com\example\android\hcgallery\Directory.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;
public class Directory {
private static DirectoryCategory[] mCategories;
public static void initializeDirectory() {
mCategories = new DirectoryCategory[] {
new DirectoryCategory("Balloons", new DirectoryEntry[] {
new DirectoryEntry("Red Balloon", R.drawable.red_balloon),
new DirectoryEntry("Green Balloon", R.drawable.green_balloon),
new DirectoryEntry("Blue Balloon", R.drawable.blue_balloon)}),
new DirectoryCategory("Bikes", new DirectoryEntry[] {
new DirectoryEntry("Old school huffy", R.drawable.blue_bike),
new DirectoryEntry("New Bikes", R.drawable.rainbow_bike),
new DirectoryEntry("Chrome Fast", R.drawable.chrome_wheel)}),
new DirectoryCategory("Androids", new DirectoryEntry[] {
new DirectoryEntry("Steampunk Android", R.drawable.punk_droid),
new DirectoryEntry("Stargazing Android", R.drawable.stargazer_droid),
new DirectoryEntry("Big Android", R.drawable.big_droid) }),
new DirectoryCategory("Pastries", new DirectoryEntry[] {
new DirectoryEntry("Cupcake", R.drawable.cupcake),
new DirectoryEntry("Donut", R.drawable.donut),
new DirectoryEntry("Eclair", R.drawable.eclair),
new DirectoryEntry("Froyo", R.drawable.froyo), }), };
}
public static int getCategoryCount() {
return mCategories.length;
}
public static DirectoryCategory getCategory(int i) {
return mCategories[i];
}
}
//src\com\example\android\hcgallery\DirectoryCategory.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;
public class DirectoryCategory {
private String name;
private DirectoryEntry[] entries;
public DirectoryCategory(String name, DirectoryEntry[] entries) {
this.name = name;
this.entries = entries;
}
public String getName() {
return name;
}
public int getEntryCount() {
return entries.length;
}
public DirectoryEntry getEntry(int i) {
return entries[i];
}
}
//src\com\example\android\hcgallery\DirectoryEntry.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.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
public class DirectoryEntry {
private String name;
private int resID;
public DirectoryEntry(String name, int resID) {
this.name = name;
this.resID = resID;
}
public String getName() {
return name;
}
public Drawable getDrawable(Resources res) {
return res.getDrawable(resID);
}
public Bitmap getBitmap(Resources res) {
return BitmapFactory.decodeResource(res, resID);
}
}
//src\com\example\android\hcgallery\FitCenterFrameLayout.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.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple layout that fits and centers each child view, maintaining aspect ratio.
*/
public class FitCenterFrameLayout extends ViewGroup {
public FitCenterFrameLayout(Context context) {
super(context);
}
public FitCenterFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We purposely disregard child measurements.
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
int childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).measure(childWidthSpec, childHeightSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childCount = getChildCount();
final int parentLeft = getPaddingLeft();
final int parentTop = getPaddingTop();
final int parentRight = r - l - getPaddingRight();
final int parentBottom = b - t - getPaddingBottom();
final int parentWidth = parentRight - parentLeft;
final int parentHeight = parentBottom - parentTop;
int unpaddedWidth, unpaddedHeight, parentUnpaddedWidth, parentUnpaddedHeight;
int childPaddingLeft, childPaddingTop, childPaddingRight, childPaddingBottom;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
// Fit and center the child within the parent. Make sure not to consider padding
// as part of the child's aspect ratio.
childPaddingLeft = child.getPaddingLeft();
childPaddingTop = child.getPaddingTop();
childPaddingRight = child.getPaddingRight();
childPaddingBottom = child.getPaddingBottom();
unpaddedWidth = child.getMeasuredWidth() - childPaddingLeft - childPaddingRight;
unpaddedHeight = child.getMeasuredHeight() - childPaddingTop - childPaddingBottom;
parentUnpaddedWidth = parentWidth - childPaddingLeft - childPaddingRight;
parentUnpaddedHeight = parentHeight - childPaddingTop - childPaddingBottom;
if (parentUnpaddedWidth * unpaddedHeight > parentUnpaddedHeight * unpaddedWidth) {
// The child view should be left/right letterboxed.
final int scaledChildWidth = unpaddedWidth * parentUnpaddedHeight
/ unpaddedHeight + childPaddingLeft + childPaddingRight;
child.layout(
parentLeft + (parentWidth - scaledChildWidth) / 2,
parentTop,
parentRight - (parentWidth - scaledChildWidth) / 2,
parentBottom);
} else {
// The child view should be top/bottom letterboxed.
final int scaledChildHeight = unpaddedHeight * parentUnpaddedWidth
/ unpaddedWidth + childPaddingTop + childPaddingBottom;
child.layout(
parentLeft,
parentTop + (parentHeight - scaledChildHeight) / 2,
parentRight,
parentTop + (parentHeight + scaledChildHeight) / 2);
}
}
}
}
//src\com\example\android\hcgallery\MainActivity.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.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.Menu;
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 {
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 int mThemeId = -1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null && savedInstanceState.getInt("theme", -1) != -1) {
mThemeId = savedInstanceState.getInt("theme");
this.setTheme(mThemeId);
}
setContentView(R.layout.main);
Directory.initializeDirectory();
ActionBar bar = getActionBar();
int i;
for (i = 0; i < Directory.getCategoryCount(); i++) {
bar.addTab(bar.newTab().setText(Directory.getCategory(i).getName())
.setTabListener(this));
}
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);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.camera:
Intent intent = new Intent(this, CameraSample.class);
intent.putExtra("theme", mThemeId);
startActivity(intent);
return true;
case R.id.toggleTitles:
toggleVisibleTitles();
return true;
case R.id.toggleTheme:
if (mThemeId == R.style.AppTheme_Dark) {
mThemeId = R.style.AppTheme_Light;
} else {
mThemeId = R.style.AppTheme_Dark;
}
this.recreate();
return true;
case R.id.showDialog:
showDialog("This is indeed an awesome dialog.");
return true;
case R.id.showStandardNotification:
showNotification(false);
return true;
case R.id.showCustomNotification:
showNotification(true);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void toggleVisibleTitles() {
// Use these for custom animations.
final FragmentManager fm = getFragmentManager();
final TitlesFragment f = (TitlesFragment) fm
.findFragmentById(R.id.frag_title);
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.
final boolean isPortrait = getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_PORTRAIT;
final boolean shouldShow = f.isHidden() || mCurrentTitlesAnimator != null;
// Cancel the current titles animation if there is one.
if (mCurrentTitlesAnimator != null)
mCurrentTitlesAnimator.cancel();
// Begin setting up the object animator. We'll animate the bottom or right edge of the
// titles view, as well as its alpha for a fade effect.
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(
titlesView,
PropertyValuesHolder.ofInt(
isPortrait ? "bottom" : "right",
shouldShow ? getResources().getDimensionPixelSize(R.dimen.titles_size)
: 0),
PropertyValuesHolder.ofFloat("alpha", shouldShow ? 1 : 0)
);
// At each step of the animation, we'll perform layout by calling setLayoutParams.
final ViewGroup.LayoutParams lp = titlesView.getLayoutParams();
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// *** WARNING ***: triggering layout at each animation frame highly impacts
// performance so you should only do this for simple layouts. More complicated
// layouts can be better served with individual animations on child views to
// avoid the performance penalty of layout.
if (isPortrait) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
} else {
lp.width = (Integer) valueAnimator.getAnimatedValue();
}
titlesView.setLayoutParams(lp);
}
});
if (shouldShow) {
fm.beginTransaction().show(f).commit();
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
mCurrentTitlesAnimator = null;
}
});
} else {
objectAnimator.addListener(new AnimatorListenerAdapter() {
boolean canceled;
@Override
public void onAnimationCancel(Animator animation) {
canceled = true;
super.onAnimationCancel(animation);
}
@Override
public void onAnimationEnd(Animator animator) {
if (canceled)
return;
mCurrentTitlesAnimator = null;
fm.beginTransaction().hide(f).commit();
}
});
}
// Start the animation.
objectAnimator.start();
mCurrentTitlesAnimator = objectAnimator;
invalidateOptionsMenu();
// Manually trigger onNewIntent to check for ACTION_DIALOG.
onNewIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
if (ACTION_DIALOG.equals(intent.getAction())) {
showDialog(intent.getStringExtra(Intent.EXTRA_TEXT));
}
}
void showDialog(String text) {
// DialogFragment.show() will take care of adding the fragment
// in a transaction. We also want to remove any currently showing
// dialog, so make our own transaction and take care of that here.
FragmentTransaction ft = getFragmentManager().beginTransaction();
DialogFragment newFragment = MyDialogFragment.newInstance(text);
// Show the dialog.
newFragment.show(ft, "dialog");
}
void showNotification(boolean custom) {
final Resources res = getResources();
final NotificationManager notificationManager = (NotificationManager) getSystemService(
NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_stat_notify_example)
.setAutoCancel(true)
.setTicker(getString(R.string.notification_text))
.setContentIntent(getDialogPendingIntent("Tapped the notification entry."));
if (custom) {
// Sets a custom content view for the notification, including an image button.
RemoteViews layout = new RemoteViews(getPackageName(), R.layout.notification);
layout.setTextViewText(R.id.notification_title, getString(R.string.app_name));
layout.setOnClickPendingIntent(R.id.notification_button,
getDialogPendingIntent("Tapped the 'dialog' button in the notification."));
builder.setContent(layout);
// Notifications in Android 3.0 now have a standard mechanism for displaying large
// bitmaps such as contact avatars. Here, we load an example image and resize it to the
// appropriate size for large bitmaps in notifications.
Bitmap largeIconTemp = BitmapFactory.decodeResource(res,
R.drawable.notification_default_largeicon);
Bitmap largeIcon = Bitmap.createScaledBitmap(
largeIconTemp,
res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height),
false);
largeIconTemp.recycle();
builder.setLargeIcon(largeIcon);
} else {
builder
.setNumber(7) // An example number.
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.notification_text));
}
notificationManager.notify(NOTIFICATION_DEFAULT, builder.getNotification());
}
PendingIntent getDialogPendingIntent(String dialogText) {
return PendingIntent.getActivity(
this,
dialogText.hashCode(), // Otherwise previous PendingIntents with the same
// requestCode may be overwritten.
new Intent(ACTION_DIALOG)
.putExtra(Intent.EXTRA_TEXT, dialogText)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
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);
}
public static class MyDialogFragment extends DialogFragment {
public static MyDialogFragment newInstance(String title) {
MyDialogFragment frag = new MyDialogFragment();
Bundle args = new Bundle();
args.putString("text", title);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String text = getArguments().getString("text");
return new AlertDialog.Builder(getActivity())
.setTitle("A Dialog of Awesome")
.setMessage(text)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}
)
.create();
}
}
}
//src\com\example\android\hcgallery\TitlesFragment.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.Fragment;
import android.app.FragmentManager;
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.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemLongClickListener;
public class TitlesFragment extends ListFragment {
private int mCategory = 0;
private int mCurPosition = 0;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//Current position should survive screen rotations.
if (savedInstanceState != null) {
mCategory = savedInstanceState.getInt("category");
mCurPosition = savedInstanceState.getInt("listPosition");
}
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();
// 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;
}
});
selectPosition(mCurPosition);
}
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);
}
}
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;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
updateImage(position);
}
private void updateImage(int position) {
ContentFragment frag = (ContentFragment) getFragmentManager()
.findFragmentById(R.id.frag_content);
frag.updateContentAndRecycleBitmap(mCategory, position);
mCurPosition = position;
}
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);
}
}
//
//res\layout\action_bar_custom.xml
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent">Hello Action Bar
//res\layout\camera_sample.xml
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/camera_frag"
android:layout_width="match_parent"
android:layout_height="match_parent" />
//res\layout\content_welcome.xml
class="com.example.android.hcgallery.FitCenterFrameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp"
android:clickable="true">
android:background="@drawable/picture_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:duplicateParentState="true"/>
//res\layout\main.xml
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/frags">
android:id="@+id/frag_title"
android:visibility="gone"
android:layout_marginTop="?android:attr/actionBarSize"
android:layout_width="@dimen/titles_size"
android:layout_height="match_parent" />
android:id="@+id/frag_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
//res\layout\notification.xml
android:id="@+id/notificationbg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
android:paddingLeft="16dip"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:orientation="vertical">
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
android:focusable="true"
android:ellipsize="marquee"
android:singleLine="true"
android:layout_gravity="left"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_gravity="left"
android:maxLines="2"
android:scrollHorizontally="true"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/notification_text" />
android:id="@+id/notification_button"
android:src="@drawable/btn_notification_ic_example"
android:background="@null"
android:layout_weight="0"
android:layout_width="48dip"
android:layout_height="match_parent" />
//res\layout\title_list_item.xml
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:gravity="bottom"
android:textAppearance="?android:attr/textAppearanceMedium"
android:background="?android:attr/activatedBackgroundIndicator" >
//
//res\layout-port\main.xml
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/frags">
android:id="@+id/frag_title"
android:visibility="gone"
android:layout_marginTop="?android:attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="@dimen/titles_size"/>
android:id="@+id/frag_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
//
//res\menu\camera_menu.xml
//res\menu\main_menu.xml
//res\menu\photo_context_menu.xml
//
//res\values\attrs.xml
//res\values\colors.xml
#ccffffff
#cc000000
#80cccccc
//res\values\dimens.xml
300dp
//res\values\strings.xml
Honeycomb Gallery
Camera Example
Clip Label
Honeycomb Example Widget
Touch to show data
Example notification text
Photo selection
//res\values\styles.xml
//
//res\values-port\dimens.xml
200dp