Added shared element transitions to Sunshine.

This commit is contained in:
Dan Galpin
2015-05-25 03:25:28 -07:00
parent be40c17687
commit d9f57fbcfb
13 changed files with 51 additions and 11 deletions

View File

@@ -16,6 +16,7 @@
package com.example.android.sunshine.app;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
@@ -37,6 +38,7 @@ public class DetailActivity extends AppCompatActivity {
Bundle arguments = new Bundle();
arguments.putParcelable(DetailFragment.DETAIL_URI, getIntent().getData());
arguments.putBoolean(DetailFragment.DETAIL_TRANSITION_ANIMATION, true);
DetailFragment fragment = new DetailFragment();
fragment.setArguments(arguments);
@@ -44,6 +46,9 @@ public class DetailActivity extends AppCompatActivity {
getSupportFragmentManager().beginTransaction()
.add(R.id.weather_detail_container, fragment)
.commit();
// Being here means we are in animation mode
supportPostponeEnterTransition();
}
}
}

View File

@@ -48,11 +48,13 @@ public class DetailFragment extends Fragment implements LoaderManager.LoaderCall
private static final String LOG_TAG = DetailFragment.class.getSimpleName();
static final String DETAIL_URI = "URI";
static final String DETAIL_TRANSITION_ANIMATION = "DTA";
private static final String FORECAST_SHARE_HASHTAG = " #SunshineApp";
private String mForecast;
private Uri mUri;
private boolean mTransitionAnimation;
private static final int DETAIL_LOADER = 0;
@@ -108,6 +110,7 @@ public class DetailFragment extends Fragment implements LoaderManager.LoaderCall
Bundle arguments = getArguments();
if (arguments != null) {
mUri = arguments.getParcelable(DetailFragment.DETAIL_URI);
mTransitionAnimation = arguments.getBoolean(DetailFragment.DETAIL_TRANSITION_ANIMATION, false);
}
View rootView = inflater.inflate(R.layout.fragment_detail_start, container, false);
@@ -265,7 +268,7 @@ public class DetailFragment extends Fragment implements LoaderManager.LoaderCall
Toolbar toolbarView = (Toolbar) getView().findViewById(R.id.toolbar);
// We need to start the enter transition after the data has loaded
if (activity instanceof DetailActivity) {
if ( mTransitionAnimation ) {
activity.supportStartPostponedEnterTransition();
if ( null != toolbarView ) {

View File

@@ -21,6 +21,7 @@ import android.os.Build;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -145,6 +146,10 @@ public class ForecastAdapter extends RecyclerView.Adapter<ForecastAdapter.Foreca
.into(forecastAdapterViewHolder.mIconView);
}
// this enables better animations. even if we lose state due to a device rotation,
// the animator can use this to re-find the original view
ViewCompat.setTransitionName(forecastAdapterViewHolder.mIconView, "iconView" + position);
// Read date from cursor
long dateInMillis = mCursor.getLong(ForecastFragment.COL_WEATHER_DATE);

View File

@@ -57,6 +57,7 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
private int mPosition = RecyclerView.NO_POSITION;
private boolean mUseTodayLayout, mAutoSelectView;
private int mChoiceMode;
private boolean mHoldForTransition;
private static final String SELECTED_KEY = "selected_position";
@@ -102,7 +103,7 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
/**
* DetailFragmentCallback for when an item has been selected.
*/
public void onItemSelected(Uri dateUri);
public void onItemSelected(Uri dateUri, ForecastAdapter.ForecastAdapterViewHolder vh);
}
public ForecastFragment() {
@@ -159,6 +160,7 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
0, 0);
mChoiceMode = a.getInt(R.styleable.ForecastFragment_android_choiceMode, AbsListView.CHOICE_MODE_NONE);
mAutoSelectView = a.getBoolean(R.styleable.ForecastFragment_autoSelectView, false);
mHoldForTransition = a.getBoolean(R.styleable.ForecastFragment_sharedElementTransitions, false);
a.recycle();
}
@@ -188,7 +190,8 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
String locationSetting = Utility.getPreferredLocation(getActivity());
((Callback) getActivity())
.onItemSelected(WeatherContract.WeatherEntry.buildWeatherLocationWithDate(
locationSetting, date)
locationSetting, date),
vh
);
mPosition = vh.getAdapterPosition();
}
@@ -237,6 +240,12 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// We hold for transition here just in-case the activity
// needs to be re-created. In a standard return transition,
// this doesn't actually make a difference.
if ( mHoldForTransition ) {
getActivity().supportPostponeEnterTransition();
}
getLoaderManager().initLoader(FORECAST_LOADER, null, this);
super.onActivityCreated(savedInstanceState);
}
@@ -316,7 +325,9 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
mRecyclerView.smoothScrollToPosition(mPosition);
}
updateEmptyView();
if ( data.getCount() > 0 ) {
if ( data.getCount() == 0 ) {
getActivity().supportStartPostponedEnterTransition();
} else {
mRecyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
@@ -330,6 +341,9 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
if ( null != vh && mAutoSelectView ) {
mForecastAdapter.selectView( vh );
}
if ( mHoldForTransition ) {
getActivity().supportStartPostponedEnterTransition();
}
return true;
}
return false;
@@ -339,6 +353,8 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
}
@Override
public void onDestroy() {
super.onDestroy();

View File

@@ -26,11 +26,13 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
import com.google.android.gms.common.ConnectionResult;
@@ -161,7 +163,7 @@ public class MainActivity extends AppCompatActivity implements ForecastFragment.
}
@Override
public void onItemSelected(Uri contentUri) {
public void onItemSelected(Uri contentUri, ForecastAdapter.ForecastAdapterViewHolder vh) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
@@ -180,7 +182,8 @@ public class MainActivity extends AppCompatActivity implements ForecastFragment.
.setData(contentUri);
ActivityOptionsCompat activityOptions =
ActivityOptionsCompat.makeSceneTransitionAnimation(this);
ActivityOptionsCompat.makeSceneTransitionAnimation(this,
new Pair<View, String>(vh.mIconView, getString(R.string.detail_icon_transition_name)));
ActivityCompat.startActivity(this, intent, activityOptions.toBundle());
}
}

