PhotoViewFragment.java revision 1abd4654c2eeacc7d854a438a9c72d7239278bea
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.LoaderCallbacks;
23import android.content.Context;
24import android.content.Intent;
25import android.content.Loader;
26import android.database.Cursor;
27import android.graphics.Bitmap;
28import android.os.Bundle;
29import android.app.LoaderManager;
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;
37
38import com.android.ex.photo.Intents;
39import com.android.ex.photo.PhotoViewActivity;
40import com.android.ex.photo.PhotoViewActivity.CursorChangedListener;
41import com.android.ex.photo.PhotoViewActivity.OnScreenListener;
42import com.android.ex.photo.R;
43import com.android.ex.photo.adapters.PhotoPagerAdapter;
44import com.android.ex.photo.loaders.PhotoBitmapLoader;
45import com.android.ex.photo.util.ImageUtils;
46import com.android.ex.photo.views.PhotoView;
47
48/**
49 * Displays a photo.
50 */
51public class PhotoViewFragment extends Fragment implements
52        LoaderCallbacks<Bitmap>, OnClickListener, OnScreenListener, CursorChangedListener {
53    /**
54     * Interface for components that are internally scrollable left-to-right.
55     */
56    public static interface HorizontallyScrollable {
57        /**
58         * Return {@code true} if the component needs to receive right-to-left
59         * touch movements.
60         *
61         * @param origX the raw x coordinate of the initial touch
62         * @param origY the raw y coordinate of the initial touch
63         */
64
65        public boolean interceptMoveLeft(float origX, float origY);
66
67        /**
68         * Return {@code true} if the component needs to receive left-to-right
69         * touch movements.
70         *
71         * @param origX the raw x coordinate of the initial touch
72         * @param origY the raw y coordinate of the initial touch
73         */
74        public boolean interceptMoveRight(float origX, float origY);
75    }
76
77    private final static String STATE_INTENT_KEY =
78            "com.android.mail.photo.fragments.PhotoViewFragment.INTENT";
79
80    // Loader IDs
81    private final static int LOADER_ID_PHOTO = 1;
82    private final static int LOADER_ID_THUMBNAIL = 2;
83
84    /** The size of the photo */
85    public static Integer sPhotoSize;
86
87    /** The URL of a photo to display */
88    private String mResolvedPhotoUri;
89    private String mThumbnailUri;
90    /** The intent we were launched with */
91    private Intent mIntent;
92    private PhotoViewActivity mCallback;
93    private PhotoPagerAdapter mAdapter;
94
95    private PhotoView mPhotoView;
96    private ImageView mPhotoPreview;
97    private final int mPosition;
98
99    /** Whether or not the fragment should make the photo full-screen */
100    private boolean mFullScreen;
101
102    private boolean mShowingThumbnail;
103
104    public PhotoViewFragment() {
105        mPosition = -1;
106    }
107
108    public PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter) {
109        mIntent = intent;
110        mPosition = position;
111        mAdapter = adapter;
112        mShowingThumbnail = false;
113    }
114
115    @Override
116    public void onAttach(Activity activity) {
117        super.onAttach(activity);
118        mCallback = (PhotoViewActivity) activity;
119        if (mCallback == null) {
120            throw new IllegalArgumentException("Activity must be a derived class of PhotoViewActivity");
121        }
122
123        if (sPhotoSize == null) {
124            final DisplayMetrics metrics = new DisplayMetrics();
125            final WindowManager wm =
126                    (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
127            final ImageUtils.ImageSize imageSize = ImageUtils.sUseImageSize;
128            wm.getDefaultDisplay().getMetrics(metrics);
129            switch (imageSize) {
130                case EXTRA_SMALL: {
131                    // Use a photo that's 80% of the "small" size
132                    sPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000;
133                    break;
134                }
135
136                case SMALL:
137                case NORMAL:
138                default: {
139                    sPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels);
140                    break;
141                }
142            }
143        }
144    }
145
146    @Override
147    public void onDetach() {
148        mCallback = null;
149        super.onDetach();
150    }
151
152    @Override
153    public void onCreate(Bundle savedInstanceState) {
154        super.onCreate(savedInstanceState);
155
156        if (savedInstanceState != null) {
157            mIntent = new Intent().putExtras(savedInstanceState.getBundle(STATE_INTENT_KEY));
158        }
159
160        mResolvedPhotoUri = mIntent.getStringExtra(Intents.EXTRA_RESOLVED_PHOTO_URI);
161        mThumbnailUri = mIntent.getStringExtra(Intents.EXTRA_THUMBNAIL_URI);
162    }
163
164    @Override
165    public View onCreateView(LayoutInflater inflater, ViewGroup container,
166            Bundle savedInstanceState) {
167        final View view = inflater.inflate(R.layout.photo_fragment_view, container, false);
168
169        mPhotoView = (PhotoView) view.findViewById(R.id.photo_view);
170        mPhotoView.setOnClickListener(this);
171        mPhotoView.setFullScreen(mFullScreen, false);
172        mPhotoView.enableImageTransforms(true);
173
174        mPhotoPreview = (ImageView) view.findViewById(R.id.photo_preview_image);
175
176        // Don't call until we've setup the entire view
177        setViewVisibility();
178
179        return view;
180    }
181
182    @Override
183    public void onResume() {
184        mCallback.addScreenListener(this);
185        mCallback.addCursorListener(this);
186
187        getLoaderManager().initLoader(LOADER_ID_PHOTO, null, this);
188
189        super.onResume();
190    }
191
192    @Override
193    public void onPause() {
194        super.onPause();
195        // Remove listeners
196        mCallback.removeCursorListener(this);
197        mCallback.removeScreenListener(this);
198        resetPhotoView();
199    }
200
201    @Override
202    public void onDestroyView() {
203        // Clean up views and other components
204        if (mPhotoView != null) {
205            mPhotoView.clear();
206            mPhotoView = null;
207        }
208
209        super.onDestroyView();
210    }
211
212    @Override
213    public void onSaveInstanceState(Bundle outState) {
214        super.onSaveInstanceState(outState);
215
216        if (mIntent != null) {
217            outState.putParcelable(STATE_INTENT_KEY, mIntent.getExtras());
218        }
219    }
220
221    @Override
222    public Loader<Bitmap> onCreateLoader(int id, Bundle args) {
223        switch (id) {
224            case LOADER_ID_PHOTO:
225                return new PhotoBitmapLoader(getActivity(), mResolvedPhotoUri);
226            case LOADER_ID_THUMBNAIL:
227                return new PhotoBitmapLoader(getActivity(), mThumbnailUri);
228            default:
229                return null;
230        }
231    }
232
233    @Override
234    public void onLoadFinished(Loader<Bitmap> loader, Bitmap data) {
235        // If we don't have a view, the fragment has been paused. We'll get the cursor again later.
236        if (getView() == null) {
237            return;
238        }
239
240        final int id = loader.getId();
241        switch (id) {
242            case LOADER_ID_PHOTO:
243                if (data == null) {
244                    getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this);
245                    return;
246                }
247
248                mShowingThumbnail = false;
249                bindPhoto(data);
250                mCallback.setViewActivated();
251                setViewVisibility();
252                mPhotoPreview.setVisibility(View.GONE);
253                break;
254            case LOADER_ID_THUMBNAIL:
255                if (isPhotoBound()) {
256                    return;
257                }
258
259                if (data == null) {
260                    // no preview, show default
261                    mPhotoPreview.setVisibility(View.VISIBLE);
262                    mPhotoPreview.setImageResource(R.drawable.default_image);
263                    return;
264                }
265
266                mShowingThumbnail = true;
267                mPhotoPreview.setVisibility(View.VISIBLE);
268                mPhotoPreview.setImageBitmap(data);
269                mCallback.setViewActivated();
270                setViewVisibility();
271                break;
272            default:
273                break;
274        }
275    }
276
277    /**
278     * Binds an image to the photo view.
279     */
280    private void bindPhoto(Bitmap bitmap) {
281        if (mPhotoView != null) {
282            mPhotoView.bindPhoto(bitmap);
283        }
284    }
285
286    /**
287     * Resets the photo view to it's default state w/ no bound photo.
288     */
289    private void resetPhotoView() {
290        if (mPhotoView != null) {
291            mPhotoView.bindPhoto(null);
292        }
293    }
294
295    @Override
296    public void onLoaderReset(Loader<Bitmap> loader) {
297        // Do nothing
298    }
299
300    @Override
301    public void onClick(View v) {
302        mCallback.toggleFullScreen();
303    }
304
305    @Override
306    public void onFullScreenChanged(boolean fullScreen) {
307        setViewVisibility();
308    }
309
310    @Override
311    public void onViewActivated() {
312        if (!mCallback.isFragmentActive(this)) {
313            // we're not in the foreground; reset our view
314            resetViews();
315        } else {
316            mCallback.onFragmentVisible(this);
317        }
318    }
319
320    /**
321     * Reset the views to their default states
322     */
323    public void resetViews() {
324        if (mPhotoView != null) {
325            mPhotoView.resetTransformations();
326        }
327    }
328
329    @Override
330    public boolean onInterceptMoveLeft(float origX, float origY) {
331        if (!mCallback.isFragmentActive(this)) {
332            // we're not in the foreground; don't intercept any touches
333            return false;
334        }
335
336        return (mPhotoView != null && mPhotoView.interceptMoveLeft(origX, origY));
337    }
338
339    @Override
340    public boolean onInterceptMoveRight(float origX, float origY) {
341        if (!mCallback.isFragmentActive(this)) {
342            // we're not in the foreground; don't intercept any touches
343            return false;
344        }
345
346        return (mPhotoView != null && mPhotoView.interceptMoveRight(origX, origY));
347    }
348
349    /**
350     * Returns {@code true} if a photo has been bound. Otherwise, returns {@code false}.
351     */
352    public boolean isPhotoBound() {
353        return (mPhotoView != null && mPhotoView.isPhotoBound() && !mShowingThumbnail);
354    }
355
356    /**
357     * Sets view visibility depending upon whether or not we're in "full screen" mode.
358     */
359    private void setViewVisibility() {
360        final boolean fullScreen = mCallback.isFragmentFullScreen(this);
361        final boolean hide = fullScreen;
362
363        setFullScreen(hide);
364    }
365
366    /**
367     * Sets full-screen mode for the views.
368     */
369    public void setFullScreen(boolean fullScreen) {
370        mFullScreen = fullScreen;
371    }
372
373    @Override
374    public void onCursorChanged(Cursor cursor) {
375        if (cursor.moveToPosition(mPosition) && !isPhotoBound()) {
376            final LoaderManager manager = getLoaderManager();
377            final Loader<Bitmap> fakeLoader = manager.getLoader(LOADER_ID_PHOTO);
378            if (fakeLoader == null) {
379                return;
380            }
381
382            final PhotoBitmapLoader loader =
383                    (PhotoBitmapLoader) fakeLoader;
384            mResolvedPhotoUri = mAdapter.getPhotoUri(cursor);
385            loader.setPhotoUri(mResolvedPhotoUri);
386            loader.forceLoad();
387        }
388    }
389}
390