Added shared element transitions to Sunshine.
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
package com.example.android.sunshine.app;
|
package com.example.android.sunshine.app;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
@@ -37,6 +38,7 @@ public class DetailActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
Bundle arguments = new Bundle();
|
Bundle arguments = new Bundle();
|
||||||
arguments.putParcelable(DetailFragment.DETAIL_URI, getIntent().getData());
|
arguments.putParcelable(DetailFragment.DETAIL_URI, getIntent().getData());
|
||||||
|
arguments.putBoolean(DetailFragment.DETAIL_TRANSITION_ANIMATION, true);
|
||||||
|
|
||||||
DetailFragment fragment = new DetailFragment();
|
DetailFragment fragment = new DetailFragment();
|
||||||
fragment.setArguments(arguments);
|
fragment.setArguments(arguments);
|
||||||
@@ -44,6 +46,9 @@ public class DetailActivity extends AppCompatActivity {
|
|||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.add(R.id.weather_detail_container, fragment)
|
.add(R.id.weather_detail_container, fragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
|
// Being here means we are in animation mode
|
||||||
|
supportPostponeEnterTransition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,11 +48,13 @@ public class DetailFragment extends Fragment implements LoaderManager.LoaderCall
|
|||||||
|
|
||||||
private static final String LOG_TAG = DetailFragment.class.getSimpleName();
|
private static final String LOG_TAG = DetailFragment.class.getSimpleName();
|
||||||
static final String DETAIL_URI = "URI";
|
static final String DETAIL_URI = "URI";
|
||||||
|
static final String DETAIL_TRANSITION_ANIMATION = "DTA";
|
||||||
|
|
||||||
private static final String FORECAST_SHARE_HASHTAG = " #SunshineApp";
|
private static final String FORECAST_SHARE_HASHTAG = " #SunshineApp";
|
||||||
|
|
||||||
private String mForecast;
|
private String mForecast;
|
||||||
private Uri mUri;
|
private Uri mUri;
|
||||||
|
private boolean mTransitionAnimation;
|
||||||
|
|
||||||
private static final int DETAIL_LOADER = 0;
|
private static final int DETAIL_LOADER = 0;
|
||||||
|
|
||||||
@@ -108,6 +110,7 @@ public class DetailFragment extends Fragment implements LoaderManager.LoaderCall
|
|||||||
Bundle arguments = getArguments();
|
Bundle arguments = getArguments();
|
||||||
if (arguments != null) {
|
if (arguments != null) {
|
||||||
mUri = arguments.getParcelable(DetailFragment.DETAIL_URI);
|
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);
|
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);
|
Toolbar toolbarView = (Toolbar) getView().findViewById(R.id.toolbar);
|
||||||
|
|
||||||
// We need to start the enter transition after the data has loaded
|
// We need to start the enter transition after the data has loaded
|
||||||
if (activity instanceof DetailActivity) {
|
if ( mTransitionAnimation ) {
|
||||||
activity.supportStartPostponedEnterTransition();
|
activity.supportStartPostponedEnterTransition();
|
||||||
|
|
||||||
if ( null != toolbarView ) {
|
if ( null != toolbarView ) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -145,6 +146,10 @@ public class ForecastAdapter extends RecyclerView.Adapter<ForecastAdapter.Foreca
|
|||||||
.into(forecastAdapterViewHolder.mIconView);
|
.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
|
// Read date from cursor
|
||||||
long dateInMillis = mCursor.getLong(ForecastFragment.COL_WEATHER_DATE);
|
long dateInMillis = mCursor.getLong(ForecastFragment.COL_WEATHER_DATE);
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
|||||||
private int mPosition = RecyclerView.NO_POSITION;
|
private int mPosition = RecyclerView.NO_POSITION;
|
||||||
private boolean mUseTodayLayout, mAutoSelectView;
|
private boolean mUseTodayLayout, mAutoSelectView;
|
||||||
private int mChoiceMode;
|
private int mChoiceMode;
|
||||||
|
private boolean mHoldForTransition;
|
||||||
|
|
||||||
private static final String SELECTED_KEY = "selected_position";
|
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.
|
* DetailFragmentCallback for when an item has been selected.
|
||||||
*/
|
*/
|
||||||
public void onItemSelected(Uri dateUri);
|
public void onItemSelected(Uri dateUri, ForecastAdapter.ForecastAdapterViewHolder vh);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForecastFragment() {
|
public ForecastFragment() {
|
||||||
@@ -159,6 +160,7 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
|||||||
0, 0);
|
0, 0);
|
||||||
mChoiceMode = a.getInt(R.styleable.ForecastFragment_android_choiceMode, AbsListView.CHOICE_MODE_NONE);
|
mChoiceMode = a.getInt(R.styleable.ForecastFragment_android_choiceMode, AbsListView.CHOICE_MODE_NONE);
|
||||||
mAutoSelectView = a.getBoolean(R.styleable.ForecastFragment_autoSelectView, false);
|
mAutoSelectView = a.getBoolean(R.styleable.ForecastFragment_autoSelectView, false);
|
||||||
|
mHoldForTransition = a.getBoolean(R.styleable.ForecastFragment_sharedElementTransitions, false);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +190,8 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
|||||||
String locationSetting = Utility.getPreferredLocation(getActivity());
|
String locationSetting = Utility.getPreferredLocation(getActivity());
|
||||||
((Callback) getActivity())
|
((Callback) getActivity())
|
||||||
.onItemSelected(WeatherContract.WeatherEntry.buildWeatherLocationWithDate(
|
.onItemSelected(WeatherContract.WeatherEntry.buildWeatherLocationWithDate(
|
||||||
locationSetting, date)
|
locationSetting, date),
|
||||||
|
vh
|
||||||
);
|
);
|
||||||
mPosition = vh.getAdapterPosition();
|
mPosition = vh.getAdapterPosition();
|
||||||
}
|
}
|
||||||
@@ -237,6 +240,12 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
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);
|
getLoaderManager().initLoader(FORECAST_LOADER, null, this);
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
}
|
}
|
||||||
@@ -316,7 +325,9 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
|||||||
mRecyclerView.smoothScrollToPosition(mPosition);
|
mRecyclerView.smoothScrollToPosition(mPosition);
|
||||||
}
|
}
|
||||||
updateEmptyView();
|
updateEmptyView();
|
||||||
if ( data.getCount() > 0 ) {
|
if ( data.getCount() == 0 ) {
|
||||||
|
getActivity().supportStartPostponedEnterTransition();
|
||||||
|
} else {
|
||||||
mRecyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
mRecyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreDraw() {
|
public boolean onPreDraw() {
|
||||||
@@ -330,6 +341,9 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
|||||||
if ( null != vh && mAutoSelectView ) {
|
if ( null != vh && mAutoSelectView ) {
|
||||||
mForecastAdapter.selectView( vh );
|
mForecastAdapter.selectView( vh );
|
||||||
}
|
}
|
||||||
|
if ( mHoldForTransition ) {
|
||||||
|
getActivity().supportStartPostponedEnterTransition();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -339,6 +353,8 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ import android.os.AsyncTask;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.ActivityOptionsCompat;
|
import android.support.v4.app.ActivityOptionsCompat;
|
||||||
|
import android.support.v4.util.Pair;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
|
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
@@ -161,7 +163,7 @@ public class MainActivity extends AppCompatActivity implements ForecastFragment.
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(Uri contentUri) {
|
public void onItemSelected(Uri contentUri, ForecastAdapter.ForecastAdapterViewHolder vh) {
|
||||||
if (mTwoPane) {
|
if (mTwoPane) {
|
||||||
// In two-pane mode, show the detail view in this activity by
|
// In two-pane mode, show the detail view in this activity by
|
||||||
// adding or replacing the detail fragment using a
|
// adding or replacing the detail fragment using a
|
||||||
@@ -180,7 +182,8 @@ public class MainActivity extends AppCompatActivity implements ForecastFragment.
|
|||||||
.setData(contentUri);
|
.setData(contentUri);
|
||||||
|
|
||||||
ActivityOptionsCompat activityOptions =
|
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());
|
ActivityCompat.startActivity(this, intent, activityOptions.toBundle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,11 @@
|
|||||||
-->
|
-->
|
||||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/fragment_forecast"
|
android:id="@+id/fragment_forecast"
|
||||||
android:name="com.example.android.sunshine.app.ForecastFragment"
|
android:name="com.example.android.sunshine.app.ForecastFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:layout="@android:layout/list_content"
|
tools:layout="@android:layout/list_content"
|
||||||
|
app:sharedElementTransitions="true"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -49,7 +49,8 @@
|
|||||||
android:maxWidth="@dimen/today_icon"
|
android:maxWidth="@dimen/today_icon"
|
||||||
app:layout_columnWeight="1"
|
app:layout_columnWeight="1"
|
||||||
app:layout_gravity="fill_horizontal"
|
app:layout_gravity="fill_horizontal"
|
||||||
tools:src="@drawable/art_clouds" />
|
tools:src="@drawable/art_clouds"
|
||||||
|
android:transitionName="@string/detail_icon_transition_name"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/detail_high_textview"
|
android:id="@+id/detail_high_textview"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
-->
|
-->
|
||||||
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
|
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:transitionOrdering="together"
|
android:transitionOrdering="together"
|
||||||
android:duration="500">
|
android:duration="250">
|
||||||
<fade xmlns:android="http://schemas.android.com/apk/res/android">
|
<fade xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<targets>
|
<targets>
|
||||||
<target android:excludeId="@android:id/statusBarBackground"/>
|
<target android:excludeId="@android:id/statusBarBackground"/>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
-->
|
-->
|
||||||
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
|
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:transitionOrdering="together"
|
android:transitionOrdering="together"
|
||||||
android:duration="500">
|
android:duration="250">
|
||||||
<fade>
|
<fade>
|
||||||
<targets>
|
<targets>
|
||||||
<target android:excludeId="@android:id/statusBarBackground" />
|
<target android:excludeId="@android:id/statusBarBackground" />
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
-->
|
-->
|
||||||
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
|
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:transitionOrdering="together"
|
android:transitionOrdering="together"
|
||||||
android:duration="500">
|
android:duration="250">
|
||||||
<fade xmlns:android="http://schemas.android.com/apk/res/android">
|
<fade xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<targets>
|
<targets>
|
||||||
<target android:excludeId="@android:id/statusBarBackground"/>
|
<target android:excludeId="@android:id/statusBarBackground"/>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
-->
|
-->
|
||||||
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
|
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:transitionOrdering="together"
|
android:transitionOrdering="together"
|
||||||
android:duration="500">
|
android:duration="250">
|
||||||
<fade>
|
<fade>
|
||||||
<targets>
|
<targets>
|
||||||
<target android:excludeId="@android:id/statusBarBackground" />
|
<target android:excludeId="@android:id/statusBarBackground" />
|
||||||
|
|||||||
@@ -21,5 +21,6 @@
|
|||||||
<declare-styleable name="ForecastFragment">
|
<declare-styleable name="ForecastFragment">
|
||||||
<attr name="android:choiceMode" />
|
<attr name="android:choiceMode" />
|
||||||
<attr name="autoSelectView" format="boolean"/>
|
<attr name="autoSelectView" format="boolean"/>
|
||||||
|
<attr name="sharedElementTransitions" format="boolean"/>
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -211,4 +211,8 @@
|
|||||||
<string name="condition_962">Hurricane</string>
|
<string name="condition_962">Hurricane</string>
|
||||||
|
|
||||||
<string name="condition_unknown">Unknown (<xliff:g id="low">%1$s</xliff:g>)</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>
|
</resources>
|
||||||
Reference in New Issue
Block a user