View File

@@ -15,9 +15,11 @@
-->
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/fragment_forecast"
android:name="com.example.android.sunshine.app.ForecastFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@android:layout/list_content"
app:sharedElementTransitions="true"
/>

View File

@@ -49,7 +49,8 @@
android:maxWidth="@dimen/today_icon"
app:layout_columnWeight="1"
app:layout_gravity="fill_horizontal"
tools:src="@drawable/art_clouds" />
tools:src="@drawable/art_clouds"
android:transitionName="@string/detail_icon_transition_name"/>
<TextView
android:id="@+id/detail_high_textview"

View File

@@ -16,7 +16,7 @@
-->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together"
android:duration="500">
android:duration="250">
<fade xmlns:android="http://schemas.android.com/apk/res/android">
<targets>
<target android:excludeId="@android:id/statusBarBackground"/>

View File

@@ -16,7 +16,7 @@
-->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together"
android:duration="500">
android:duration="250">
<fade>
<targets>
<target android:excludeId="@android:id/statusBarBackground" />

View File

@@ -16,7 +16,7 @@
-->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together"
android:duration="500">
android:duration="250">
<fade xmlns:android="http://schemas.android.com/apk/res/android">
<targets>
<target android:excludeId="@android:id/statusBarBackground"/>

View File

@@ -16,7 +16,7 @@
-->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together"
android:duration="500">
android:duration="250">
<fade>
<targets>
<target android:excludeId="@android:id/statusBarBackground" />

View File

@@ -21,5 +21,6 @@
<declare-styleable name="ForecastFragment">
<attr name="android:choiceMode" />
<attr name="autoSelectView" format="boolean"/>
<attr name="sharedElementTransitions" format="boolean"/>
</declare-styleable>
</resources>

View File

@@ -211,4 +211,8 @@
<string name="condition_962">Hurricane</string>
<string name="condition_unknown">Unknown (<xliff:g id="low">%1$s</xliff:g>)</string>
<!-- For Shared Element Transitions -->
<string name="detail_icon_transition_name" translatable="false">TN_DetailIcon</string>
</resources>