Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2013 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.camera.ui; |
| 18 | |
Doris Liu | 48239f4 | 2013-03-04 22:19:10 -0800 | [diff] [blame] | 19 | import android.app.Activity; |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 20 | import android.content.Context; |
| 21 | import android.content.res.Configuration; |
| 22 | import android.util.AttributeSet; |
| 23 | import android.view.Gravity; |
| 24 | import android.view.View; |
Doris Liu | 48239f4 | 2013-03-04 22:19:10 -0800 | [diff] [blame] | 25 | import android.view.ViewGroup; |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 26 | import android.widget.FrameLayout; |
| 27 | |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 28 | import com.android.camera.debug.Log; |
Angus Kong | b50b5cb | 2013-08-09 14:55:20 -0700 | [diff] [blame] | 29 | import com.android.camera.util.CameraUtil; |
Doris Liu | 48239f4 | 2013-03-04 22:19:10 -0800 | [diff] [blame] | 30 | |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 31 | /* RotatableLayout rotates itself as well as all its children when orientation |
| 32 | * changes. Specifically, when going from portrait to landscape, camera |
| 33 | * controls move from the bottom of the screen to right side of the screen |
| 34 | * (i.e. counter clockwise). Similarly, when the screen changes to portrait, we |
| 35 | * need to move the controls from right side to the bottom of the screen, which |
| 36 | * is a clockwise rotation. |
| 37 | */ |
| 38 | |
| 39 | public class RotatableLayout extends FrameLayout { |
| 40 | |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 41 | private static final Log.Tag TAG = new Log.Tag("RotatableLayout"); |
Doris Liu | 8cecf83 | 2013-09-17 15:07:31 -0700 | [diff] [blame] | 42 | private static final int UNKOWN_ORIENTATION = -1; |
Doris Liu | bec9cf0 | 2013-04-20 11:30:35 -0700 | [diff] [blame] | 43 | // Initial orientation of the layout (ORIENTATION_PORTRAIT, or ORIENTATION_LANDSCAPE) |
| 44 | private int mInitialOrientation; |
Doris Liu | 8cecf83 | 2013-09-17 15:07:31 -0700 | [diff] [blame] | 45 | private int mPrevRotation = UNKOWN_ORIENTATION; |
Doris Liu | 3068c52 | 2013-10-13 14:46:57 -0700 | [diff] [blame] | 46 | private boolean mIsDefaultToPortrait = false; |
Doris Liu | 8cecf83 | 2013-09-17 15:07:31 -0700 | [diff] [blame] | 47 | |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 48 | public RotatableLayout(Context context, AttributeSet attrs, int defStyle) { |
| 49 | super(context, attrs, defStyle); |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 50 | init(); |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 51 | } |
| 52 | |
| 53 | public RotatableLayout(Context context, AttributeSet attrs) { |
| 54 | super(context, attrs); |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 55 | init(); |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 56 | } |
| 57 | |
| 58 | public RotatableLayout(Context context) { |
| 59 | super(context); |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 60 | init(); |
| 61 | } |
| 62 | |
| 63 | private void init() { |
Doris Liu | bec9cf0 | 2013-04-20 11:30:35 -0700 | [diff] [blame] | 64 | mInitialOrientation = getResources().getConfiguration().orientation; |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | @Override |
Doris Liu | bec9cf0 | 2013-04-20 11:30:35 -0700 | [diff] [blame] | 68 | public void onAttachedToWindow() { |
Doris Liu | 8cecf83 | 2013-09-17 15:07:31 -0700 | [diff] [blame] | 69 | // Before the first time this view is attached to window, device rotation |
| 70 | // will not trigger onConfigurationChanged callback. So in the first run |
| 71 | // we need to rotate the view if necessary. After that, onConfigurationChanged |
| 72 | // call will track all the subsequent device rotation. |
| 73 | if (mPrevRotation == UNKOWN_ORIENTATION) { |
Doris Liu | 0ba8eaa | 2013-10-16 12:51:02 -0700 | [diff] [blame] | 74 | mIsDefaultToPortrait = CameraUtil.isDefaultToPortrait((Activity) getContext()); |
Doris Liu | 3068c52 | 2013-10-13 14:46:57 -0700 | [diff] [blame] | 75 | if (mIsDefaultToPortrait) { |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 76 | // Natural orientation for tablet is landscape |
Doris Liu | 3068c52 | 2013-10-13 14:46:57 -0700 | [diff] [blame] | 77 | mPrevRotation = mInitialOrientation == Configuration.ORIENTATION_PORTRAIT ? |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 78 | 0 : 90; |
| 79 | } else { |
Doris Liu | 0ba8eaa | 2013-10-16 12:51:02 -0700 | [diff] [blame] | 80 | // When tablet orientation is 0 or 270 (i.e. getUnifiedOrientation |
| 81 | // = 0 or 90), we load the layout resource without any rotation. |
Doris Liu | 3068c52 | 2013-10-13 14:46:57 -0700 | [diff] [blame] | 82 | mPrevRotation = mInitialOrientation == Configuration.ORIENTATION_LANDSCAPE ? |
Doris Liu | 0ba8eaa | 2013-10-16 12:51:02 -0700 | [diff] [blame] | 83 | 0 : 270; |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 84 | } |
| 85 | |
Doris Liu | 8cecf83 | 2013-09-17 15:07:31 -0700 | [diff] [blame] | 86 | // check if there is any rotation before the view is attached to window |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 87 | rotateIfNeeded(); |
Doris Liu | 44ab147 | 2013-06-07 18:04:57 -0700 | [diff] [blame] | 88 | } |
| 89 | } |
| 90 | |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 91 | private void rotateIfNeeded() { |
Doris Liu | 8cecf83 | 2013-09-17 15:07:31 -0700 | [diff] [blame] | 92 | if (mPrevRotation == UNKOWN_ORIENTATION) { |
| 93 | return; |
| 94 | } |
Angus Kong | b50b5cb | 2013-08-09 14:55:20 -0700 | [diff] [blame] | 95 | int rotation = CameraUtil.getDisplayRotation((Activity) getContext()); |
Doris Liu | 44ab147 | 2013-06-07 18:04:57 -0700 | [diff] [blame] | 96 | int diff = (rotation - mPrevRotation + 360) % 360; |
| 97 | if ( diff == 0) { |
| 98 | // No rotation |
| 99 | return; |
| 100 | } else if (diff == 180) { |
| 101 | // 180-degree rotation |
Doris Liu | fa1c3a5 | 2013-05-03 14:33:57 -0700 | [diff] [blame] | 102 | mPrevRotation = rotation; |
Doris Liu | 44ab147 | 2013-06-07 18:04:57 -0700 | [diff] [blame] | 103 | flipChildren(); |
Doris Liu | bec9cf0 | 2013-04-20 11:30:35 -0700 | [diff] [blame] | 104 | return; |
| 105 | } |
Doris Liu | 44ab147 | 2013-06-07 18:04:57 -0700 | [diff] [blame] | 106 | // 90 or 270-degree rotation |
Doris Liu | bbee3bf | 2013-04-02 17:48:56 -0700 | [diff] [blame] | 107 | boolean clockwise = isClockWiseRotation(mPrevRotation, rotation); |
Doris Liu | bec9cf0 | 2013-04-20 11:30:35 -0700 | [diff] [blame] | 108 | mPrevRotation = rotation; |
Doris Liu | 44ab147 | 2013-06-07 18:04:57 -0700 | [diff] [blame] | 109 | rotateLayout(clockwise); |
Doris Liu | bec9cf0 | 2013-04-20 11:30:35 -0700 | [diff] [blame] | 110 | } |
| 111 | |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 112 | protected int getUnifiedRotation() { |
| 113 | // all the layout code assumes camera device orientation to be portrait |
| 114 | // adjust rotation for landscape |
| 115 | int rotation = CameraUtil.getDisplayRotation((Activity) getContext()); |
Doris Liu | 3068c52 | 2013-10-13 14:46:57 -0700 | [diff] [blame] | 116 | if (!mIsDefaultToPortrait) { |
Doris Liu | 86ad843 | 2013-10-11 19:01:30 -0700 | [diff] [blame] | 117 | return (rotation + 90) % 360; |
| 118 | } |
| 119 | return rotation; |
| 120 | } |
| 121 | |
| 122 | public void checkLayoutFlip() { |
| 123 | int currentRotation = CameraUtil.getDisplayRotation((Activity) getContext()); |
| 124 | if ((currentRotation - mPrevRotation + 360) % 360 == 180) { |
| 125 | mPrevRotation = currentRotation; |
| 126 | flipChildren(); |
| 127 | requestLayout(); |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | @Override |
| 132 | public void onWindowVisibilityChanged(int visibility) { |
| 133 | if (visibility == View.VISIBLE) { |
| 134 | // Make sure when coming back from onPause, the layout is rotated correctly |
| 135 | checkLayoutFlip(); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | @Override |
| 140 | public void onConfigurationChanged(Configuration config) { |
| 141 | super.onConfigurationChanged(config); |
| 142 | rotateIfNeeded(); |
| 143 | } |
| 144 | |
Doris Liu | bec9cf0 | 2013-04-20 11:30:35 -0700 | [diff] [blame] | 145 | protected void rotateLayout(boolean clockwise) { |
Doris Liu | 48239f4 | 2013-03-04 22:19:10 -0800 | [diff] [blame] | 146 | // Change the size of the layout |
| 147 | ViewGroup.LayoutParams lp = getLayoutParams(); |
| 148 | int width = lp.width; |
| 149 | int height = lp.height; |
| 150 | lp.height = width; |
| 151 | lp.width = height; |
| 152 | setLayoutParams(lp); |
Doris Liu | bbee3bf | 2013-04-02 17:48:56 -0700 | [diff] [blame] | 153 | |
Doris Liu | 48239f4 | 2013-03-04 22:19:10 -0800 | [diff] [blame] | 154 | // rotate all the children |
Doris Liu | bbee3bf | 2013-04-02 17:48:56 -0700 | [diff] [blame] | 155 | rotateChildren(clockwise); |
| 156 | } |
| 157 | |
| 158 | protected void rotateChildren(boolean clockwise) { |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 159 | int childCount = getChildCount(); |
| 160 | for (int i = 0; i < childCount; i++) { |
| 161 | View child = getChildAt(i); |
| 162 | rotate(child, clockwise); |
| 163 | } |
Doris Liu | bbee3bf | 2013-04-02 17:48:56 -0700 | [diff] [blame] | 164 | } |
| 165 | |
| 166 | protected void flipChildren() { |
Doris Liu | bbee3bf | 2013-04-02 17:48:56 -0700 | [diff] [blame] | 167 | int childCount = getChildCount(); |
| 168 | for (int i = 0; i < childCount; i++) { |
| 169 | View child = getChildAt(i); |
| 170 | flip(child); |
| 171 | } |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 172 | } |
| 173 | |
Doris Liu | 48239f4 | 2013-03-04 22:19:10 -0800 | [diff] [blame] | 174 | public static boolean isClockWiseRotation(int prevRotation, int currentRotation) { |
| 175 | if (prevRotation == (currentRotation + 90) % 360) { |
| 176 | return true; |
| 177 | } |
| 178 | return false; |
| 179 | } |
| 180 | |
Doris Liu | 6a0de79 | 2013-02-26 10:54:25 -0800 | [diff] [blame] | 181 | public static void rotate(View view, boolean isClockwise) { |
| 182 | if (isClockwise) { |
| 183 | rotateClockwise(view); |
| 184 | } else { |
| 185 | rotateCounterClockwise(view); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | private static boolean contains(int value, int mask) { |
| 190 | return (value & mask) == mask; |
| 191 | } |
| 192 | |
| 193 | public static void rotateClockwise(View view) { |
| 194 | if (view == null) return; |
| 195 | LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| 196 | int gravity = lp.gravity; |
| 197 | int ngravity = 0; |
| 198 | // rotate gravity |
| 199 | if (contains(gravity, Gravity.LEFT)) { |
| 200 | ngravity |= Gravity.TOP; |
| 201 | } |
| 202 | if (contains(gravity, Gravity.RIGHT)) { |
| 203 | ngravity |= Gravity.BOTTOM; |
| 204 | } |
| 205 | if (contains(gravity, Gravity.TOP)) { |
| 206 | ngravity |= Gravity.RIGHT; |
| 207 | } |
| 208 | if (contains(gravity, Gravity.BOTTOM)) { |
| 209 | ngravity |= Gravity.LEFT; |
| 210 | } |
| 211 | if (contains(gravity, Gravity.CENTER)) { |
| 212 | ngravity |= Gravity.CENTER; |
| 213 | } |
| 214 | if (contains(gravity, Gravity.CENTER_HORIZONTAL)) { |
| 215 | ngravity |= Gravity.CENTER_VERTICAL; |
| 216 | } |
| 217 | if (contains(gravity, Gravity.CENTER_VERTICAL)) { |
| 218 | ngravity |= Gravity.CENTER_HORIZONTAL; |
| 219 | } |
| 220 | lp.gravity = ngravity; |
| 221 | int ml = lp.leftMargin; |
| 222 | int mr = lp.rightMargin; |
| 223 | int mt = lp.topMargin; |
| 224 | int mb = lp.bottomMargin; |
| 225 | lp.leftMargin = mb; |
| 226 | lp.rightMargin = mt; |
| 227 | lp.topMargin = ml; |
| 228 | lp.bottomMargin = mr; |
| 229 | int width = lp.width; |
| 230 | int height = lp.height; |
| 231 | lp.width = height; |
| 232 | lp.height = width; |
| 233 | view.setLayoutParams(lp); |
| 234 | } |
| 235 | |
| 236 | public static void rotateCounterClockwise(View view) { |
| 237 | if (view == null) return; |
| 238 | LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| 239 | int gravity = lp.gravity; |
| 240 | int ngravity = 0; |
| 241 | // change gravity |
| 242 | if (contains(gravity, Gravity.RIGHT)) { |
| 243 | ngravity |= Gravity.TOP; |
| 244 | } |
| 245 | if (contains(gravity, Gravity.LEFT)) { |
| 246 | ngravity |= Gravity.BOTTOM; |
| 247 | } |
| 248 | if (contains(gravity, Gravity.TOP)) { |
| 249 | ngravity |= Gravity.LEFT; |
| 250 | } |
| 251 | if (contains(gravity, Gravity.BOTTOM)) { |
| 252 | ngravity |= Gravity.RIGHT; |
| 253 | } |
| 254 | if (contains(gravity, Gravity.CENTER)) { |
| 255 | ngravity |= Gravity.CENTER; |
| 256 | } |
| 257 | if (contains(gravity, Gravity.CENTER_HORIZONTAL)) { |
| 258 | ngravity |= Gravity.CENTER_VERTICAL; |
| 259 | } |
| 260 | if (contains(gravity, Gravity.CENTER_VERTICAL)) { |
| 261 | ngravity |= Gravity.CENTER_HORIZONTAL; |
| 262 | } |
| 263 | lp.gravity = ngravity; |
| 264 | int ml = lp.leftMargin; |
| 265 | int mr = lp.rightMargin; |
| 266 | int mt = lp.topMargin; |
| 267 | int mb = lp.bottomMargin; |
| 268 | lp.leftMargin = mt; |
| 269 | lp.rightMargin = mb; |
| 270 | lp.topMargin = mr; |
| 271 | lp.bottomMargin = ml; |
| 272 | int width = lp.width; |
| 273 | int height = lp.height; |
| 274 | lp.width = height; |
| 275 | lp.height = width; |
| 276 | view.setLayoutParams(lp); |
| 277 | } |
Doris Liu | bbee3bf | 2013-04-02 17:48:56 -0700 | [diff] [blame] | 278 | |
| 279 | // Rotate a given view 180 degrees |
| 280 | public static void flip(View view) { |
| 281 | rotateClockwise(view); |
| 282 | rotateClockwise(view); |
| 283 | } |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 284 | } |