PhotoViewFragment.java revision 075e0938e1413b6743f51baa5260cd9c8dea0711
1/* 2 * Copyright (C) 2011 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.ex.photo.fragments; 19 20import android.app.Activity; 21import android.app.Fragment; 22import android.app.LoaderManager; 23import android.app.LoaderManager.LoaderCallbacks; 24import android.content.Context; 25import android.content.Intent; 26import android.content.Loader; 27import android.database.Cursor; 28import android.graphics.Bitmap; 29import android.os.Bundle; 30import android.util.DisplayMetrics; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.View.OnClickListener; 34import android.view.ViewGroup; 35import android.view.WindowManager; 36import android.widget.ImageView; 37import android.widget.ProgressBar; 38import android.widget.TextView; 39 40import com.android.ex.photo.Intents; 41import com.android.ex.photo.PhotoViewActivity; 42import com.android.ex.photo.PhotoViewActivity.CursorChangedListener; 43import com.android.ex.photo.PhotoViewActivity.OnScreenListener; 44import com.android.ex.photo.R; 45import com.android.ex.photo.adapters.PhotoPagerAdapter; 46import com.android.ex.photo.loaders.PhotoBitmapLoader; 47import com.android.ex.photo.util.ImageUtils; 48import com.android.ex.photo.views.PhotoView; 49import com.android.ex.photo.views.ProgressBarWrapper; 50 51/** 52 * Displays a photo. 53 */ 54public class PhotoViewFragment extends Fragment implements 55 LoaderCallbacks<Bitmap>, OnClickListener, OnScreenListener, CursorChangedListener { 56 /** 57 * Interface for components that are internally scrollable left-to-right. 58 */ 59 public static interface HorizontallyScrollable { 60 /** 61 * Return {@code true} if the component needs to receive right-to-left 62 * touch movements. 63 * 64 * @param origX the raw x coordinate of the initial touch 65 * @param origY the raw y coordinate of the initial touch 66 */ 67 68 public boolean interceptMoveLeft(float origX, float origY); 69 70 /** 71 * Return {@code true} if the component needs to receive left-to-right 72 * touch movements. 73 * 74 * @param origX the raw x coordinate of the initial touch 75 * @param origY the raw y coordinate of the initial touch 76 */ 77 public boolean interceptMoveRight(float origX, float origY); 78 } 79 80 private final static String STATE_INTENT_KEY = 81 "com.android.mail.photo.fragments.PhotoViewFragment.INTENT"; 82 83 // Loader IDs 84 private final static int LOADER_ID_PHOTO = 1; 85 private final static int LOADER_ID_THUMBNAIL = 2; 86 87 /** The size of the photo */ 88 public static Integer sPhotoSize; 89 90 /** The URL of a photo to display */ 91 private String mResolvedPhotoUri; 92 private String mThumbnailUri; 93 /** The intent we were launched with */ 94 private Intent mIntent; 95 private PhotoViewActivity mCallback; 96 private PhotoPagerAdapter mAdapter; 97 98 private PhotoView mPhotoView; 99 private ImageView mPhotoPreviewImage; 100 private TextView mEmptyText; 101 private ImageView mRetryButton; 102 private ProgressBarWrapper mPhotoProgressBar; 103 104 private final int mPosition; 105 106 /** Whether or not the fragment should make the photo full-screen */ 107 private boolean mFullScreen; 108 109 /** Whether or not the progress bar is showing valid information about the progress stated */ 110 private boolean mProgressBarNeeded = true; 111 112 private View mPhotoPreviewAndProgress; 113 114 public PhotoViewFragment() { 115 mPosition = -1; 116 mProgressBarNeeded = true; 117 } 118 119 public PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter) { 120 mIntent = intent; 121 mPosition = position; 122 mAdapter = adapter; 123 mProgressBarNeeded = true; 124 } 125 126 @Override 127 public void onAttach(Activity activity) { 128 super.onAttach(activity); 129 mCallback = (PhotoViewActivity) activity; 130 if (mCallback == null) { 131 throw new IllegalArgumentException( 132 "Activity must be a derived class of PhotoViewActivity"); 133 } 134 135 if (sPhotoSize == null) { 136 final DisplayMetrics metrics = new DisplayMetrics(); 137 final WindowManager wm = 138 (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE); 139 final ImageUtils.ImageSize imageSize = ImageUtils.sUseImageSize; 140 wm.getDefaultDisplay().getMetrics(metrics); 141 switch (imageSize) { 142 case EXTRA_SMALL: { 143 // Use a photo that's 80% of the "small" size 144 sPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000; 145 break; 146 } 147 148 case SMALL: 149 case NORMAL: 150 default: { 151 sPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels); 152 break; 153 } 154 } 155 } 156 } 157 158 @Override 159 public void onDetach() { 160 mCallback = null; 161 super.onDetach(); 162 } 163 164 @Override 165 public void onCreate(Bundle savedInstanceState) { 166 super.onCreate(savedInstanceState); 167 168 if (savedInstanceState != null) { 169 final Bundle state = savedInstanceState.getBundle(STATE_INTENT_KEY); 170 if (state != null) { 171 mIntent = new Intent().putExtras(state); 172 } 173 } 174 175 if (mIntent != null) { 176 mResolvedPhotoUri = mIntent.getStringExtra(Intents.EXTRA_RESOLVED_PHOTO_URI); 177 mThumbnailUri = mIntent.getStringExtra(Intents.EXTRA_THUMBNAIL_URI); 178 } 179 } 180 181 @Override 182 public View onCreateView(LayoutInflater inflater, ViewGroup container, 183 Bundle savedInstanceState) { 184 final View view = inflater.inflate(R.layout.photo_fragment_view, container, false); 185 186 mPhotoView = (PhotoView) view.findViewById(R.id.photo_view); 187 mPhotoView.setMaxInitialScale(mIntent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1)); 188 mPhotoView.setOnClickListener(this); 189 mPhotoView.setFullScreen(mFullScreen, false); 190 mPhotoView.enableImageTransforms(true); 191 192 mPhotoPreviewAndProgress = view.findViewById(R.id.photo_preview); 193 mPhotoPreviewImage = (ImageView) view.findViewById(R.id.photo_preview_image); 194 final ProgressBar indeterminate = 195 (ProgressBar) view.findViewById(R.id.indeterminate_progress); 196 final ProgressBar determinate = 197 (ProgressBar) view.findViewById(R.id.determinate_progress); 198 mPhotoProgressBar = new ProgressBarWrapper(determinate, indeterminate, true); 199 mEmptyText = (TextView) view.findViewById(R.id.empty_text); 200 mRetryButton = (ImageView) view.findViewById(R.id.retry_button); 201 202 // Don't call until we've setup the entire view 203 setViewVisibility(); 204 205 return view; 206 } 207 208 @Override 209 public void onResume() { 210 mCallback.addScreenListener(this); 211 mCallback.addCursorListener(this); 212 213 getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this); 214 215 super.onResume(); 216 } 217 218 @Override 219 public void onPause() { 220 super.onPause(); 221 // Remove listeners 222 mCallback.removeCursorListener(this); 223 mCallback.removeScreenListener(this); 224 resetPhotoView(); 225 } 226 227 @Override 228 public void onDestroyView() { 229 // Clean up views and other components 230 if (mPhotoView != null) { 231 mPhotoView.clear(); 232 mPhotoView = null; 233 } 234 235 super.onDestroyView(); 236 } 237 238 @Override 239 public void onSaveInstanceState(Bundle outState) { 240 super.onSaveInstanceState(outState); 241 242 if (mIntent != null) { 243 outState.putParcelable(STATE_INTENT_KEY, mIntent.getExtras()); 244 } 245 } 246 247 @Override 248 public Loader<Bitmap> onCreateLoader(int id, Bundle args) { 249 switch (id) { 250 case LOADER_ID_PHOTO: 251 return new PhotoBitmapLoader(getActivity(), mResolvedPhotoUri); 252 case LOADER_ID_THUMBNAIL: 253 return new PhotoBitmapLoader(getActivity(), mThumbnailUri); 254 default: 255 return null; 256 } 257 } 258 259 @Override 260 public void onLoadFinished(Loader<Bitmap> loader, Bitmap data) { 261 // If we don't have a view, the fragment has been paused. We'll get the cursor again later. 262 if (getView() == null) { 263 return; 264 } 265 266 final int id = loader.getId(); 267 switch (id) { 268 case LOADER_ID_PHOTO: 269 if (data != null) { 270 bindPhoto(data); 271 mPhotoPreviewAndProgress.setVisibility(View.GONE); 272 mProgressBarNeeded = false; 273 } else { 274 // Received a null result for the full size image. Instead attempt to load the 275 // thumbnail 276 getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this); 277 } 278 break; 279 case LOADER_ID_THUMBNAIL: 280 mProgressBarNeeded = false; 281 if (isPhotoBound()) { 282 // There is need to do anything with the thumbnail image, as the full size 283 // image is being shown. 284 mPhotoPreviewAndProgress.setVisibility(View.GONE); 285 return; 286 } else if (data == null) { 287 // no preview, show default 288 mPhotoPreviewImage.setVisibility(View.VISIBLE); 289 mPhotoPreviewImage.setImageResource(R.drawable.default_image); 290 } else { 291 bindPhoto(data); 292 getLoaderManager().initLoader(LOADER_ID_PHOTO, null, this); 293 } 294 break; 295 default: 296 break; 297 } 298 299 if (mProgressBarNeeded == false) { 300 // Hide the progress bar as it isn't needed anymore. 301 mPhotoProgressBar.setVisibility(View.GONE); 302 } 303 304 mCallback.setViewActivated(); 305 setViewVisibility(); 306 } 307 308 /** 309 * Binds an image to the photo view. 310 */ 311 private void bindPhoto(Bitmap bitmap) { 312 if (mPhotoView != null) { 313 mPhotoView.bindPhoto(bitmap); 314 } 315 } 316 317 /** 318 * Resets the photo view to it's default state w/ no bound photo. 319 */ 320 private void resetPhotoView() { 321 if (mPhotoView != null) { 322 mPhotoView.bindPhoto(null); 323 } 324 } 325 326 @Override 327 public void onLoaderReset(Loader<Bitmap> loader) { 328 // Do nothing 329 } 330 331 @Override 332 public void onClick(View v) { 333 mCallback.toggleFullScreen(); 334 } 335 336 @Override 337 public void onFullScreenChanged(boolean fullScreen) { 338 setViewVisibility(); 339 } 340 341 @Override 342 public void onViewActivated() { 343 if (!mCallback.isFragmentActive(this)) { 344 // we're not in the foreground; reset our view 345 resetViews(); 346 } else { 347 mCallback.onFragmentVisible(this); 348 } 349 } 350 351 /** 352 * Reset the views to their default states 353 */ 354 public void resetViews() { 355 if (mPhotoView != null) { 356 mPhotoView.resetTransformations(); 357 } 358 } 359 360 @Override 361 public boolean onInterceptMoveLeft(float origX, float origY) { 362 if (!mCallback.isFragmentActive(this)) { 363 // we're not in the foreground; don't intercept any touches 364 return false; 365 } 366 367 return (mPhotoView != null && mPhotoView.interceptMoveLeft(origX, origY)); 368 } 369 370 @Override 371 public boolean onInterceptMoveRight(float origX, float origY) { 372 if (!mCallback.isFragmentActive(this)) { 373 // we're not in the foreground; don't intercept any touches 374 return false; 375 } 376 377 return (mPhotoView != null && mPhotoView.interceptMoveRight(origX, origY)); 378 } 379 380 /** 381 * Returns {@code true} if a photo has been bound. Otherwise, returns {@code false}. 382 */ 383 public boolean isPhotoBound() { 384 return (mPhotoView != null && mPhotoView.isPhotoBound()); 385 } 386 387 /** 388 * Sets view visibility depending upon whether or not we're in "full screen" mode. 389 */ 390 private void setViewVisibility() { 391 final boolean fullScreen = mCallback.isFragmentFullScreen(this); 392 final boolean hide = fullScreen; 393 394 setFullScreen(hide); 395 } 396 397 /** 398 * Sets full-screen mode for the views. 399 */ 400 public void setFullScreen(boolean fullScreen) { 401 mFullScreen = fullScreen; 402 } 403 404 @Override 405 public void onCursorChanged(Cursor cursor) { 406 if (cursor.moveToPosition(mPosition) && !isPhotoBound()) { 407 final LoaderManager manager = getLoaderManager(); 408 final Loader<Bitmap> fakeLoader = manager.getLoader(LOADER_ID_PHOTO); 409 if (fakeLoader == null) { 410 return; 411 } 412 413 final PhotoBitmapLoader loader = 414 (PhotoBitmapLoader) fakeLoader; 415 mResolvedPhotoUri = mAdapter.getPhotoUri(cursor); 416 loader.setPhotoUri(mResolvedPhotoUri); 417 loader.forceLoad(); 418 } 419 } 420 421 public ProgressBarWrapper getPhotoProgressBar() { 422 return mPhotoProgressBar; 423 } 424 425 public TextView getEmptyText() { 426 return mEmptyText; 427 } 428 429 public ImageView getRetryButton() { 430 return mRetryButton; 431 } 432 433 public boolean isProgressBarNeeded() { 434 return mProgressBarNeeded; 435 } 436} 437