Merge pull request #138 from poisdeux/sharedelementtransitions

Implemented shared element transition for movie posters
This commit is contained in:
Synced Synapse 2015-11-25 22:33:07 +00:00
commit abd1b2fc67
6 changed files with 132 additions and 45 deletions

View File

@ -15,6 +15,7 @@
*/ */
package org.xbmc.kore.ui; package org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.res.Resources; import android.content.res.Resources;
@ -44,6 +45,7 @@ import android.widget.Toast;
import com.melnykov.fab.FloatingActionButton; import com.melnykov.fab.FloatingActionButton;
import com.melnykov.fab.ObservableScrollView; import com.melnykov.fab.ObservableScrollView;
import org.xbmc.kore.R; import org.xbmc.kore.R;
import org.xbmc.kore.Settings; import org.xbmc.kore.Settings;
import org.xbmc.kore.jsonrpc.ApiCallback; import org.xbmc.kore.jsonrpc.ApiCallback;
@ -74,8 +76,14 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
implements LoaderManager.LoaderCallbacks<Cursor> { implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = LogUtils.makeLogTag(MovieDetailsFragment.class); private static final String TAG = LogUtils.makeLogTag(MovieDetailsFragment.class);
public static final String MOVIEID = "movie_id"; public static final String BUNDLE_KEY_MOVIETITLE = "movie_title";
public static final String BUNDLE_KEY_MOVIEPLOT = "movie_plot";
public static final String BUNDLE_KEY_MOVIEID = "movie_id";
public static final String POSTER_TRANS_NAME = "POSTER_TRANS_NAME";
public static final String BUNDLE_KEY_MOVIEGENRES = "movie_genres";
public static final String BUNDLE_KEY_MOVIEYEAR = "movie_year";
public static final String BUNDLE_KEY_MOVIERUNTIME = "movie_runtime";
public static final String BUNDLE_KEY_MOVIERATING = "movie_rating";
// Loader IDs // Loader IDs
private static final int LOADER_MOVIE = 0, private static final int LOADER_MOVIE = 0,
LOADER_CAST = 1; LOADER_CAST = 1;
@ -129,18 +137,31 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
/** /**
* Create a new instance of this, initialized to show the movie movieId * Create a new instance of this, initialized to show the movie movieId
*/ */
public static MovieDetailsFragment newInstance(int movieId) { @TargetApi(21)
public static MovieDetailsFragment newInstance(MovieListFragment.ViewHolder vh) {
MovieDetailsFragment fragment = new MovieDetailsFragment(); MovieDetailsFragment fragment = new MovieDetailsFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putInt(MOVIEID, movieId); args.putInt(BUNDLE_KEY_MOVIEID, vh.movieId);
args.putString(BUNDLE_KEY_MOVIETITLE, vh.movieTitle);
args.putString(BUNDLE_KEY_MOVIEPLOT, vh.movieTagline);
args.putString(BUNDLE_KEY_MOVIEGENRES, vh.movieGenres);
args.putInt(BUNDLE_KEY_MOVIEYEAR, vh.movieYear);
args.putInt(BUNDLE_KEY_MOVIERUNTIME, vh.movieRuntime);
args.putDouble(BUNDLE_KEY_MOVIERATING, vh.movieRating);
if( Utils.isLollipopOrLater()) {
args.putString(POSTER_TRANS_NAME, vh.artView.getTransitionName());
}
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
} }
@TargetApi(21)
@Override @Override
protected View createView(LayoutInflater inflater, ViewGroup container) { protected View createView(LayoutInflater inflater, ViewGroup container) {
movieId = getArguments().getInt(MOVIEID, -1); Bundle bundle = getArguments();
movieId = bundle.getInt(BUNDLE_KEY_MOVIEID, -1);
if (movieId == -1) { if (movieId == -1) {
// There's nothing to show // There's nothing to show
@ -167,10 +188,20 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
FloatingActionButton fab = (FloatingActionButton)fabButton; FloatingActionButton fab = (FloatingActionButton)fabButton;
fab.attachToScrollView((ObservableScrollView) mediaPanel); fab.attachToScrollView((ObservableScrollView) mediaPanel);
if(Utils.isLollipopOrLater()) {
mediaPoster.setTransitionName(getArguments().getString(POSTER_TRANS_NAME));
}
mediaTitle.setText(bundle.getString(BUNDLE_KEY_MOVIETITLE));
mediaUndertitle.setText(bundle.getString(BUNDLE_KEY_MOVIEPLOT));
mediaGenres.setText(bundle.getString(BUNDLE_KEY_MOVIEGENRES));
setMediaYear(bundle.getInt(BUNDLE_KEY_MOVIERUNTIME), bundle.getInt(BUNDLE_KEY_MOVIEYEAR));
setMediaRating(bundle.getDouble(BUNDLE_KEY_MOVIERATING));
// Pad main content view to overlap with bottom system bar // Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true); // UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false); // mediaPanel.setClipToPadding(false);
return root; return root;
} }
@ -209,9 +240,18 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
public void onResume() { public void onResume() {
// Force the exit view to invisible // Force the exit view to invisible
exitTransitionView.setVisibility(View.INVISIBLE); exitTransitionView.setVisibility(View.INVISIBLE);
//As we make mediaPoster invisible in onStop() we need to make it visible here.
mediaPoster.setVisibility(View.VISIBLE);
super.onResume(); super.onResume();
} }
@Override
public void onStop() {
//For some reason poster is included in the bottom slide animation, by making it invisible it is not noticeable for the user
mediaPoster.setVisibility(View.INVISIBLE);
super.onStop();
}
@Override @Override
protected void onSyncProcessEnded(MediaSyncEvent event) { protected void onSyncProcessEnded(MediaSyncEvent event) {
if (event.status == MediaSyncEvent.STATUS_SUCCESS) { if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
@ -456,12 +496,8 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
mediaTitle.setText(movieTitle); mediaTitle.setText(movieTitle);
mediaUndertitle.setText(cursor.getString(MovieDetailsQuery.TAGLINE)); mediaUndertitle.setText(cursor.getString(MovieDetailsQuery.TAGLINE));
int runtime = cursor.getInt(MovieDetailsQuery.RUNTIME) / 60; setMediaYear(cursor.getInt(MovieDetailsQuery.RUNTIME) / 60, cursor.getInt(MovieDetailsQuery.YEAR));
String durationYear = runtime > 0 ?
String.format(getString(R.string.minutes_abbrev), String.valueOf(runtime)) +
" | " + String.valueOf(cursor.getInt(MovieDetailsQuery.YEAR)) :
String.valueOf(cursor.getInt(MovieDetailsQuery.YEAR));
mediaYear.setText(durationYear);
mediaGenres.setText(cursor.getString(MovieDetailsQuery.GENRES)); mediaGenres.setText(cursor.getString(MovieDetailsQuery.GENRES));
double rating = cursor.getDouble(MovieDetailsQuery.RATING); double rating = cursor.getDouble(MovieDetailsQuery.RATING);
@ -469,8 +505,7 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
mediaRating.setVisibility(View.VISIBLE); mediaRating.setVisibility(View.VISIBLE);
mediaMaxRating.setVisibility(View.VISIBLE); mediaMaxRating.setVisibility(View.VISIBLE);
mediaRatingVotes.setVisibility(View.VISIBLE); mediaRatingVotes.setVisibility(View.VISIBLE);
mediaRating.setText(String.format("%01.01f", rating)); setMediaRating(rating);
mediaMaxRating.setText(getString(R.string.max_rating_video));
String votes = cursor.getString(MovieDetailsQuery.VOTES); String votes = cursor.getString(MovieDetailsQuery.VOTES);
mediaRatingVotes.setText((TextUtils.isEmpty(votes)) ? mediaRatingVotes.setText((TextUtils.isEmpty(votes)) ?
"" : String.format(getString(R.string.votes), votes)); "" : String.format(getString(R.string.votes), votes));
@ -508,7 +543,6 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
movieTitle, cursor.getString(MovieDetailsQuery.FILE)); movieTitle, cursor.getString(MovieDetailsQuery.FILE));
// Check if downloaded file exists // Check if downloaded file exists
downloadButton.setVisibility(View.VISIBLE);
if (movieDownloadInfo.downloadFileExists()) { if (movieDownloadInfo.downloadFileExists()) {
Resources.Theme theme = getActivity().getTheme(); Resources.Theme theme = getActivity().getTheme();
TypedArray styledAttributes = theme.obtainStyledAttributes(new int[]{ TypedArray styledAttributes = theme.obtainStyledAttributes(new int[]{
@ -522,6 +556,19 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
} }
} }
private void setMediaRating(double rating) {
mediaRating.setText(String.format("%01.01f", rating));
mediaMaxRating.setText(getString(R.string.max_rating_video));
}
private void setMediaYear(int runtime, int year) {
String durationYear = runtime > 0 ?
String.format(getString(R.string.minutes_abbrev), String.valueOf(runtime)) +
" | " + year :
String.valueOf(year);
mediaYear.setText(durationYear);
}
private void setupSeenButton(int playcount) { private void setupSeenButton(int playcount) {
// Seen button // Seen button
if (playcount > 0) { if (playcount > 0) {

View File

@ -15,6 +15,7 @@
*/ */
package org.xbmc.kore.ui; package org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -47,6 +48,7 @@ import org.xbmc.kore.provider.MediaDatabase;
import org.xbmc.kore.service.LibrarySyncService; import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils; import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
/** /**
* Fragment that presents the movie list * Fragment that presents the movie list
@ -55,7 +57,7 @@ public class MovieListFragment extends AbstractListFragment {
private static final String TAG = LogUtils.makeLogTag(MovieListFragment.class); private static final String TAG = LogUtils.makeLogTag(MovieListFragment.class);
public interface OnMovieSelectedListener { public interface OnMovieSelectedListener {
public void onMovieSelected(int movieId, String movieTitle); public void onMovieSelected(ViewHolder vh);
} }
// Activity listener // Activity listener
@ -72,7 +74,7 @@ public class MovieListFragment extends AbstractListFragment {
// Get the movie id from the tag // Get the movie id from the tag
ViewHolder tag = (ViewHolder) view.getTag(); ViewHolder tag = (ViewHolder) view.getTag();
// Notify the activity // Notify the activity
listenerActivity.onMovieSelected(tag.movieId, tag.movieTitle); listenerActivity.onMovieSelected(tag);
} }
}; };
} }
@ -253,16 +255,16 @@ public class MovieListFragment extends AbstractListFragment {
// the user transitions to that fragment, avoiding another call and imediatelly showing the image // the user transitions to that fragment, avoiding another call and imediatelly showing the image
Resources resources = context.getResources(); Resources resources = context.getResources();
artWidth = (int)(resources.getDimension(R.dimen.now_playing_poster_width) / artWidth = (int)(resources.getDimension(R.dimen.now_playing_poster_width) /
UIUtils.IMAGE_RESIZE_FACTOR); UIUtils.IMAGE_RESIZE_FACTOR);
artHeight = (int)(resources.getDimension(R.dimen.now_playing_poster_height) / artHeight = (int)(resources.getDimension(R.dimen.now_playing_poster_height) /
UIUtils.IMAGE_RESIZE_FACTOR); UIUtils.IMAGE_RESIZE_FACTOR);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public View newView(Context context, final Cursor cursor, ViewGroup parent) { public View newView(Context context, final Cursor cursor, ViewGroup parent) {
final View view = LayoutInflater.from(context) final View view = LayoutInflater.from(context)
.inflate(R.layout.grid_item_movie, parent, false); .inflate(R.layout.grid_item_movie, parent, false);
// Setup View holder pattern // Setup View holder pattern
ViewHolder viewHolder = new ViewHolder(); ViewHolder viewHolder = new ViewHolder();
@ -277,6 +279,7 @@ public class MovieListFragment extends AbstractListFragment {
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@TargetApi(21)
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
final ViewHolder viewHolder = (ViewHolder)view.getTag(); final ViewHolder viewHolder = (ViewHolder)view.getTag();
@ -284,36 +287,50 @@ public class MovieListFragment extends AbstractListFragment {
// Save the movie id // Save the movie id
viewHolder.movieId = cursor.getInt(MovieListQuery.MOVIEID); viewHolder.movieId = cursor.getInt(MovieListQuery.MOVIEID);
viewHolder.movieTitle = cursor.getString(MovieListQuery.TITLE); viewHolder.movieTitle = cursor.getString(MovieListQuery.TITLE);
viewHolder.movieTagline = cursor.getString(MovieListQuery.TAGLINE);
viewHolder.movieYear = cursor.getInt(MovieListQuery.YEAR);
viewHolder.movieRating = cursor.getDouble(MovieListQuery.RATING);
viewHolder.titleView.setText(viewHolder.movieTitle); viewHolder.titleView.setText(viewHolder.movieTitle);
String details = TextUtils.isEmpty(cursor.getString(MovieListQuery.TAGLINE)) ?
cursor.getString(MovieListQuery.GENRES) : viewHolder.movieGenres = cursor.getString(MovieListQuery.GENRES);
cursor.getString(MovieListQuery.TAGLINE); String details = TextUtils.isEmpty(viewHolder.movieTagline) ?
viewHolder.movieGenres :
viewHolder.movieTagline;
viewHolder.detailsView.setText(details); viewHolder.detailsView.setText(details);
// viewHolder.yearView.setText(String.valueOf(cursor.getInt(MovieListQuery.YEAR))); // viewHolder.yearView.setText(String.valueOf(cursor.getInt(MovieListQuery.YEAR)));
int runtime = cursor.getInt(MovieListQuery.RUNTIME) / 60; viewHolder.movieRuntime = cursor.getInt(MovieListQuery.RUNTIME) / 60;
String duration = runtime > 0 ? String duration = viewHolder.movieRuntime > 0 ?
String.format(context.getString(R.string.minutes_abbrev), String.valueOf(runtime)) + String.format(context.getString(R.string.minutes_abbrev), String.valueOf(viewHolder.movieRuntime)) +
" | " + String.valueOf(cursor.getInt(MovieListQuery.YEAR)) : " | " + viewHolder.movieYear :
String.valueOf(cursor.getInt(MovieListQuery.YEAR)); String.valueOf(viewHolder.movieYear);
viewHolder.durationView.setText(duration); viewHolder.durationView.setText(duration);
UIUtils.loadImageWithCharacterAvatar(context, hostManager, UIUtils.loadImageWithCharacterAvatar(context, hostManager,
cursor.getString(MovieListQuery.THUMBNAIL), viewHolder.movieTitle, cursor.getString(MovieListQuery.THUMBNAIL), viewHolder.movieTitle,
viewHolder.artView, artWidth, artHeight); viewHolder.artView, artWidth, artHeight);
if(Utils.isLollipopOrLater()) {
viewHolder.artView.setTransitionName("a"+viewHolder.movieId);
}
} }
} }
/** /**
* View holder pattern * View holder pattern
*/ */
private static class ViewHolder { public static class ViewHolder {
TextView titleView; TextView titleView;
TextView detailsView; TextView detailsView;
// TextView yearView; // TextView yearView;
TextView durationView; TextView durationView;
ImageView artView; ImageView artView;
int movieId; int movieId;
String movieTitle; String movieTitle;
String movieTagline;
int movieYear;
int movieRuntime;
String movieGenres;
double movieRating;
} }
} }

View File

@ -22,6 +22,7 @@ import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.transition.Transition;
import android.transition.TransitionInflater; import android.transition.TransitionInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -67,10 +68,14 @@ public class MoviesActivity extends BaseActivity
// Setup animations // Setup animations
if (Utils.isLollipopOrLater()) { if (Utils.isLollipopOrLater()) {
movieListFragment.setExitTransition(null); //Fade added to prevent shared element from disappearing very shortly at the start of the transition.
movieListFragment.setReenterTransition(TransitionInflater Transition fade = TransitionInflater
.from(this) .from(this)
.inflateTransition(android.R.transition.fade)); .inflateTransition(android.R.transition.fade);
movieListFragment.setExitTransition(fade);
movieListFragment.setReenterTransition(fade);
movieListFragment.setSharedElementReturnTransition(TransitionInflater.from(
this).inflateTransition(R.transition.change_image));
} }
getSupportFragmentManager() getSupportFragmentManager()
.beginTransaction() .beginTransaction()
@ -168,31 +173,43 @@ public class MoviesActivity extends BaseActivity
/** /**
* Callback from movielist fragment when a movie is selected. * Callback from movielist fragment when a movie is selected.
* Switch fragment in portrait * Switch fragment in portrait
* @param movieId Movie selected * @param vh ViewHolder holding movie info of item clicked
* @param movieTitle Title
*/ */
@TargetApi(21) @TargetApi(21)
public void onMovieSelected(int movieId, String movieTitle) { public void onMovieSelected(MovieListFragment.ViewHolder vh) {
selectedMovieId = movieId; selectedMovieTitle = vh.movieTitle;
selectedMovieTitle = movieTitle; selectedMovieId = vh.movieId;
MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(movieId);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
// Set up transitions // Set up transitions
if (Utils.isLollipopOrLater()) { if (Utils.isLollipopOrLater()) {
MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
movieDetailsFragment.setEnterTransition(TransitionInflater movieDetailsFragment.setEnterTransition(TransitionInflater
.from(this) .from(this)
.inflateTransition(R.transition.media_details)); .inflateTransition(R.transition.media_details));
movieDetailsFragment.setReturnTransition(null); movieDetailsFragment.setReturnTransition(null);
Transition changeImageTransition = TransitionInflater.from(
this).inflateTransition(R.transition.change_image);
movieDetailsFragment.setSharedElementReturnTransition(changeImageTransition);
movieDetailsFragment.setSharedElementEnterTransition(changeImageTransition);
fragTrans.replace(R.id.fragment_container, movieDetailsFragment)
.addToBackStack(null)
.addSharedElement(vh.artView, vh.artView.getTransitionName())
.commit();
} else { } else {
MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0, fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0,
R.anim.fragment_list_popenter, 0); R.anim.fragment_list_popenter, 0);
fragTrans.replace(R.id.fragment_container, movieDetailsFragment)
.addToBackStack(null)
.commit();
} }
fragTrans.replace(R.id.fragment_container, movieDetailsFragment)
.addToBackStack(null)
.commit();
setupActionBar(selectedMovieTitle); setupActionBar(selectedMovieTitle);
} }
} }

View File

@ -125,7 +125,7 @@
style="@style/Widget.Button.Borderless" style="@style/Widget.Button.Borderless"
android:src="?attr/iconDownload" android:src="?attr/iconDownload"
android:contentDescription="@string/download" android:contentDescription="@string/download"
android:visibility="gone"/> />
<ImageButton <ImageButton
android:id="@+id/seen" android:id="@+id/seen"
android:layout_width="@dimen/buttonbar_button_width" android:layout_width="@dimen/buttonbar_button_width"

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeTransform />
<changeBounds />
</transitionSet>

View File

@ -19,6 +19,7 @@
<fade> <fade>
<targets> <targets>
<target android:excludeId="@id/media_panel_group"/> <target android:excludeId="@id/media_panel_group"/>
<target android:excludeId="@id/fab"/>
</targets> </targets>
</fade> </fade>
<!--<slide android:slideEdge="top">--> <!--<slide android:slideEdge="top">-->