diff options
author | Doris Liu <tianliu@google.com> | 2013-11-20 00:24:46 -0800 |
---|---|---|
committer | Doris Liu <tianliu@google.com> | 2013-11-23 23:33:06 -0800 |
commit | f55f3c461c5a6ae6b61fa75562ca01683aa93f9a (patch) | |
tree | f1d68caddb8a49d5e57ae138760efc9dd25e96fd | |
parent | c4e665625b88a8363fa2bd9848bf88ec9b45637f (diff) | |
download | android_packages_apps_Camera2-f55f3c461c5a6ae6b61fa75562ca01683aa93f9a.tar.gz android_packages_apps_Camera2-f55f3c461c5a6ae6b61fa75562ca01683aa93f9a.tar.bz2 android_packages_apps_Camera2-f55f3c461c5a6ae6b61fa75562ca01683aa93f9a.zip |
Pinhole animation, quick switch between photo and video
Also, first pass of the view hierarchy refactor.
Change-Id: I6c80191f15908bd24c16b76df6ef92df3ef905fc
53 files changed, 1060 insertions, 160 deletions
diff --git a/res/drawable-hdpi/ic_camera_normal.png b/res/drawable-hdpi/ic_camera_normal.png Binary files differnew file mode 100644 index 000000000..ecafe13e1 --- /dev/null +++ b/res/drawable-hdpi/ic_camera_normal.png diff --git a/res/drawable-hdpi/ic_craft_normal.png b/res/drawable-hdpi/ic_craft_normal.png Binary files differnew file mode 100644 index 000000000..86571e46d --- /dev/null +++ b/res/drawable-hdpi/ic_craft_normal.png diff --git a/res/drawable-hdpi/ic_photo_sphere_normal.png b/res/drawable-hdpi/ic_photo_sphere_normal.png Binary files differnew file mode 100644 index 000000000..64cff4233 --- /dev/null +++ b/res/drawable-hdpi/ic_photo_sphere_normal.png diff --git a/res/drawable-hdpi/ic_settings_normal.png b/res/drawable-hdpi/ic_settings_normal.png Binary files differnew file mode 100644 index 000000000..51c559c62 --- /dev/null +++ b/res/drawable-hdpi/ic_settings_normal.png diff --git a/res/drawable-hdpi/ic_timelapse_normal.png b/res/drawable-hdpi/ic_timelapse_normal.png Binary files differnew file mode 100644 index 000000000..6303033dd --- /dev/null +++ b/res/drawable-hdpi/ic_timelapse_normal.png diff --git a/res/drawable-hdpi/ic_video_normal.png b/res/drawable-hdpi/ic_video_normal.png Binary files differnew file mode 100644 index 000000000..90d9adf66 --- /dev/null +++ b/res/drawable-hdpi/ic_video_normal.png diff --git a/res/drawable-hdpi/ic_wide_angle_normal.png b/res/drawable-hdpi/ic_wide_angle_normal.png Binary files differnew file mode 100644 index 000000000..7c66ba4c7 --- /dev/null +++ b/res/drawable-hdpi/ic_wide_angle_normal.png diff --git a/res/drawable-mdpi/ic_camera_normal.png b/res/drawable-mdpi/ic_camera_normal.png Binary files differnew file mode 100644 index 000000000..c6034c97c --- /dev/null +++ b/res/drawable-mdpi/ic_camera_normal.png diff --git a/res/drawable-mdpi/ic_craft_normal.png b/res/drawable-mdpi/ic_craft_normal.png Binary files differnew file mode 100644 index 000000000..6a53af594 --- /dev/null +++ b/res/drawable-mdpi/ic_craft_normal.png diff --git a/res/drawable-mdpi/ic_photo_sphere_normal.png b/res/drawable-mdpi/ic_photo_sphere_normal.png Binary files differnew file mode 100644 index 000000000..9278222d9 --- /dev/null +++ b/res/drawable-mdpi/ic_photo_sphere_normal.png diff --git a/res/drawable-mdpi/ic_settings_normal.png b/res/drawable-mdpi/ic_settings_normal.png Binary files differnew file mode 100644 index 000000000..cac3ab0d4 --- /dev/null +++ b/res/drawable-mdpi/ic_settings_normal.png diff --git a/res/drawable-mdpi/ic_timelapse_normal.png b/res/drawable-mdpi/ic_timelapse_normal.png Binary files differnew file mode 100644 index 000000000..44b02c2cf --- /dev/null +++ b/res/drawable-mdpi/ic_timelapse_normal.png diff --git a/res/drawable-mdpi/ic_video_normal.png b/res/drawable-mdpi/ic_video_normal.png Binary files differnew file mode 100644 index 000000000..c8a2dd155 --- /dev/null +++ b/res/drawable-mdpi/ic_video_normal.png diff --git a/res/drawable-mdpi/ic_wide_angle_normal.png b/res/drawable-mdpi/ic_wide_angle_normal.png Binary files differnew file mode 100644 index 000000000..73f2011d3 --- /dev/null +++ b/res/drawable-mdpi/ic_wide_angle_normal.png diff --git a/res/drawable-xhdpi/ic_camera_normal.png b/res/drawable-xhdpi/ic_camera_normal.png Binary files differnew file mode 100644 index 000000000..db7ad435c --- /dev/null +++ b/res/drawable-xhdpi/ic_camera_normal.png diff --git a/res/drawable-xhdpi/ic_craft_normal.png b/res/drawable-xhdpi/ic_craft_normal.png Binary files differnew file mode 100644 index 000000000..783581dbc --- /dev/null +++ b/res/drawable-xhdpi/ic_craft_normal.png diff --git a/res/drawable-xhdpi/ic_photo_sphere_normal.png b/res/drawable-xhdpi/ic_photo_sphere_normal.png Binary files differnew file mode 100644 index 000000000..63f78399e --- /dev/null +++ b/res/drawable-xhdpi/ic_photo_sphere_normal.png diff --git a/res/drawable-xhdpi/ic_settings_normal.png b/res/drawable-xhdpi/ic_settings_normal.png Binary files differnew file mode 100644 index 000000000..5c531bb85 --- /dev/null +++ b/res/drawable-xhdpi/ic_settings_normal.png diff --git a/res/drawable-xhdpi/ic_timelapse_normal.png b/res/drawable-xhdpi/ic_timelapse_normal.png Binary files differnew file mode 100644 index 000000000..f9a160420 --- /dev/null +++ b/res/drawable-xhdpi/ic_timelapse_normal.png diff --git a/res/drawable-xhdpi/ic_video_normal.png b/res/drawable-xhdpi/ic_video_normal.png Binary files differnew file mode 100644 index 000000000..c6e561cfe --- /dev/null +++ b/res/drawable-xhdpi/ic_video_normal.png diff --git a/res/drawable-xhdpi/ic_wide_angle_normal.png b/res/drawable-xhdpi/ic_wide_angle_normal.png Binary files differnew file mode 100644 index 000000000..ffa96376d --- /dev/null +++ b/res/drawable-xhdpi/ic_wide_angle_normal.png diff --git a/res/drawable-xxhdpi/ic_camera_normal.png b/res/drawable-xxhdpi/ic_camera_normal.png Binary files differnew file mode 100644 index 000000000..125d3955d --- /dev/null +++ b/res/drawable-xxhdpi/ic_camera_normal.png diff --git a/res/drawable-xxhdpi/ic_craft_normal.png b/res/drawable-xxhdpi/ic_craft_normal.png Binary files differnew file mode 100644 index 000000000..e7dfb21d8 --- /dev/null +++ b/res/drawable-xxhdpi/ic_craft_normal.png diff --git a/res/drawable-xxhdpi/ic_photo_sphere_normal.png b/res/drawable-xxhdpi/ic_photo_sphere_normal.png Binary files differnew file mode 100644 index 000000000..2c886415d --- /dev/null +++ b/res/drawable-xxhdpi/ic_photo_sphere_normal.png diff --git a/res/drawable-xxhdpi/ic_settings_normal.png b/res/drawable-xxhdpi/ic_settings_normal.png Binary files differnew file mode 100644 index 000000000..1ff448531 --- /dev/null +++ b/res/drawable-xxhdpi/ic_settings_normal.png diff --git a/res/drawable-xxhdpi/ic_timelapse_normal.png b/res/drawable-xxhdpi/ic_timelapse_normal.png Binary files differnew file mode 100644 index 000000000..aaade0436 --- /dev/null +++ b/res/drawable-xxhdpi/ic_timelapse_normal.png diff --git a/res/drawable-xxhdpi/ic_video_normal.png b/res/drawable-xxhdpi/ic_video_normal.png Binary files differnew file mode 100644 index 000000000..e9fd11b9a --- /dev/null +++ b/res/drawable-xxhdpi/ic_video_normal.png diff --git a/res/drawable-xxhdpi/ic_wide_angle_normal.png b/res/drawable-xxhdpi/ic_wide_angle_normal.png Binary files differnew file mode 100644 index 000000000..31460dda2 --- /dev/null +++ b/res/drawable-xxhdpi/ic_wide_angle_normal.png diff --git a/res/drawable/craft.png b/res/drawable/craft.png Binary files differdeleted file mode 100644 index 89dfb44fc..000000000 --- a/res/drawable/craft.png +++ /dev/null diff --git a/res/drawable/photo.png b/res/drawable/photo.png Binary files differdeleted file mode 100644 index d5b648719..000000000 --- a/res/drawable/photo.png +++ /dev/null diff --git a/res/drawable/photosphere.png b/res/drawable/photosphere.png Binary files differdeleted file mode 100644 index f60de5eba..000000000 --- a/res/drawable/photosphere.png +++ /dev/null diff --git a/res/drawable/settings.png b/res/drawable/settings.png Binary files differdeleted file mode 100644 index c50d3240f..000000000 --- a/res/drawable/settings.png +++ /dev/null diff --git a/res/drawable/timelapse.png b/res/drawable/timelapse.png Binary files differdeleted file mode 100644 index fae717653..000000000 --- a/res/drawable/timelapse.png +++ /dev/null diff --git a/res/drawable/video.png b/res/drawable/video.png Binary files differdeleted file mode 100644 index ea7b90a2d..000000000 --- a/res/drawable/video.png +++ /dev/null diff --git a/res/drawable/wideangle.png b/res/drawable/wideangle.png Binary files differdeleted file mode 100644 index 9c5486537..000000000 --- a/res/drawable/wideangle.png +++ /dev/null diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml index 63c54d01f..5386794bf 100644 --- a/res/layout/activity_main.xml +++ b/res/layout/activity_main.xml @@ -23,6 +23,12 @@ android:background="@null"> <include layout="@layout/camera_filmstrip" /> + <com.android.camera.ui.ModeTransitionView + android:id="@+id/mode_transition_view" + android:layerType="hardware" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="match_parent" /> <include layout="@layout/mode_list_layout" /> </com.android.camera.ui.MainActivityLayout>
\ No newline at end of file diff --git a/res/layout/generic_module.xml b/res/layout/generic_module.xml new file mode 100644 index 000000000..588b5d807 --- /dev/null +++ b/res/layout/generic_module.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> +<!-- This layout is shared by phone and tablet in landscape orientation. --> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent"> + <!-- Wrap a frame layout around texture view so that when scaled, texture + view will not draw outside its unscaled bounds --> + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TextureView + android:id="@+id/preview_content" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <View + android:id="@+id/preview_cover" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black" + android:visibility="gone" /> + </FrameLayout> + <View + android:id="@+id/flash_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/white" + android:visibility="gone" + android:alpha="0" /> + <com.android.camera.ui.RenderOverlay + android:id="@+id/render_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <FrameLayout + android:id="@+id/module_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <include layout="@layout/camera_controls" + android:layout_gravity="center" + style="@style/CameraControls"/> +</merge> diff --git a/res/layout/photo_module.xml b/res/layout/photo_module.xml index 0410f16fc..447e87afb 100644 --- a/res/layout/photo_module.xml +++ b/res/layout/photo_module.xml @@ -24,22 +24,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center"> - <!-- Wrap a frame layout around texture view so that when scaled, texture - view will not draw outside its unscaled bounds --> - <FrameLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - <TextureView - android:id="@+id/preview_content" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - <View - android:id="@+id/preview_cover" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/black" - android:visibility="gone" /> - </FrameLayout> <ImageView android:id="@+id/review_image" android:layout_width="match_parent" @@ -48,24 +32,11 @@ android:clickable="true" android:background="@android:color/black" android:scaleType="fitCenter"/> - <View - android:id="@+id/flash_overlay" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/white" - android:visibility="gone" - android:alpha="0" /> + <ViewStub android:id="@+id/face_view_stub" android:inflatedId="@+id/face_view" android:layout="@layout/face_view" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> - <com.android.camera.ui.RenderOverlay - android:id="@+id/render_overlay" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - <include layout="@layout/camera_controls" - android:layout_gravity="center" - style="@style/CameraControls"/> </merge> diff --git a/res/layout/video_module.xml b/res/layout/video_module.xml index 198be8913..91288a23e 100644 --- a/res/layout/video_module.xml +++ b/res/layout/video_module.xml @@ -17,38 +17,6 @@ <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent"> - <!-- Wrap a frame layout around texture view so that when scaled, texture - view will not draw outside its unscaled bounds --> - <FrameLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - <TextureView - android:id="@+id/preview_content" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - <View - android:id="@+id/preview_cover" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/black" - android:visibility="gone" /> - </FrameLayout> - <View - android:id="@+id/flash_overlay" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/white" - android:visibility="gone" - android:alpha="0" /> - <FrameLayout android:id="@+id/preview_border" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" - android:background="@drawable/ic_snapshot_border" /> - <com.android.camera.ui.RenderOverlay - android:id="@+id/render_overlay" - android:layout_width="match_parent" - android:layout_height="match_parent" /> <com.android.camera.ui.RotateLayout android:id="@+id/recording_time_rect" style="@style/ViewfinderLabelLayout"> <include layout="@layout/viewfinder_labels_video" android:id="@+id/labels" /> @@ -67,7 +35,4 @@ android:visibility="gone" android:onClick="onReviewPlayClicked"/> - <include layout="@layout/camera_controls" - android:layout_gravity="center" - style="@style/CameraControls"/> </merge> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 1dc112a71..01d5280bd 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -158,4 +158,5 @@ <dimen name="mode_selector_icon_block_width">84dp</dimen> <dimen name="mode_selector_icon_drawable_size">32dp</dimen> + <dimen name="mode_transition_view_icon_size">48dp</dimen> </resources> diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index a2853065b..37a8bd676 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -68,6 +68,7 @@ import android.widget.ShareActionProvider; import com.android.camera.app.AppController; import com.android.camera.app.AppManagerFactory; import com.android.camera.app.CameraApp; +import com.android.camera.app.CameraAppUI; import com.android.camera.app.CameraController; import com.android.camera.app.CameraManager; import com.android.camera.app.CameraManagerFactory; @@ -100,6 +101,7 @@ import com.android.camera.tinyplanet.TinyPlanetFragment; import com.android.camera.ui.CameraControls; import com.android.camera.ui.DetailsDialog; import com.android.camera.ui.FilmstripView; +import com.android.camera.ui.MainActivityLayout; import com.android.camera.ui.ModeListView; import com.android.camera.ui.SettingsView; import com.android.camera.util.ApiHelper; @@ -113,7 +115,7 @@ import com.android.camera2.R; import java.io.File; public class CameraActivity extends Activity - implements AppController, ModeListView.ModeSwitchListener, CameraManager.CameraOpenCallback, + implements AppController, CameraManager.CameraOpenCallback, ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener, OrientationManager.OnOrientationChangeListener { @@ -221,6 +223,7 @@ public class CameraActivity extends Activity private CameraController mCameraController; private boolean mPaused; + private CameraAppUI mCameraAppUI; private MediaSaver mMediaSaver; @@ -827,6 +830,11 @@ public class CameraActivity extends Activity } @Override + public int getCurrentModuleIndex() { + return mCurrentModeIndex; + } + + @Override public SurfaceTexture getPreviewBuffer() { // TODO: implement this return null; @@ -1088,12 +1096,6 @@ public class CameraActivity extends Activity ModulesInfo.setupModules(this, mModuleManager); mModeListView = (ModeListView) findViewById(R.id.mode_list_layout); - if (mModeListView != null) { - mModeListView.setModeSwitchListener(this); - } else { - Log.e(TAG, "Cannot find mode list in the view hierarchy"); - } - if (ApiHelper.HAS_ROTATION_ANIMATION) { setRotationAnimation(); } @@ -1154,6 +1156,12 @@ public class CameraActivity extends Activity // Set up the camera preview first so the preview shows up ASAP. mFilmstripController.setListener(mFilmStripListener); + // TODO: Remove the 3rd parameter once mCameraModuleRoot is moved out of filmstrip + mCameraAppUI = new CameraAppUI(this, + (MainActivityLayout) findViewById(R.id.activity_root_view), + mCameraModuleRootView, + isSecureCamera(), isCaptureIntent()); + int modeIndex = -1; if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction()) || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) { @@ -1198,6 +1206,12 @@ public class CameraActivity extends Activity mSettingsManager = new SettingsManager(this); setModuleFromModeIndex(modeIndex); + + // TODO: Remove this when refactor is done. + if (modeIndex == ModulesInfo.MODULE_PHOTO || + modeIndex == ModulesInfo.MODULE_VIDEO) { + mCameraAppUI.prepareModuleUI(); + } mCurrentModule.init(this, mCameraModuleRootView); if (!mSecureCamera) { @@ -1494,10 +1508,33 @@ public class CameraActivity extends Activity return; } + if (modeIndex == ModeListView.MODE_SETTING) { + onSettingsSelected(); + return; + } + CameraHolder.instance().keep(); closeModule(mCurrentModule); - + int oldModuleIndex = mCurrentModeIndex; setModuleFromModeIndex(modeIndex); + + // TODO: The following check is temporary for quick switch between video and photo. + // When the refactor is done, similar logic will be applied to all modules. + if (mCurrentModeIndex == ModulesInfo.MODULE_PHOTO + || mCurrentModeIndex == ModulesInfo.MODULE_VIDEO) { + if (oldModuleIndex != ModulesInfo.MODULE_PHOTO + && oldModuleIndex != ModulesInfo.MODULE_VIDEO) { + mCameraAppUI.prepareModuleUI(); + } else { + mCameraAppUI.clearModuleUI(); + } + } else { + // This is the old way of removing all views in CameraRootView. Will + // be deprecated soon. It is here to make sure modules that haven't + // been refactored can still function. + mCameraAppUI.clearCameraUI(); + } + openModule(mCurrentModule); mCurrentModule.onOrientationChanged(mLastRawOrientation); if (mMediaSaver != null) { @@ -1509,8 +1546,7 @@ public class CameraActivity extends Activity prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply(); } - @Override - public void onSettingsSelected(int modeIndex) { + public void onSettingsSelected() { // Temporary until we finalize the touch flow. LayoutInflater inflater = getLayoutInflater(); SettingsView settingsView = (SettingsView) inflater.inflate(R.layout.settings_list_layout, @@ -1590,7 +1626,6 @@ public class CameraActivity extends Activity private void closeModule(CameraModule module) { module.pause(); - ((ViewGroup) mCameraModuleRootView).removeAllViews(); } private void performDeletion() { diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 468ec7b62..69706cc40 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -846,10 +846,10 @@ public class PhotoModule case PhotoController.PREVIEW_STOPPED: case PhotoController.SNAPSHOT_IN_PROGRESS: case PhotoController.SWITCHING_CAMERA: - mUI.enableGestures(false); + // TODO: Tell app UI to disable swipe break; case PhotoController.IDLE: - mUI.enableGestures(true); + // TODO: Tell app UI to enable swipe break; } } diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index 0842a638a..c75e97b1f 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -71,7 +71,6 @@ public class PhotoUI implements PieListener, private final AnimationManager mAnimationManager; private CameraActivity mActivity; private PhotoController mController; - private PreviewGestures mGestures; private View mRootView; private SurfaceTexture mSurfaceTexture; @@ -192,8 +191,9 @@ public class PhotoUI implements PieListener, mController = controller; mRootView = parent; + ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout); mActivity.getLayoutInflater().inflate(R.layout.photo_module, - (ViewGroup) mRootView, true); + (ViewGroup) moduleRoot, true); mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); mFlashOverlay = mRootView.findViewById(R.id.flash_overlay); mPreviewCover = mRootView.findViewById(R.id.preview_cover); @@ -203,6 +203,11 @@ public class PhotoUI implements PieListener, mTextureView.addOnLayoutChangeListener(mLayoutListener); initIndicators(); + mSurfaceTexture = mTextureView.getSurfaceTexture(); + if (mSurfaceTexture != null) { + setTransformMatrix(mTextureView.getWidth(), mTextureView.getHeight()); + mPreviewCover.setVisibility(View.GONE); + } mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button); mMenuButton = mRootView.findViewById(R.id.menu); ViewStub faceViewStub = (ViewStub) mRootView @@ -342,15 +347,7 @@ public class PhotoUI implements PieListener, mZoomRenderer = new ZoomRenderer(mActivity); mRenderOverlay.addRenderer(mZoomRenderer); } - - if (mGestures == null) { - // this will handle gesture disambiguation and dispatching - mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); - mRenderOverlay.setGestures(mGestures); - } - mGestures.setZoomEnabled(params.isZoomSupported()); - mGestures.setRenderOverlay(mRenderOverlay); - mRenderOverlay.requestLayout(); + mRenderOverlay.setGestures(null); initializeZoom(params); updateOnScreenIndicators(params, prefGroup, prefs); @@ -524,12 +521,6 @@ public class PhotoUI implements PieListener, mAnimationManager.startFlashAnimation(mFlashOverlay); } - public void enableGestures(boolean enable) { - if (mGestures != null) { - mGestures.setEnabled(enable); - } - } - // forward from preview gestures to controller @Override public void onSingleTapUp(View view, int x, int y) { @@ -564,9 +555,6 @@ public class PhotoUI implements PieListener, if (mFaceView != null) { mFaceView.setBlockDraw(!previewFocused); } - if (mGestures != null) { - mGestures.setEnabled(previewFocused); - } if (mRenderOverlay != null) { // this can not happen in capture mode mRenderOverlay.setVisibility(previewFocused ? View.VISIBLE : View.GONE); diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index 7d5680fb4..58de510d7 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -540,7 +540,7 @@ public class VideoModule extends CameraModule @Override public void onShutterButtonFocus(boolean pressed) { - mUI.setShutterPressed(pressed); + // TODO: Remove this when old camera controls are removed from the UI. } private void readVideoPreferences() { diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java index affed36cd..eff8ff732 100644 --- a/src/com/android/camera/VideoUI.java +++ b/src/com/android/camera/VideoUI.java @@ -79,7 +79,6 @@ public class VideoUI implements PieRenderer.PieListener, private CameraControls mCameraControls; private SettingsPopup mPopup; private ZoomRenderer mZoomRenderer; - private PreviewGestures mGestures; private View mMenuButton; private OnScreenIndicators mOnScreenIndicators; private RotateLayout mRecordingTimeRect; @@ -170,11 +169,21 @@ public class VideoUI implements PieRenderer.PieListener, mActivity = activity; mController = controller; mRootView = parent; - mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true); + ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout); + mActivity.getLayoutInflater().inflate(R.layout.video_module, + (ViewGroup) moduleRoot, true); mPreviewCover = mRootView.findViewById(R.id.preview_cover); mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content); mTextureView.setSurfaceTextureListener(this); mTextureView.addOnLayoutChangeListener(mLayoutListener); + + mSurfaceTexture = mTextureView.getSurfaceTexture(); + if (mSurfaceTexture != null) { + mPreviewWidth = mTextureView.getWidth(); + mPreviewHeight = mTextureView.getHeight(); + setTransformMatrix(mPreviewWidth, mPreviewHeight); + mPreviewCover.setVisibility(View.GONE); + } mFlashOverlay = mRootView.findViewById(R.id.flash_overlay); mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button); initializeMiscControls(); @@ -366,9 +375,6 @@ public class VideoUI implements PieRenderer.PieListener, } public void enableCameraControls(boolean enable) { - if (mGestures != null) { - mGestures.setZoomOnly(!enable); - } if (mPieRenderer != null && mPieRenderer.showsItems()) { mPieRenderer.hide(); } @@ -427,11 +433,7 @@ public class VideoUI implements PieRenderer.PieListener, mZoomRenderer = new ZoomRenderer(mActivity); } mRenderOverlay.addRenderer(mZoomRenderer); - if (mGestures == null) { - mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); - mRenderOverlay.setGestures(mGestures); - } - mGestures.setRenderOverlay(mRenderOverlay); + mRenderOverlay.setGestures(null); mPreviewThumb = mRootView.findViewById(R.id.preview_thumb); mPreviewThumb.setOnClickListener(new OnClickListener() { @@ -521,12 +523,6 @@ public class VideoUI implements PieRenderer.PieListener, return false; } - // disable preview gestures after shutter is pressed - public void setShutterPressed(boolean pressed) { - if (mGestures == null) return; - mGestures.setEnabled(!pressed); - } - public void enableShutter(boolean enable) { if (mShutterButton != null) { mShutterButton.setEnabled(enable); @@ -612,9 +608,7 @@ public class VideoUI implements PieRenderer.PieListener, } else { hideUI(); } - if (mGestures != null) { - mGestures.setEnabled(previewFocused); - } + if (mRenderOverlay != null) { // this can not happen in capture mode mRenderOverlay.setVisibility(previewFocused ? View.VISIBLE : View.GONE); @@ -627,11 +621,6 @@ public class VideoUI implements PieRenderer.PieListener, } public void initializeZoom(Parameters param) { - if (param == null || !param.isZoomSupported()) { - mGestures.setZoomEnabled(false); - return; - } - mGestures.setZoomEnabled(true); mZoomMax = param.getMaxZoom(); mZoomRatios = param.getZoomRatios(); // Currently we use immediate zoom for fast zooming to get better UX and diff --git a/src/com/android/camera/app/AppController.java b/src/com/android/camera/app/AppController.java index 61c1c1b4d..84398eb1f 100644 --- a/src/com/android/camera/app/AppController.java +++ b/src/com/android/camera/app/AppController.java @@ -24,6 +24,7 @@ import android.widget.FrameLayout; import com.android.camera.LocationManager; import com.android.camera.SettingsManager; +import com.android.camera.ui.ModeListView; /** * The controller at app level. @@ -65,6 +66,18 @@ public interface AppController { */ public boolean isPaused(); + /** + * Returns current running module index. + */ + public int getCurrentModuleIndex(); + + /** + * This gets called when mode is changed. + * + * @param moduleIndex index of the new module to switch to + */ + public void onModeSelected(int moduleIndex); + /********************** UI / Camera preview **********************/ /** diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java new file mode 100644 index 000000000..104c8c0be --- /dev/null +++ b/src/com/android/camera/app/CameraAppUI.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2013 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.android.camera.app; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.util.Log; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.TextureView; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.android.camera.AnimationManager; +import com.android.camera.ui.CameraControls; +import com.android.camera.ui.MainActivityLayout; +import com.android.camera.ui.ModeListView; +import com.android.camera.ui.ModeTransitionView; +import com.android.camera.ui.RenderOverlay; +import com.android.camera2.R; + +/** + * CameraAppUI centralizes control of views shared across modules. Whereas module + * specific views will be handled in each Module UI. For example, we can now + * bring the flash animation and capture animation up from each module to app + * level, as these animations are largely the same for all modules. + * + * This class also serves to disambiguate touch events. It recognizes all the + * swipe gestures that happen on the preview by attaching a touch listener to + * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge + * of how swipe from each direction should be handled, it can then redirect these + * events to appropriate recipient views. + */ +public class CameraAppUI implements ModeListView.ModeSwitchListener { + private final static String TAG = "CameraAppUI"; + + private final AppController mController; + private final boolean mIsCaptureIntent; + private final boolean mIsSecureCamera; + private final AnimationManager mAnimationManager; + + // Swipe states: + private final int IDLE = 0; + private final int SWIPE_UP = 1; + private final int SWIPE_DOWN = 2; + private final int SWIPE_LEFT = 3; + private final int SWIPE_RIGHT = 4; + + // Touch related measures: + private final int mSlop; + private final int SWIPE_TIME_OUT = 500; + + // App level views: + private final ViewGroup mCameraRootView; + private final ModeTransitionView mModeTransitionView; + private final MainActivityLayout mAppRootView; + private final ModeListView mModeListView; + private final View mFilmStripView; + private TextureView mTextureView; + private CameraControls mCameraControls; + private RenderOverlay mRenderOverlay; + private View mFlashOverlay; + private ViewGroup mModuleUI; + + private GestureDetector mGestureDetector; + private int mSwipeState = IDLE; + private ImageView mPreviewThumbView; + + public interface AnimationFinishedListener { + public void onAnimationFinished(boolean success); + } + + private class MyTouchListener implements View.OnTouchListener { + + @Override + public boolean onTouch(View v, MotionEvent event) { + return mGestureDetector.onTouchEvent(event); + } + } + + /** + * This gesture listener finds out the direction of the scroll gestures and + * sends them to CameraAppUI to do further handling. + */ + private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { + private MotionEvent mDown; + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) { + if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT + || mSwipeState != IDLE) { + return true; + } + + int deltaX = (int) (ev.getX() - mDown.getX()); + int deltaY = (int) (ev.getY() - mDown.getY()); + if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { + if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) { + // Calculate the direction of the swipe. + if (deltaX >= Math.abs(deltaY)) { + // Swipe right. + setSwipeState(SWIPE_RIGHT); + } else if (deltaX <= -Math.abs(deltaY)) { + // Swipe left. + setSwipeState(SWIPE_LEFT); + } else if (deltaY >= Math.abs(deltaX)) { + // Swipe down. + setSwipeState(SWIPE_DOWN); + } else if (deltaY <= -Math.abs(deltaX)) { + // Swipe up. + setSwipeState(SWIPE_UP); + } + } + } + return true; + } + + private void setSwipeState(int swipeState) { + mSwipeState = swipeState; + // Notify new swipe detected. + onSwipeDetected(swipeState); + } + + @Override + public boolean onDown(MotionEvent ev) { + mDown = MotionEvent.obtain(ev); + mSwipeState = IDLE; + return true; + } + + @Override + public boolean onSingleTapUp(MotionEvent ev) { + // This keeps pie menu functioning until the alternative is in. + // TODO: Remove after bottom bar is finalized. + mRenderOverlay.directTouchEventsToPie(mDown); + mRenderOverlay.directTouchEventsToPie(ev); + return true; + } + } + + public CameraAppUI(AppController controller, MainActivityLayout appRootView, + ViewGroup cameraRoot, + boolean isSecureCamera, boolean isCaptureIntent) { + mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop(); + mController = controller; + mIsSecureCamera = isSecureCamera; + mIsCaptureIntent = isCaptureIntent; + + mAppRootView = appRootView; + mFilmStripView = appRootView.findViewById(R.id.filmstrip_view); + mCameraRootView = cameraRoot; + mModeTransitionView = (ModeTransitionView) + mAppRootView.findViewById(R.id.mode_transition_view); + mGestureDetector = new GestureDetector(controller.getAndroidContext(), + new MyGestureListener()); + mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout); + if (mModeListView != null) { + mModeListView.setModeSwitchListener(this); + } else { + Log.e(TAG, "Cannot find mode list in the view hierarchy"); + } + mAnimationManager = new AnimationManager(); + } + + /** + * Redirects touch events to appropriate recipient views based on swipe direction. + * More specifically, swipe up and swipe down will be handled by the view that handles + * mode transition; swipe left will be send to filmstrip; swipe right will be redirected + * to mode list in order to bring up mode list. + */ + private void onSwipeDetected(int swipeState) { + if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) { + // Quick switch between photo/video. + if (mController.getCurrentModuleIndex() == ModeListView.MODE_PHOTO || + mController.getCurrentModuleIndex() == ModeListView.MODE_VIDEO) { + mAppRootView.redirectTouchEventsTo(mModeTransitionView); + + final int moduleToTransitionTo = + mController.getCurrentModuleIndex() == ModeListView.MODE_PHOTO ? + ModeListView.MODE_VIDEO : ModeListView.MODE_PHOTO; + int shadeColorId = ModeListView.getModeThemeColor(moduleToTransitionTo); + int iconRes = ModeListView.getModeIconResourceId(moduleToTransitionTo); + + AnimationFinishedListener listener = new AnimationFinishedListener() { + public void onAnimationFinished(boolean success) { + if (success) { + // Go to new module when the previous operation is successful. + mController.onModeSelected(moduleToTransitionTo); + mModeTransitionView.startPeepHoleAnimation(); + } + } + }; + if (mSwipeState == SWIPE_UP) { + mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener); + } else { + mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener); + } + } + } else if (swipeState == SWIPE_LEFT) { + // Pass the touch sequence to filmstrip. + mAppRootView.redirectTouchEventsTo(mFilmStripView); + + } else if (swipeState == SWIPE_RIGHT) { + // Pass the touch to mode switcher + mAppRootView.redirectTouchEventsTo(mModeListView); + } + } + + /** + * This inflates generic_module layout, which contains all the shared views across + * modules. Then each module inflates their own views in the given view group. For + * now, this is called every time switching from a not-yet-refactored module to a + * refactored module. In the future, this should only need to be done once per app + * start. + */ + public void prepareModuleUI() { + mCameraRootView.removeAllViews(); + LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.generic_module, mCameraRootView, true); + + mModuleUI = (ViewGroup) mCameraRootView.findViewById(R.id.module_layout); + mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content); + mRenderOverlay = (RenderOverlay) mCameraRootView.findViewById(R.id.render_overlay); + mRenderOverlay.setOnTouchListener(new MyTouchListener()); + mFlashOverlay = mCameraRootView.findViewById(R.id.flash_overlay); + mPreviewThumbView = (ImageView) mCameraRootView.findViewById(R.id.preview_thumb); + + // TODO: Remove camera controls. + mCameraControls = (CameraControls) mCameraRootView.findViewById(R.id.camera_controls); + } + + // TODO: Remove this when refactor is done. + // This is here to ensure refactored modules can work with not-yet-refactored ones. + public void clearCameraUI() { + mCameraRootView.removeAllViews(); + mModuleUI = null; + mTextureView = null; + mRenderOverlay = null; + mFlashOverlay = null; + mCameraControls = null; + } + + /** + * Called indirectly from each module in their initialization to get a view group + * to inflate the module specific views in. + * + * @return a view group for modules to attach views to + */ + public ViewGroup getModuleRootView() { + return mModuleUI; + } + + /** + * Remove all the module specific views. + */ + public void clearModuleUI() { + if (mModuleUI != null) { + mModuleUI.removeAllViews(); + } + mRenderOverlay.clear(); + } + + @Override + public void onModeSelected(int modeIndex) { + mController.onModeSelected(modeIndex); + } + + /** + * Sets the transform matrix on the preview TextureView + */ + public void setPreviewTransformMatrix(Matrix transformMatrix) { + if (mTextureView == null) { + throw new UnsupportedOperationException("Cannot set transform matrix on a null" + + " TextureView"); + } + mTextureView.setTransform(transformMatrix); + } + + + /********************** Capture animation **********************/ + /* TODO: This session is subject to UX changes. In addition to the generic + flash animation and post capture animation, consider designating a parameter + for specifying the type of animation, as well as an animation finished listener + so that modules can have more knowledge of the status of the animation. */ + + /** + * Starts the pre-capture animation. + */ + public void startPreCaptureAnimation() { + mAnimationManager.startFlashAnimation(mFlashOverlay); + } + + /** + * Cancels the pre-capture animation. + */ + public void cancelPreCaptureAnimation() { + mAnimationManager.cancelAnimations(); + } + + /** + * Starts the post-capture animation with the current preview image. + */ + public void startPostCaptureAnimation() { + if (mTextureView == null) { + Log.e(TAG, "Cannot get a frame from a null TextureView for animation"); + return; + } + // TODO: Down sample bitmap + startPostCaptureAnimation(mTextureView.getBitmap()); + } + + /** + * Starts the post-capture animation with the given thumbnail. + * + * @param thumbnail The thumbnail for the animation. + */ + public void startPostCaptureAnimation(Bitmap thumbnail) { + mPreviewThumbView.setImageBitmap(thumbnail); + mAnimationManager.startCaptureAnimation(mPreviewThumbView); + } + + /** + * Cancels the post-capture animation. + */ + public void cancelPostCaptureAnimation() { + mAnimationManager.cancelAnimations(); + } +} diff --git a/src/com/android/camera/ui/MainActivityLayout.java b/src/com/android/camera/ui/MainActivityLayout.java index 7ae293bfc..f4fb9f8a5 100644 --- a/src/com/android/camera/ui/MainActivityLayout.java +++ b/src/com/android/camera/ui/MainActivityLayout.java @@ -19,13 +19,15 @@ package com.android.camera.ui; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; +import android.view.View; import android.view.ViewConfiguration; import android.widget.FrameLayout; import com.android.camera2.R; -class MainActivityLayout extends FrameLayout { +public class MainActivityLayout extends FrameLayout { // Only check for intercepting touch events within first 500ms private static final int SWIPE_TIME_OUT = 500; @@ -34,6 +36,9 @@ class MainActivityLayout extends FrameLayout { private boolean mCheckToIntercept; private MotionEvent mDown; private final int mSlop; + private final String TAG = "MainActivityLayout"; + private boolean mRequestToInterceptTouchEvents = false; + private View mTouchReceiver = null; public MainActivityLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -45,7 +50,13 @@ class MainActivityLayout extends FrameLayout { if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { mCheckToIntercept = true; mDown = MotionEvent.obtain(ev); + mTouchReceiver = null; + mRequestToInterceptTouchEvents = false; return false; + } else if (mRequestToInterceptTouchEvents) { + mRequestToInterceptTouchEvents = false; + onTouchEvent(mDown); + return true; } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { // Do not intercept touch once child is in zoom mode mCheckToIntercept = false; @@ -63,6 +74,7 @@ class MainActivityLayout extends FrameLayout { && deltaX > mSlop) { // Intercept right swipe if (Math.abs(deltaX) >= Math.abs(deltaY) * 2) { + mTouchReceiver = mModeList; onTouchEvent(mDown); return true; } @@ -73,9 +85,11 @@ class MainActivityLayout extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent ev) { - // TODO: This also needs to be modified with a better touch flow. - // Pass the right swipe to mode switcher. - return mModeList.onTouchEvent(ev); + if (mTouchReceiver != null) { + mTouchReceiver.setVisibility(VISIBLE); + return mTouchReceiver.onTouchEvent(ev); + } + return false; } @Override @@ -88,4 +102,13 @@ class MainActivityLayout extends FrameLayout { public void onFinishInflate() { mModeList = (ModeListView) findViewById(R.id.mode_list_layout); } + + public void redirectTouchEventsTo(View touchReceiver) { + if (touchReceiver == null) { + Log.e(TAG, "Cannot redirect touch to a null receiver."); + return; + } + mTouchReceiver = touchReceiver; + mRequestToInterceptTouchEvents = true; + } } diff --git a/src/com/android/camera/ui/ModeListView.java b/src/com/android/camera/ui/ModeListView.java index e54945598..1ae70c912 100644 --- a/src/com/android/camera/ui/ModeListView.java +++ b/src/com/android/camera/ui/ModeListView.java @@ -23,8 +23,6 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.LayoutInflater; @@ -75,15 +73,16 @@ public class ModeListView extends ScrollView { private static final int ACCORDION_ANIMATION = 2; private static final int SCROLLING = 3; - private final int[] mIconResId = {R.drawable.photo, R.drawable.video, - R.drawable.photosphere, R.drawable.craft, R.drawable.timelapse, - R.drawable.wideangle, R.drawable.settings,}; + private static final int[] mIconResId = {R.drawable.ic_camera_normal, + R.drawable.ic_video_normal, R.drawable.ic_photo_sphere_normal, + R.drawable.ic_craft_normal, R.drawable.ic_timelapse_normal, + R.drawable.ic_wide_angle_normal, R.drawable.ic_settings_normal,}; - private final int[] mTextResId = {R.string.mode_camera, R.string.mode_video, + private static final int[] mTextResId = {R.string.mode_camera, R.string.mode_video, R.string.mode_photosphere, R.string.mode_craft, R.string.mode_timelapse, R.string.mode_wideangle, R.string.mode_settings}; - private final int[] mIconBlockColor = {R.color.camera_mode_color, + private static final int[] mIconBlockColor = {R.color.camera_mode_color, R.color.video_mode_color, R.color.photosphere_mode_color, R.color.craft_mode_color, R.color.timelapse_mode_color, R.color.wideangle_mode_color, R.color.settings_mode_color}; @@ -109,7 +108,6 @@ public class ModeListView extends ScrollView { public interface ModeSwitchListener { public void onModeSelected(int modeIndex); - public void onSettingsSelected(int modeIndex); } /** @@ -194,11 +192,7 @@ public class ModeListView extends ScrollView { int index = getFocusItem(ev.getX(), ev.getY()); // Validate the selection if (index != NO_ITEM_SELECTED) { - if (index == MODE_SETTING) { - onSettingsSelected(index); - } else { - onModeSelected(index); - } + onModeSelected(index); } return true; } @@ -243,7 +237,6 @@ public class ModeListView extends ScrollView { ModeSelectorItem selectorItem = (ModeSelectorItem) inflater.inflate(R.layout.mode_selector, null); mListView.addView(selectorItem); - // Set alternating background color for each mode selector in the list if (i % 2 == 0) { selectorItem.setBackgroundColor(getResources() @@ -255,12 +248,7 @@ public class ModeListView extends ScrollView { selectorItem.setIconBackgroundColor(getResources().getColor(mIconBlockColor[i])); // Set image - // TODO: Down-sampling here is temporary, will be removed when we get assets - // from UX. The goal will be to fit the icon into 32dp x 32dp rect. - BitmapFactory.Options opt = new BitmapFactory.Options(); - opt.inSampleSize = 4; - Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mIconResId[i], opt); - selectorItem.setImageBitmap(bitmap); + selectorItem.setImageResource(mIconResId[i]); // Set text CharSequence text = getResources().getText(mTextResId[i]); @@ -271,13 +259,6 @@ public class ModeListView extends ScrollView { resetModeSelectors(); } - private void onSettingsSelected(int modeIndex) { - if (mListener != null) { - mListener.onSettingsSelected(modeIndex); - } - snapBack(); - } - /** Notify ModeSwitchListener, if any, of the mode change. */ private void onModeSelected(int modeIndex) { if (mListener != null) { @@ -576,4 +557,32 @@ public class ModeListView extends ScrollView { }); mAnimatorSet.start(); } + + /** + * Get the theme color of a specific mode. + * + * @param modeIndex index of the mode + * @return theme color of the mode if input index is valid, otherwise 0 + */ + public static int getModeThemeColor(int modeIndex) { + if (modeIndex < 0 || modeIndex >= MODE_TOTAL) { + return 0; + } else { + return mIconBlockColor[modeIndex]; + } + } + + /** + * Get the mode icon resource id of a specific mode. + * + * @param modeIndex index of the mode + * @return icon resource id if the index is valid, otherwise 0 + */ + public static int getModeIconResourceId(int modeIndex) { + if (modeIndex < 0 || modeIndex >= MODE_TOTAL) { + return 0; + } else { + return mIconResId[modeIndex]; + } + } } diff --git a/src/com/android/camera/ui/ModeSelectorItem.java b/src/com/android/camera/ui/ModeSelectorItem.java index d6f25463a..5b6b1a226 100644 --- a/src/com/android/camera/ui/ModeSelectorItem.java +++ b/src/com/android/camera/ui/ModeSelectorItem.java @@ -113,12 +113,12 @@ class ModeSelectorItem extends FrameLayout { } /** - * Sets bitmap as the icon for the mode. + * Sets image resource as the icon for the mode. * - * @param bitmap bitmap to be used as icon + * @param resource resource id of the asset to be used as icon */ - public void setImageBitmap(Bitmap bitmap) { - mIcon.setImageBitmap(bitmap); + public void setImageResource(int resource) { + mIcon.setImageResource(resource); } /** diff --git a/src/com/android/camera/ui/ModeTransitionView.java b/src/com/android/camera/ui/ModeTransitionView.java new file mode 100644 index 000000000..c36c229d9 --- /dev/null +++ b/src/com/android/camera/ui/ModeTransitionView.java @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2013 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.android.camera.ui; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import com.android.camera.app.CameraAppUI; +import com.android.camera.util.Gusterpolator; +import com.android.camera2.R; + +/** + * This view is designed to handle all the animations during camera mode transition. + * It should only be visible during mode switch. + */ +public class ModeTransitionView extends View { + private static final String TAG = "ModeTransitionView"; + + private static final int PEEP_HOLE_ANIMATION_DURATION_MS = 550; + private static final int ICON_FADE_OUT_DURATION_MS = 850; + + private static final int PULL_UP_SHADE = 0; + private static final int PULL_DOWN_SHADE = 1; + private static final int PEEP_HOLE_ANIMATION = 2; + private static final int IDLE = 3; + + private static final float SCROLL_DISTANCE_MULTIPLY_FACTOR = 2f; + private static final int ALPHA_FULLY_TRANSPARENT = 0; + private static final int ALPHA_FULLY_OPAQUE = 255; + private static final int ALPHA_HALF_TRANSPARENT = 127; + + private final GestureDetector mGestureDetector; + private final Paint mMaskPaint = new Paint(); + private final Rect mIconRect = new Rect(); + /** An empty drawable to fall back to when mIconDrawable set to null. */ + private final Drawable mDefaultDrawable = new ColorDrawable(); + + private Drawable mIconDrawable; + private int mBackgroundColor; + private int mWidth = 0; + private int mHeight = 0; + private int mPeepHoleCenterX = 0; + private int mPeepHoleCenterY = 0; + private float mRadius = 0f; + private int mIconSize; + private AnimatorSet mPeepHoleAnimator; + private int mAnimationType = PEEP_HOLE_ANIMATION; + private float mScrollDistance = 0; + private final Path mShadePath = new Path(); + private final Paint mShadePaint = new Paint(); + private CameraAppUI.AnimationFinishedListener mAnimationFinishedListener; + private float mScrollTrend; + + public ModeTransitionView(Context context, AttributeSet attrs) { + super(context, attrs); + mMaskPaint.setAlpha(0); + mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + mBackgroundColor = getResources().getColor(R.color.video_mode_color); + mGestureDetector = new GestureDetector(getContext(), + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(MotionEvent ev) { + setScrollDistance(0f); + mScrollTrend = 0f; + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + setScrollDistance(getScrollDistance() + + SCROLL_DISTANCE_MULTIPLY_FACTOR * distanceY); + mScrollTrend = 0.3f * mScrollTrend + 0.7f * distanceY; + return false; + } + }); + mIconSize = getResources().getDimensionPixelSize(R.dimen.mode_transition_view_icon_size); + setIconDrawable(mDefaultDrawable); + } + + /** + * Updates the size and shape of the shade + */ + private void updateShade() { + if (mAnimationType == PULL_UP_SHADE || mAnimationType == PULL_DOWN_SHADE) { + mShadePath.reset(); + float shadeHeight; + if (mAnimationType == PULL_UP_SHADE) { + // Scroll distance > 0. + mShadePath.addRect(0, mHeight - getScrollDistance(), mWidth, mHeight, + Path.Direction.CW); + shadeHeight = getScrollDistance(); + } else { + // Scroll distance < 0. + mShadePath.addRect(0, 0, mWidth, - getScrollDistance(), Path.Direction.CW); + shadeHeight = getScrollDistance() * (-1); + } + + if (mIconDrawable != null) { + if (shadeHeight < mHeight / 2 || mHeight == 0) { + mIconDrawable.setAlpha(ALPHA_FULLY_TRANSPARENT); + } else { + int alpha = ((int) shadeHeight - mHeight / 2) * ALPHA_FULLY_OPAQUE + / (mHeight / 2); + mIconDrawable.setAlpha(alpha); + } + } + invalidate(); + } + } + + /** + * Sets the scroll distance. Note this function gets called in every + * frame during animation. It should be very light weight. + * + * @param scrollDistance the scaled distance that user has scrolled + */ + public void setScrollDistance(float scrollDistance) { + // First make sure scroll distance is clamped to the valid range. + if (mAnimationType == PULL_UP_SHADE) { + scrollDistance = Math.min(scrollDistance, mHeight); + scrollDistance = Math.max(scrollDistance, 0); + } else if (mAnimationType == PULL_DOWN_SHADE) { + scrollDistance = Math.min(scrollDistance, 0); + scrollDistance = Math.max(scrollDistance, -mHeight); + } + mScrollDistance = scrollDistance; + updateShade(); + } + + public float getScrollDistance() { + return mScrollDistance; + } + + @Override + public void onDraw(Canvas canvas) { + if (mAnimationType == PEEP_HOLE_ANIMATION) { + canvas.drawColor(mBackgroundColor); + if (mPeepHoleAnimator != null) { + // Draw a transparent circle using clear mode + canvas.drawCircle(mPeepHoleCenterX, mPeepHoleCenterY, mRadius, mMaskPaint); + } + } else if (mAnimationType == PULL_UP_SHADE || mAnimationType == PULL_DOWN_SHADE) { + canvas.drawPath(mShadePath, mShadePaint); + } else if (mAnimationType == IDLE) { + canvas.drawColor(mBackgroundColor); + } + super.onDraw(canvas); + mIconDrawable.draw(canvas); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mWidth = right - left; + mHeight = bottom - top; + // Center the icon in the view. + mIconRect.set(mWidth / 2 - mIconSize / 2, mHeight / 2 - mIconSize / 2, + mWidth / 2 + mIconSize / 2, mHeight / 2 + mIconSize / 2); + mIconDrawable.setBounds(mIconRect); + } + + /** + * This is an overloaded function. When no position is provided for the animation, + * the peep hole will start at the default position (i.e. center of the view). + */ + public void startPeepHoleAnimation() { + float x = mWidth / 2; + float y = mHeight / 2; + startPeepHoleAnimation(x, y); + } + + /** + * Starts the peep hole animation where the circle is centered at position (x, y). + */ + private void startPeepHoleAnimation(float x, float y) { + if (mPeepHoleAnimator != null && mPeepHoleAnimator.isRunning()) { + return; + } + mAnimationType = PEEP_HOLE_ANIMATION; + mPeepHoleCenterX = (int) x; + mPeepHoleCenterY = (int) y; + + int horizontalDistanceToFarEdge = Math.max(mPeepHoleCenterX, mWidth - mPeepHoleCenterX); + int verticalDistanceToFarEdge = Math.max(mPeepHoleCenterY, mHeight - mPeepHoleCenterY); + int endRadius = (int) (Math.sqrt(horizontalDistanceToFarEdge * horizontalDistanceToFarEdge + + verticalDistanceToFarEdge * verticalDistanceToFarEdge)); + + final ValueAnimator radiusAnimator = ValueAnimator.ofFloat(0, endRadius); + radiusAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS); + + final ValueAnimator iconScaleAnimator = ValueAnimator.ofFloat(1f, 0.5f); + iconScaleAnimator.setDuration(ICON_FADE_OUT_DURATION_MS); + + final ValueAnimator iconAlphaAnimator = ValueAnimator.ofInt(ALPHA_HALF_TRANSPARENT, + ALPHA_FULLY_TRANSPARENT); + iconAlphaAnimator.setDuration(ICON_FADE_OUT_DURATION_MS); + + mPeepHoleAnimator = new AnimatorSet(); + mPeepHoleAnimator.playTogether(radiusAnimator, iconAlphaAnimator, iconScaleAnimator); + mPeepHoleAnimator.setInterpolator(Gusterpolator.INSTANCE); + + iconAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + // Modify mask by enlarging the hole + mRadius = (Float) radiusAnimator.getAnimatedValue(); + + mIconDrawable.setAlpha((Integer) iconAlphaAnimator.getAnimatedValue()); + float scale = (Float) iconScaleAnimator.getAnimatedValue(); + int size = (int) (scale * (float) mIconSize); + + mIconDrawable.setBounds(mPeepHoleCenterX - size / 2, + mPeepHoleCenterY - size / 2, + mPeepHoleCenterX + size / 2, + mPeepHoleCenterY + size / 2); + + invalidate(); + } + }); + + mPeepHoleAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + mPeepHoleAnimator = null; + mRadius = 0; + mIconDrawable.setAlpha(ALPHA_FULLY_OPAQUE); + mIconDrawable.setBounds(mIconRect); + setVisibility(GONE); + mAnimationType = IDLE; + if (mAnimationFinishedListener != null) { + mAnimationFinishedListener.onAnimationFinished(true); + mAnimationFinishedListener = null; + } + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + mPeepHoleAnimator.start(); + + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + boolean touchHandled = mGestureDetector.onTouchEvent(ev); + if (ev.getActionMasked() == MotionEvent.ACTION_UP) { + // TODO: Take into account fling + snap(); + } + return touchHandled; + } + + /** + * Snaps the shade to position at the end of a gesture. + */ + private void snap() { + if (mScrollTrend >= 0 && mAnimationType == PULL_UP_SHADE) { + // Snap to full screen. + snapShadeTo(mHeight, ALPHA_FULLY_OPAQUE); + } else if (mScrollTrend <= 0 && mAnimationType == PULL_DOWN_SHADE) { + // Snap to full screen. + snapShadeTo(-mHeight, ALPHA_FULLY_OPAQUE); + } else if (mScrollTrend < 0 && mAnimationType == PULL_UP_SHADE) { + // Snap back. + snapShadeTo(0, ALPHA_FULLY_TRANSPARENT, false); + } else if (mScrollTrend > 0 && mAnimationType == PULL_DOWN_SHADE) { + // Snap back. + snapShadeTo(0, ALPHA_FULLY_TRANSPARENT, false); + } + } + + private void snapShadeTo(int scrollDistance, int alpha) { + snapShadeTo(scrollDistance, alpha, true); + } + + /** + * Snaps the shade to a given scroll distance and sets the icon alpha. If the shade + * is to snap back out, then hide the view after the animation. + * + * @param scrollDistance scaled user scroll distance + * @param alpha ending alpha of the icon drawable + * @param snapToFullScreen whether this snap animation snaps the shade to full screen + */ + private void snapShadeTo(final int scrollDistance, final int alpha, + final boolean snapToFullScreen) { + if (mAnimationType == PULL_UP_SHADE || mAnimationType == PULL_DOWN_SHADE) { + ObjectAnimator scrollAnimator = ObjectAnimator.ofFloat(this, "scrollDistance", + scrollDistance); + scrollAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + setScrollDistance(scrollDistance); + mIconDrawable.setAlpha(alpha); + mAnimationType = IDLE; + if (!snapToFullScreen) { + setVisibility(GONE); + } + if (mAnimationFinishedListener != null) { + mAnimationFinishedListener.onAnimationFinished(snapToFullScreen); + mAnimationFinishedListener = null; + } + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + scrollAnimator.setInterpolator(Gusterpolator.INSTANCE); + scrollAnimator.start(); + } + } + + + /** + * Set the states for the animation that pulls up a shade with given shade color. + * + * @param shadeColorId color id of the shade that will be pulled up + * @param iconId id of the icon that will appear on top the shade + * @param listener a listener that will get notified when the animation + * is finished. Could be <code>null</code>. + */ + public void prepareToPullUpShade(int shadeColorId, int iconId, + CameraAppUI.AnimationFinishedListener listener) { + prepareShadeAnimation(PULL_UP_SHADE, shadeColorId, iconId, listener); + } + + /** + * Set the states for the animation that pulls down a shade with given shade color. + * + * @param shadeColorId color id of the shade that will be pulled down + * @param modeIconResourceId id of the icon that will appear on top the shade + * @param listener a listener that will get notified when the animation + * is finished. Could be <code>null</code>. + */ + public void prepareToPullDownShade(int shadeColorId, int modeIconResourceId, + CameraAppUI.AnimationFinishedListener listener) {; + prepareShadeAnimation(PULL_DOWN_SHADE, shadeColorId, modeIconResourceId, listener); + } + + /** + * Set the states for the animation that involves a shade. + * + * @param animationType type of animation that will happen to the shade + * @param shadeColorId color id of the shade that will be animated + * @param iconResId id of the icon that will appear on top the shade + * @param listener a listener that will get notified when the animation + * is finished. Could be <code>null</code>. + */ + private void prepareShadeAnimation(int animationType, int shadeColorId, int iconResId, + CameraAppUI.AnimationFinishedListener listener) { + mAnimationFinishedListener = listener; + if (mPeepHoleAnimator != null && mPeepHoleAnimator.isRunning()) { + mPeepHoleAnimator.end(); + } + mAnimationType = animationType; + resetShade(shadeColorId, iconResId); + } + + /** + * Reset the shade with the given shade color and icon drawable. + * + * @param shadeColorId id of the shade color + * @param modeIconResourceId resource id of the icon drawable + */ + private void resetShade(int shadeColorId, int modeIconResourceId) { + // Sets color for the shade. + int shadeColor = getResources().getColor(shadeColorId); + mBackgroundColor = shadeColor; + mShadePaint.setColor(shadeColor); + // Reset scroll distance. + setScrollDistance(0f); + // Sets new drawable. + updateIconDrawableByResourceId(modeIconResourceId); + mIconDrawable.setAlpha(0); + setVisibility(VISIBLE); + } + + /** + * By default, all drawables instances loaded from the same resource share a + * common state; if you modify the state of one instance, all the other + * instances will receive the same modification. So here we need to make sure + * we mutate the drawable loaded from resource. + * + * @param modeIconResourceId resource id of the icon drawable + */ + private void updateIconDrawableByResourceId(int modeIconResourceId) { + Drawable iconDrawable = getResources().getDrawable(modeIconResourceId); + if (iconDrawable == null) { + // Resource id not found + Log.e(TAG, "Invalid resource id for icon drawable. Setting icon drawable to null."); + setIconDrawable(null); + return; + } + // Mutate the drawable loaded from resource so modifying its states does + // not affect other drawable instances loaded from the same resource. + setIconDrawable(iconDrawable.mutate()); + } + + /** + * In order to make sure icon drawable is never set to null. Fall back to an + * empty drawable when icon needs to get reset. + * + * @param iconDrawable new drawable for icon. A value of <code>null</code> sets + * the icon drawable to the default drawable. + */ + private void setIconDrawable(Drawable iconDrawable) { + if (iconDrawable == null) { + mIconDrawable = mDefaultDrawable; + } else { + mIconDrawable = iconDrawable; + } + } +} + diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java index f1a5a9a4b..5736d7f8d 100644 --- a/src/com/android/camera/ui/PieRenderer.java +++ b/src/com/android/camera/ui/PieRenderer.java @@ -1064,7 +1064,7 @@ public class PieRenderer extends OverlayRenderer @Override public void onAnimationEnd(Animation animation) { // Keep the focus indicator for some time. - if (!mFocusCancelled) { + if (!mFocusCancelled && mOverlay != null) { mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT); } } diff --git a/src/com/android/camera/ui/RenderOverlay.java b/src/com/android/camera/ui/RenderOverlay.java index d82ce18b6..1e6b336af 100644 --- a/src/com/android/camera/ui/RenderOverlay.java +++ b/src/com/android/camera/ui/RenderOverlay.java @@ -87,8 +87,36 @@ public class RenderOverlay extends FrameLayout { return mClients.size(); } + // TODO: Remove this when refactor is done. This is only here temporarily + // to keep pie working before it's replaced with bottom bar. + public void directTouchEventsToPie(MotionEvent ev) { + PieRenderer pie = null; + for (int i = 0; i < mClients.size(); i++) { + if (mClients.get(i) instanceof PieRenderer) { + pie = (PieRenderer) mClients.get(i); + break; + } + } + if (pie == null) { + return; + } + if (pie.isOpen()) { + pie.onTouchEvent(ev); + } + } + + // TODO: Remove this when refactor is done. This is only here temporarily + // to keep pie working before it's replaced with bottom bar. + public void clear() { + mGestures = null; + while (mClients.size() > 0) { + remove(mClients.get(0)); + } + mTouchClients.clear(); + } + @Override - public boolean dispatchTouchEvent(MotionEvent m) { + public boolean onTouchEvent(MotionEvent m) { if (mGestures != null) { if (!mGestures.isEnabled()) return false; mGestures.dispatchTouch(m); @@ -134,6 +162,9 @@ public class RenderOverlay extends FrameLayout { @Override public boolean dispatchTouchEvent(MotionEvent evt) { + if (mGestures == null) { + return false; + } if (mTouchTarget != null) { return mTouchTarget.onTouchEvent(evt); |