1 /* 2 * Copyright (C) 2010 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.videoeditor.widgets; 18 19 import com.android.videoeditor.service.ApiService; 20 import com.android.videoeditor.service.MovieTransition; 21 import com.android.videoeditor.R; 22 23 import android.content.Context; 24 import android.content.res.Resources; 25 import android.graphics.Bitmap; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Paint; 29 import android.graphics.Rect; 30 import android.util.AttributeSet; 31 import android.util.DisplayMetrics; 32 import android.util.Log; 33 import android.view.Display; 34 import android.view.GestureDetector; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.WindowManager; 38 import android.widget.ImageView; 39 40 /** 41 * Transition view. This class assumes transition is always put on a MediaLinearLayout and is 42 * wrapped with a timeline scroll view. 43 */ 44 public class TransitionView extends ImageView { 45 // Logging 46 private static final String TAG = "TransitionView"; 47 48 // Instance variables 49 private final GestureDetector mSimpleGestureDetector; 50 private final ScrollViewListener mScrollListener; 51 private final Rect mGeneratingTransitionProgressDestRect; 52 private final Paint mSeparatorPaint; 53 private boolean mIsScrolling; 54 // Convenient handle to the parent timeline scroll view. 55 private TimelineHorizontalScrollView mScrollView; 56 // Convenient handle to the parent timeline linear layout. 57 private MediaLinearLayout mTimeline; 58 private int mScrollX; 59 private int mScreenWidth; 60 private String mProjectPath; 61 private Bitmap[] mBitmaps; 62 private ItemSimpleGestureListener mGestureListener; 63 private int mGeneratingTransitionProgress; 64 private boolean mIsPlaying; 65 66 public TransitionView(Context context, AttributeSet attrs, int defStyle) { 67 super(context, attrs, defStyle); 68 69 // Setup the gesture listener 70 mSimpleGestureDetector = new GestureDetector(context, 71 new GestureDetector.SimpleOnGestureListener() { 72 @Override 73 public boolean onSingleTapConfirmed(MotionEvent e) { 74 if (mGestureListener != null) { 75 return mGestureListener.onSingleTapConfirmed(TransitionView.this, -1, 76 e); 77 } else { 78 return false; 79 } 80 } 81 82 @Override 83 public void onLongPress(MotionEvent e) { 84 if (mGestureListener != null) { 85 mGestureListener.onLongPress(TransitionView.this, e); 86 } 87 } 88 }); 89 90 mScrollListener = new ScrollViewListener() { 91 @Override 92 public void onScrollBegin(View view, int scrollX, int scrollY, boolean appScroll) { 93 mIsScrolling = true; 94 } 95 96 @Override 97 public void onScrollProgress(View view, int scrollX, int scrollY, boolean appScroll) { 98 invalidate(); 99 } 100 101 @Override 102 public void onScrollEnd(View view, int scrollX, int scrollY, boolean appScroll) { 103 mIsScrolling = false; 104 mScrollX = scrollX; 105 106 if (requestThumbnails()) { 107 invalidate(); 108 } 109 } 110 }; 111 112 final Resources resources = getResources(); 113 // Prepare the bitmap rectangles 114 final ProgressBar progressBar = ProgressBar.getProgressBar(context); 115 final int layoutHeight = (int)(resources.getDimension(R.dimen.media_layout_height) - 116 resources.getDimension(R.dimen.media_layout_padding) - 117 (2 * resources.getDimension(R.dimen.timelime_transition_vertical_inset))); 118 mGeneratingTransitionProgressDestRect = new Rect(getPaddingLeft(), 119 layoutHeight - progressBar.getHeight() - getPaddingBottom(), 0, 120 layoutHeight - getPaddingBottom()); 121 122 // Initialize the progress value 123 mGeneratingTransitionProgress = -1; 124 125 // Get the screen width 126 final Display display = ((WindowManager) getContext().getSystemService( 127 Context.WINDOW_SERVICE)).getDefaultDisplay(); 128 final DisplayMetrics metrics = new DisplayMetrics(); 129 display.getMetrics(metrics); 130 mScreenWidth = metrics.widthPixels; 131 132 // Prepare the separator paint 133 mSeparatorPaint = new Paint(); 134 mSeparatorPaint.setColor(Color.BLACK); 135 mSeparatorPaint.setStrokeWidth(2); 136 } 137 138 public TransitionView(Context context, AttributeSet attrs) { 139 this(context, attrs, 0); 140 } 141 142 public TransitionView(Context context) { 143 this(context, null, 0); 144 } 145 146 @Override 147 protected void onAttachedToWindow() { 148 // Add the horizontal scroll view listener 149 mScrollView = (TimelineHorizontalScrollView) getRootView().findViewById( 150 R.id.timeline_scroller); 151 mScrollView.addScrollListener(mScrollListener); 152 mScrollX = mScrollView.getScrollX(); 153 154 mTimeline = (MediaLinearLayout) getRootView().findViewById(R.id.timeline_media); 155 } 156 157 @Override 158 protected void onDetachedFromWindow() { 159 // Remove the horizontal scroll listener 160 mScrollView.removeScrollListener(mScrollListener); 161 162 // Release the current set of bitmaps 163 if (mBitmaps != null) { 164 for (int i = 0; i < mBitmaps.length; i++) { 165 if (mBitmaps[i] != null) { 166 mBitmaps[i].recycle(); 167 } 168 } 169 170 mBitmaps = null; 171 } 172 } 173 174 /** 175 * @param projectPath The project path 176 */ 177 public void setProjectPath(String projectPath) { 178 mProjectPath = projectPath; 179 } 180 181 /** 182 * @param listener The gesture listener 183 */ 184 public void setGestureListener(ItemSimpleGestureListener listener) { 185 mGestureListener = listener; 186 } 187 188 /** 189 * Resets the transition generation progress. 190 */ 191 public void resetGeneratingTransitionProgress() { 192 setGeneratingTransitionProgress(-1); 193 } 194 195 /** 196 * Sets the transition generation progress. 197 */ 198 public void setGeneratingTransitionProgress(int progress) { 199 if (progress == 100) { 200 // Request the preview bitmaps 201 requestThumbnails(); 202 mGeneratingTransitionProgress = -1; 203 } else { 204 mGeneratingTransitionProgress = progress; 205 } 206 207 invalidate(); 208 } 209 210 /** 211 * @return true if generation is in progress 212 */ 213 public boolean isGeneratingTransition() { 214 return (mGeneratingTransitionProgress >= 0); 215 } 216 217 /** 218 * A view enters or exits the playback mode 219 * 220 * @param playback true if playback is in progress 221 */ 222 public void setPlaybackMode(boolean playback) { 223 mIsPlaying = playback; 224 } 225 226 /** 227 * @param bitmaps The bitmaps array 228 * 229 * @return true if the bitmaps were used 230 */ 231 public boolean setBitmaps(Bitmap[] bitmaps) { 232 if (mGeneratingTransitionProgress >= 0) { 233 return false; 234 } 235 236 // Release the current set of bitmaps 237 if (mBitmaps != null) { 238 for (int i = 0; i < mBitmaps.length; i++) { 239 if (mBitmaps[i] != null) { 240 mBitmaps[i].recycle(); 241 } 242 } 243 } 244 245 mBitmaps = bitmaps; 246 invalidate(); 247 248 return true; 249 } 250 251 @Override 252 protected void onDraw(Canvas canvas) { 253 super.onDraw(canvas); 254 255 // If the view is too small, don't draw anything. 256 if (getWidth() <= getPaddingLeft() + getPaddingRight()) { 257 return; 258 } 259 260 if (mGeneratingTransitionProgress >= 0) { 261 ProgressBar.getProgressBar(getContext()).draw(canvas, mGeneratingTransitionProgress, 262 mGeneratingTransitionProgressDestRect, getPaddingLeft(), 263 getWidth() - getPaddingRight()); 264 } else if (mBitmaps != null) { 265 final int halfWidth = getWidth() / 2; 266 // Draw the bitmaps 267 // Draw the left side of the transition 268 canvas.save(); 269 canvas.clipRect(getPaddingLeft(), getPaddingTop(), halfWidth, 270 getHeight() - getPaddingBottom()); 271 272 if (mBitmaps[0] != null) { 273 canvas.drawBitmap(mBitmaps[0], getPaddingLeft(), getPaddingTop(), null); 274 } else { 275 canvas.drawColor(Color.BLACK); 276 } 277 canvas.restore(); 278 279 // Draw the right side of the transition 280 canvas.save(); 281 canvas.clipRect(halfWidth, getPaddingTop(), 282 getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); 283 if (mBitmaps[1] != null) { 284 canvas.drawBitmap(mBitmaps[1], 285 getWidth() - getPaddingRight() - mBitmaps[1].getWidth(), getPaddingTop(), 286 null); 287 } else { 288 canvas.drawColor(Color.BLACK); 289 } 290 canvas.restore(); 291 292 canvas.drawLine(halfWidth, getPaddingTop(), halfWidth, 293 getHeight() - getPaddingBottom(), mSeparatorPaint); 294 295 // Dim myself if some view on the timeline is selected but not me 296 // by drawing a transparent black overlay. 297 if (!isSelected() && mTimeline.hasItemSelected()) { 298 final Paint paint = new Paint(); 299 paint.setColor(Color.BLACK); 300 paint.setAlpha(192); 301 canvas.drawPaint(paint); 302 } 303 } else if (mIsPlaying) { // Playing 304 } else if (mIsScrolling) { // Scrolling 305 } else { // Not scrolling and not playing 306 requestThumbnails(); 307 } 308 } 309 310 @Override 311 public boolean onTouchEvent(MotionEvent ev) { 312 // Let the gesture detector inspect all events. 313 mSimpleGestureDetector.onTouchEvent(ev); 314 return super.onTouchEvent(ev); 315 } 316 317 /** 318 * Request thumbnails if necessary 319 * 320 * @return true if the bitmaps already exist 321 */ 322 private boolean requestThumbnails() { 323 // Check if we already have the bitmaps 324 if (mBitmaps != null) { 325 return true; 326 } 327 328 // Do not request thumbnails during playback 329 if (mIsScrolling) { 330 return false; 331 } 332 333 final MovieTransition transition = (MovieTransition)getTag(); 334 // Check if we already requested the thumbnails 335 if (ApiService.isTransitionThumbnailsPending(mProjectPath, transition.getId())) { 336 return false; 337 } 338 339 final int start = getLeft() + getPaddingLeft() - mScrollX; 340 final int end = getRight() - getPaddingRight() - mScrollX; 341 342 if (start >= mScreenWidth || end < 0 || start == end) { 343 if (Log.isLoggable(TAG, Log.VERBOSE)) { 344 Log.v(TAG, "Transition view is off screen: " + transition.getId() + 345 ", from: " + start + " to " + end); 346 } 347 348 // Release the current set of bitmaps 349 if (mBitmaps != null) { 350 for (int i = 0; i < mBitmaps.length; i++) { 351 if (mBitmaps[i] != null) { 352 mBitmaps[i].recycle(); 353 } 354 } 355 356 mBitmaps = null; 357 } 358 359 return false; 360 } 361 362 // Compute the thumbnail width 363 final int thumbnailHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 364 // Request the thumbnails 365 ApiService.getTransitionThumbnails(getContext(), mProjectPath, transition.getId(), 366 thumbnailHeight); 367 368 return false; 369 } 370 } 371