com.example.android.threadsample.PhotoThumbnailFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.example.android.threadsample.PhotoThumbnailFragment.java

Source

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.threadsample;

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.CursorAdapter;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.GridView;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.RejectedExecutionException;

/**
 * PhotoThumbnailFragment displays a GridView of picture thumbnails downloaded from Picasa
 */
public class PhotoThumbnailFragment extends Fragment
        implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener {

    private static final String STATE_IS_HIDDEN = "com.example.android.threadsample.STATE_IS_HIDDEN";

    // The width of each column in the grid
    private int mColumnWidth;

    // A Drawable for a grid cell that's empty
    private Drawable mEmptyDrawable;

    // The GridView for displaying thumbnails
    private GridView mGridView;

    // Denotes if the GridView has been loaded
    private boolean mIsLoaded;

    // Intent for starting the IntentService that downloads the Picasa featured picture RSS feed
    private Intent mServiceIntent;

    // An adapter between a Cursor and the Fragment's GridView
    private GridViewAdapter mAdapter;

    // The URL of the Picasa featured picture RSS feed, in String format
    private static final String PICASA_RSS_URL = "http://picasaweb.google.com/data/feed/base/featured?"
            + "alt=rss&kind=photo&access=public&slabel=featured&hl=en_US&imgmax=1600";

    private static final String[] PROJECTION = { DataProviderContract._ID,
            DataProviderContract.IMAGE_THUMBURL_COLUMN, DataProviderContract.IMAGE_URL_COLUMN };

    // Constants that define the order of columns in the returned cursor
    private static final int IMAGE_THUMBURL_CURSOR_INDEX = 1;
    private static final int IMAGE_URL_CURSOR_INDEX = 2;

    // Identifies a particular Loader being used in this component
    private static final int URL_LOADER = 0;

    /*
     * This callback is invoked when the framework is starting or re-starting the Loader. It
     * returns a CursorLoader object containing the desired query
     */
    @Override
    public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle) {
        /*
         * Takes action based on the ID of the Loader that's being created
         */
        switch (loaderID) {
        case URL_LOADER:
            // Returns a new CursorLoader
            return new CursorLoader(getActivity(), // Context
                    DataProviderContract.PICTUREURL_TABLE_CONTENTURI, // Table to query
                    PROJECTION, // Projection to return
                    null, // No selection clause
                    null, // No selection arguments
                    null // Default sort order
            );
        default:
            // An invalid id was passed in
            return null;

        }

    }

    /*
     * This callback is invoked when the the Fragment's View is being loaded. It sets up the View.
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {

        // Always call the super method first
        super.onCreateView(inflater, viewGroup, bundle);

        /*
         * Inflates the View from the gridlist layout file, using the layout parameters in
         * "viewGroup"
         */
        View localView = inflater.inflate(R.layout.gridlist, viewGroup, false);

        // Sets the View's data adapter to be a new GridViewAdapter
        mAdapter = new GridViewAdapter(getActivity());

        // Gets a handle to the GridView in the layout
        mGridView = ((GridView) localView.findViewById(android.R.id.list));

        // Instantiates a DisplayMetrics object
        DisplayMetrics localDisplayMetrics = new DisplayMetrics();

        // Gets the current display metrics from the current Window
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(localDisplayMetrics);

        /*
         * Gets the dp value from the thumbSize resource as an integer in dps. The value can
         * be adjusted for specific display sizes, etc. in the dimens.xml file for a particular
         * values-<qualifier> directory
         */
        int pixelSize = getResources().getDimensionPixelSize(R.dimen.thumbSize);

        /*
         * Calculates a width scale factor from the pixel width of the current display and the
         * desired pixel size
         */
        int widthScale = localDisplayMetrics.widthPixels / pixelSize;

        // Calculates the grid column width
        mColumnWidth = (localDisplayMetrics.widthPixels / widthScale);

        // Sets the GridView's column width
        mGridView.setColumnWidth(mColumnWidth);

        // Starts by setting the GridView to have no columns
        mGridView.setNumColumns(-1);

        // Sets the GridView's data adapter
        mGridView.setAdapter(mAdapter);

        /*
         * Sets the GridView's click listener to be this class. As a result, when users click the
         * GridView, PhotoThumbnailFragment.onClick() is invoked.
         */
        mGridView.setOnItemClickListener(this);

        /*
         * Sets the "empty" View for the layout. If there's nothing to show, a ProgressBar
         * is displayed.
         */
        mGridView.setEmptyView(localView.findViewById(R.id.progressRoot));

        // Sets a dark background to show when no image is queued to be downloaded
        mEmptyDrawable = getResources().getDrawable(R.drawable.imagenotqueued);

        // Initializes the CursorLoader
        getLoaderManager().initLoader(URL_LOADER, null, this);

        /*
         * Creates a new Intent to send to the download IntentService. The Intent contains the
         * URL of the Picasa feature picture RSS feed
         */
        mServiceIntent = new Intent(getActivity(), RSSPullService.class).setData(Uri.parse(PICASA_RSS_URL));

        // If there's no pre-existing state for this Fragment
        if (bundle == null) {
            // If the data wasn't previously loaded
            if (!this.mIsLoaded) {
                // Starts the IntentService to download the RSS feed data
                getActivity().startService(mServiceIntent);
            }

            // If this Fragment existed previously, gets its state
        } else if (bundle.getBoolean(STATE_IS_HIDDEN, false)) {

            // Begins a transaction
            FragmentTransaction localFragmentTransaction = getFragmentManager().beginTransaction();

            // Hides the Fragment
            localFragmentTransaction.hide(this);

            // Commits the transaction
            localFragmentTransaction.commit();
        }

        // Returns the View inflated from the layout
        return localView;
    }

    /*
     * This callback is invoked when the Fragment is being destroyed.
     */
    @Override
    public void onDestroyView() {

        // Sets variables to null, to avoid memory leaks
        mGridView = null;

        // If the EmptyDrawable contains something, sets those members to null
        if (mEmptyDrawable != null) {
            this.mEmptyDrawable.setCallback(null);
            this.mEmptyDrawable = null;
        }

        // Always call the super method last
        super.onDestroyView();
    }

    /*
     * This callback is invoked after onDestroyView(). It clears out variables, shuts down the
     * CursorLoader, and so forth
     */
    @Override
    public void onDetach() {

        // Destroys variables and references, and catches Exceptions
        try {
            getLoaderManager().destroyLoader(0);
            if (mAdapter != null) {
                mAdapter.changeCursor(null);
                mAdapter = null;
            }
        } catch (Throwable localThrowable) {
        }

        // Always call the super method last
        super.onDetach();
        return;
    }

    /*
     * This is invoked whenever the visibility state of the Fragment changes
     */
    @Override
    public void onHiddenChanged(boolean viewState) {
        super.onHiddenChanged(viewState);
    }

    /*
     * Implements OnItemClickListener.onItemClick() for this View's listener.
     * This implementation detects the View that was clicked and retrieves its picture URL.
     */
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int viewId, long rowId) {

        // Returns a one-row cursor for the data that backs the View that was clicked.
        Cursor cursor = (Cursor) mAdapter.getItem(viewId);

        // Retrieves the urlString from the cursor
        String urlString = cursor.getString(IMAGE_URL_CURSOR_INDEX);

        /*
         * Creates a new Intent to get the full picture for the thumbnail that the user clicked.
         * The full photo is loaded into a separate Fragment
         */
        Intent localIntent = new Intent(Constants.ACTION_VIEW_IMAGE).setData(Uri.parse(urlString));

        // Broadcasts the Intent to receivers in this app. See DisplayActivity.FragmentDisplayer.
        LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(localIntent);
    }

    /*
     * Invoked when the CursorLoader finishes the query. A reference to the Loader and the
     * returned Cursor are passed in as arguments
     */
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor returnCursor) {

        /*
         *  Changes the adapter's Cursor to be the results of the load. This forces the View to
         *  redraw.
         */

        mAdapter.changeCursor(returnCursor);
    }

    /*
     * Invoked when the CursorLoader is being reset. For example, this is called if the
     * data in the provider changes and the Cursor becomes stale.
     */
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {

        // Sets the Adapter's backing data to null. This prevents memory leaks.
        mAdapter.changeCursor(null);
    }

    /*
     * This callback is invoked when the system has to destroy the Fragment for some reason. It
     * allows the Fragment to save its state, so the state can be restored later on.
     */
    @Override
    public void onSaveInstanceState(Bundle bundle) {

        // Saves the show-hide status of the display
        bundle.putBoolean(STATE_IS_HIDDEN, isHidden());

        // Always call the super method last
        super.onSaveInstanceState(bundle);
    }

    // Sets the state of the loaded flag
    public void setLoaded(boolean loadState) {
        mIsLoaded = loadState;
    }

    /**
     * Defines a custom View adapter that extends CursorAdapter. The main reason to do this is to
     * display images based on the backing Cursor, rather than just displaying the URLs that the
     * Cursor contains.
     */
    private class GridViewAdapter extends CursorAdapter {

        /**
         * Simplified constructor that calls the super constructor with the input Context,
         * a null value for Cursor, and no flags
         * @param context A Context for this object
         */
        public GridViewAdapter(Context context) {
            super(context, null, false);
        }

        /**
         *
         * Binds a View and a Cursor
         *
         * @param view An existing View object
         * @param context A Context for the View and Cursor
         * @param cursor The Cursor to bind to the View, representing one row of the returned query.
         */
        @Override
        public void bindView(View view, Context context, Cursor cursor) {

            // Gets a handle to the View
            PhotoView localImageDownloaderView = (PhotoView) view.getTag();

            // Converts the URL string to a URL and tries to retrieve the picture
            try {
                // Gets the URL
                URL localURL = new URL(cursor.getString(IMAGE_THUMBURL_CURSOR_INDEX));
                /*
                 * Invokes setImageURL for the View. If the image isn't already available, this
                 * will download and decode it.
                 */
                localImageDownloaderView.setImageURL(localURL, true, PhotoThumbnailFragment.this.mEmptyDrawable);

                // Catches an invalid URL
            } catch (MalformedURLException localMalformedURLException) {
                localMalformedURLException.printStackTrace();

                // Catches errors trying to download and decode the picture in a ThreadPool
            } catch (RejectedExecutionException localRejectedExecutionException) {
            }
        }

        /**
         * Creates a new View that shows the contents of the Cursor
         *
         *
         * @param context A Context for the View and Cursor
         * @param cursor The Cursor to display. This is a single row of the returned query
         * @param viewGroup The viewGroup that's the parent of the new View
         * @return the newly-created View
         */
        @Override
        public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
            // Gets a new layout inflater instance
            LayoutInflater inflater = LayoutInflater.from(context);

            /*
             * Creates a new View by inflating the specified layout file. The root ViewGroup is
             * the root of the layout file. This View is a FrameLayout
             */
            View layoutView = inflater.inflate(R.layout.galleryitem, null);

            /*
             * Creates a second View to hold the thumbnail image.
             */
            View thumbView = layoutView.findViewById(R.id.thumbImage);

            /*
             * Sets layout parameters for the layout based on the layout parameters of a virtual
             * list. In addition, this sets the layoutView's width to be MATCH_PARENT, and its
             * height to be the column width?
             */
            layoutView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
                    PhotoThumbnailFragment.this.mColumnWidth));

            // Sets the layoutView's tag to be the same as the thumbnail image tag.
            layoutView.setTag(thumbView);
            return layoutView;
        }

    }
}