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