Updated Sunshine to start using RecyclerView instead of ListView.
This commit is contained in:
@@ -26,5 +26,6 @@ dependencies {
|
||||
compile 'com.android.support:support-annotations:22.1.0'
|
||||
compile 'com.android.support:gridlayout-v7:22.1.0'
|
||||
compile 'com.android.support:cardview-v7:22.1.1'
|
||||
compile 'com.android.support:recyclerview-v7:22.1.1'
|
||||
compile 'com.google.android.gms:play-services-gcm:7.0.0'
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ package com.example.android.sunshine.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -28,28 +29,31 @@ import com.bumptech.glide.Glide;
|
||||
|
||||
/**
|
||||
* {@link ForecastAdapter} exposes a list of weather forecasts
|
||||
* from a {@link Cursor} to a {@link android.widget.ListView}.
|
||||
* from a {@link android.database.Cursor} to a {@link android.support.v7.widget.RecyclerView}.
|
||||
*/
|
||||
public class ForecastAdapter extends CursorAdapter {
|
||||
public class ForecastAdapter extends RecyclerView.Adapter<ForecastAdapter.ForecastAdapterViewHolder> {
|
||||
|
||||
private static final int VIEW_TYPE_COUNT = 2;
|
||||
private static final int VIEW_TYPE_TODAY = 0;
|
||||
private static final int VIEW_TYPE_FUTURE_DAY = 1;
|
||||
|
||||
// Flag to determine if we want to use a separate view for "today".
|
||||
private boolean mUseTodayLayout = true;
|
||||
|
||||
private Cursor mCursor;
|
||||
final private Context mContext;
|
||||
|
||||
/**
|
||||
* Cache of the children views for a forecast list item.
|
||||
*/
|
||||
public static class ViewHolder {
|
||||
public class ForecastAdapterViewHolder extends RecyclerView.ViewHolder {
|
||||
public final ImageView mIconView;
|
||||
public final TextView mDateView;
|
||||
public final TextView mDescriptionView;
|
||||
public final TextView mHighTempView;
|
||||
public final TextView mLowTempView;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
public ForecastAdapterViewHolder(View view) {
|
||||
super(view);
|
||||
mIconView = (ImageView) view.findViewById(R.id.list_item_icon);
|
||||
mDateView = (TextView) view.findViewById(R.id.list_item_date_textview);
|
||||
mDescriptionView = (TextView) view.findViewById(R.id.list_item_forecast_textview);
|
||||
@@ -58,14 +62,21 @@ public class ForecastAdapter extends CursorAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
public ForecastAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
public ForecastAdapter(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/*
|
||||
This takes advantage of the fact that the viewGroup passed to onCreateViewHolder is the
|
||||
RecyclerView that will be used to contain the view, so that it can get the current
|
||||
ItemSelectionManager from the view.
|
||||
|
||||
One could implement this pattern without modifying RecyclerView by taking advantage
|
||||
of the view tag to store the ItemChoiceManager.
|
||||
*/
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
// Choose the layout type
|
||||
int viewType = getItemViewType(cursor.getPosition());
|
||||
public ForecastAdapterViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
|
||||
if ( viewGroup instanceof RecyclerView ) {
|
||||
int layoutId = -1;
|
||||
switch (viewType) {
|
||||
case VIEW_TYPE_TODAY: {
|
||||
@@ -77,23 +88,21 @@ public class ForecastAdapter extends CursorAdapter {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
|
||||
|
||||
ViewHolder viewHolder = new ViewHolder(view);
|
||||
view.setTag(viewHolder);
|
||||
|
||||
return view;
|
||||
View view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutId, viewGroup, false);
|
||||
view.setFocusable(true);
|
||||
return new ForecastAdapterViewHolder(view);
|
||||
} else {
|
||||
throw new RuntimeException("Not bound to RecyclerViewSelection");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
|
||||
ViewHolder viewHolder = (ViewHolder) view.getTag();
|
||||
int weatherId = cursor.getInt(ForecastFragment.COL_WEATHER_CONDITION_ID);
|
||||
public void onBindViewHolder(ForecastAdapterViewHolder forecastAdapterViewHolder, int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
int weatherId = mCursor.getInt(ForecastFragment.COL_WEATHER_CONDITION_ID);
|
||||
int defaultImage;
|
||||
|
||||
switch (getItemViewType(cursor.getPosition())) {
|
||||
switch (getItemViewType(position)) {
|
||||
case VIEW_TYPE_TODAY:
|
||||
defaultImage = Utility.getArtResourceForWeatherCondition(weatherId);
|
||||
break;
|
||||
@@ -102,26 +111,27 @@ public class ForecastAdapter extends CursorAdapter {
|
||||
}
|
||||
|
||||
if ( Utility.usingLocalGraphics(mContext) ) {
|
||||
viewHolder.mIconView.
|
||||
setImageResource(defaultImage);
|
||||
forecastAdapterViewHolder.mIconView.setImageResource(defaultImage);
|
||||
} else {
|
||||
Glide.with(mContext)
|
||||
.load(Utility.getArtUrlForWeatherCondition(mContext, weatherId))
|
||||
.error(defaultImage)
|
||||
.crossFade()
|
||||
.into(viewHolder.mIconView);
|
||||
.into(forecastAdapterViewHolder.mIconView);
|
||||
}
|
||||
|
||||
// Read date from cursor
|
||||
long dateInMillis = cursor.getLong(ForecastFragment.COL_WEATHER_DATE);
|
||||
long dateInMillis = mCursor.getLong(ForecastFragment.COL_WEATHER_DATE);
|
||||
|
||||
// Find TextView and set formatted date on it
|
||||
viewHolder.mDateView.setText(Utility.getFriendlyDayString(context, dateInMillis));
|
||||
forecastAdapterViewHolder.mDateView.setText(Utility.getFriendlyDayString(mContext, dateInMillis));
|
||||
|
||||
// Read weather forecast from cursor
|
||||
String description = Utility.getStringForWeatherCondition(mContext, weatherId);
|
||||
|
||||
// Find TextView and set weather forecast on it
|
||||
viewHolder.mDescriptionView.setText(description);
|
||||
viewHolder.mDescriptionView.setContentDescription(mContext.getString(R.string.a11y_forecast, description));
|
||||
forecastAdapterViewHolder.mDescriptionView.setText(description);
|
||||
forecastAdapterViewHolder.mDescriptionView.setContentDescription(mContext.getString(R.string.a11y_forecast, description));
|
||||
|
||||
// For accessibility, we don't want a content description for the icon field
|
||||
// because the information is repeated in the description view and the icon
|
||||
@@ -130,27 +140,39 @@ public class ForecastAdapter extends CursorAdapter {
|
||||
// Read high temperature from cursor
|
||||
double high = mCursor.getDouble(ForecastFragment.COL_WEATHER_MAX_TEMP);
|
||||
String highString = Utility.formatTemperature(mContext, high);
|
||||
viewHolder.mHighTempView.setText(highString);
|
||||
viewHolder.mHighTempView.setContentDescription(mContext.getString(R.string.a11y_high_temp, highString));
|
||||
forecastAdapterViewHolder.mHighTempView.setText(highString);
|
||||
forecastAdapterViewHolder.mHighTempView.setContentDescription(mContext.getString(R.string.a11y_high_temp, highString));
|
||||
|
||||
// Read low temperature from cursor
|
||||
double low = mCursor.getDouble(ForecastFragment.COL_WEATHER_MIN_TEMP);
|
||||
String lowString = Utility.formatTemperature(mContext, low);
|
||||
viewHolder.mLowTempView.setText(lowString);
|
||||
viewHolder.mLowTempView.setContentDescription(mContext.getString(R.string.a11y_low_temp, lowString));
|
||||
forecastAdapterViewHolder.mLowTempView.setText(lowString);
|
||||
forecastAdapterViewHolder.mLowTempView.setContentDescription(mContext.getString(R.string.a11y_low_temp, lowString));
|
||||
}
|
||||
|
||||
|
||||
public void setUseTodayLayout(boolean useTodayLayout) {
|
||||
mUseTodayLayout = useTodayLayout;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return (position == 0 && mUseTodayLayout) ? VIEW_TYPE_TODAY : VIEW_TYPE_FUTURE_DAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return VIEW_TYPE_COUNT;
|
||||
public int getItemCount() {
|
||||
if ( null == mCursor ) return 0;
|
||||
return mCursor.getCount();
|
||||
}
|
||||
|
||||
public void swapCursor(Cursor newCursor) {
|
||||
mCursor = newCursor;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public Cursor getCursor() {
|
||||
return mCursor;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -32,22 +34,20 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.example.android.sunshine.app.data.WeatherContract;
|
||||
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
|
||||
|
||||
/**
|
||||
* Encapsulates fetching the forecast and displaying it as a {@link ListView} layout.
|
||||
* Encapsulates fetching the forecast and displaying it as a {@link RecyclerView} layout.
|
||||
*/
|
||||
public class ForecastFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public static final String LOG_TAG = ForecastFragment.class.getSimpleName();
|
||||
private ForecastAdapter mForecastAdapter;
|
||||
|
||||
private ListView mListView;
|
||||
private int mPosition = ListView.INVALID_POSITION;
|
||||
private RecyclerView mRecyclerView;
|
||||
private int mPosition = RecyclerView.NO_POSITION;
|
||||
private boolean mUseTodayLayout;
|
||||
|
||||
private static final String SELECTED_KEY = "selected_position";
|
||||
@@ -149,34 +149,36 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
// The ForecastAdapter will take data from a source and
|
||||
// use it to populate the ListView it's attached to.
|
||||
mForecastAdapter = new ForecastAdapter(getActivity(), null, 0);
|
||||
// use it to populate the RecyclerView it's attached to.
|
||||
mForecastAdapter = new ForecastAdapter(getActivity());
|
||||
|
||||
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
|
||||
|
||||
// Get a reference to the ListView, and attach this adapter to it.
|
||||
mListView = (ListView) rootView.findViewById(R.id.listview_forecast);
|
||||
View emptyView = rootView.findViewById(R.id.listview_forecast_empty);
|
||||
mListView.setEmptyView(emptyView);
|
||||
mListView.setAdapter(mForecastAdapter);
|
||||
// We'll call our MainActivity
|
||||
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
// Get a reference to the RecyclerView, and attach this adapter to it.
|
||||
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview_forecast);
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
|
||||
// CursorAdapter returns a cursor at the correct position for getItem(), or null
|
||||
// if it cannot seek to that position.
|
||||
Cursor cursor = (Cursor) adapterView.getItemAtPosition(position);
|
||||
if (cursor != null) {
|
||||
String locationSetting = Utility.getPreferredLocation(getActivity());
|
||||
((Callback) getActivity())
|
||||
.onItemSelected(WeatherContract.WeatherEntry.buildWeatherLocationWithDate(
|
||||
locationSetting, cursor.getLong(COL_WEATHER_DATE)
|
||||
));
|
||||
}
|
||||
mPosition = position;
|
||||
}
|
||||
});
|
||||
// Set the layout manager
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
// View emptyView = rootView.findViewById(R.id.recyclerview_forecast_empty);
|
||||
mRecyclerView.setAdapter(mForecastAdapter);
|
||||
// We'll call our MainActivity
|
||||
// mRecyclerView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
//
|
||||
// @Override
|
||||
// public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
|
||||
// // CursorAdapter returns a cursor at the correct position for getItem(), or null
|
||||
// // if it cannot seek to that position.
|
||||
// Cursor cursor = (Cursor) adapterView.getItemAtPosition(position);
|
||||
// if (cursor != null) {
|
||||
// String locationSetting = Utility.getPreferredLocation(getActivity());
|
||||
// ((Callback) getActivity())
|
||||
// .onItemSelected(WeatherContract.WeatherEntry.buildWeatherLocationWithDate(
|
||||
// locationSetting, cursor.getLong(COL_WEATHER_DATE)
|
||||
// ));
|
||||
// }
|
||||
// mPosition = position;
|
||||
// }
|
||||
// });
|
||||
|
||||
// If there's instance state, mine it for useful information.
|
||||
// The end-goal here is that the user never knows that turning their device sideways
|
||||
@@ -184,7 +186,7 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
// or magically appeared to take advantage of room, but data or place in the app was never
|
||||
// actually *lost*.
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(SELECTED_KEY)) {
|
||||
// The listview probably hasn't even been populated yet. Actually perform the
|
||||
// The RecyclerView probably hasn't even been populated yet. Actually perform the
|
||||
// swapout in onLoadFinished.
|
||||
mPosition = savedInstanceState.getInt(SELECTED_KEY);
|
||||
}
|
||||
@@ -233,9 +235,9 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
// When tablets rotate, the currently selected list item needs to be saved.
|
||||
// When no item is selected, mPosition will be set to Listview.INVALID_POSITION,
|
||||
// When no item is selected, mPosition will be set to RecyclerView.INVALID_POSITION,
|
||||
// so check for that before storing.
|
||||
if (mPosition != ListView.INVALID_POSITION) {
|
||||
if (mPosition != RecyclerView.NO_POSITION) {
|
||||
outState.putInt(SELECTED_KEY, mPosition);
|
||||
}
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -267,10 +269,10 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
mForecastAdapter.swapCursor(data);
|
||||
if (mPosition != ListView.INVALID_POSITION) {
|
||||
if (mPosition != RecyclerView.NO_POSITION) {
|
||||
// If we don't need to restart the loader, and there's a desired position to restore
|
||||
// to, do so now.
|
||||
mListView.smoothScrollToPosition(mPosition);
|
||||
mRecyclerView.smoothScrollToPosition(mPosition);
|
||||
}
|
||||
updateEmptyView();
|
||||
}
|
||||
@@ -292,8 +294,8 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
use to determine why they aren't seeing weather.
|
||||
*/
|
||||
private void updateEmptyView() {
|
||||
if ( mForecastAdapter.getCount() == 0 ) {
|
||||
TextView tv = (TextView) getView().findViewById(R.id.listview_forecast_empty);
|
||||
if ( mForecastAdapter.getItemCount() == 0 ) {
|
||||
TextView tv = (TextView) getView().findViewById(R.id.recyclerview_forecast_empty);
|
||||
if ( null != tv ) {
|
||||
// if cursor is empty, why? do we have an invalid location
|
||||
int message = R.string.empty_forecast_list;
|
||||
|
||||
@@ -18,15 +18,14 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.example.android.sunshine.app.ForecastFragment">
|
||||
<ListView
|
||||
<android.support.v7.widget.RecyclerView
|
||||
style="@style/ForecastListStyle"
|
||||
android:id="@+id/listview_forecast"
|
||||
android:id="@+id/recyclerview_forecast"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@null" />
|
||||
android:layout_height="match_parent"/>
|
||||
<!-- empty list -->
|
||||
<TextView
|
||||
android:id="@+id/listview_forecast_empty"
|
||||
android:id="@+id/recyclerview_forecast_empty"
|
||||
android:text="@string/empty_forecast_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="@dimen/appbar_elevation"
|
||||
tools:context="com.example.android.sunshine.app.ForecastFragment">
|
||||
<ListView
|
||||
<android.support.v7.widget.RecyclerView
|
||||
style="@style/ForecastListStyle"
|
||||
android:id="@+id/listview_forecast"
|
||||
android:id="@+id/recyclerview_forecast"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@null" />
|
||||
<!-- empty list -->
|
||||
<TextView
|
||||
android:id="@+id/listview_forecast_empty"
|
||||
android:id="@+id/recyclerview_forecast_empty"
|
||||
android:text="@string/empty_forecast_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
Reference in New Issue
Block a user