blob: 0a89e9ebaeffc2a724219bd7e516791db8a76039 [file] [log] [blame]
/*
* 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;
}
}
}