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;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.res.Resources;
@ -44,6 +45,7 @@ import android.widget.Toast;
import com.melnykov.fab.FloatingActionButton;
import com.melnykov.fab.ObservableScrollView;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.jsonrpc.ApiCallback;
@ -74,8 +76,14 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
implements LoaderManager.LoaderCallbacks<Cursor> {
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
private static final int LOADER_MOVIE = 0,
LOADER_CAST = 1;
@ -129,18 +137,31 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
/**
* 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();
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);
return fragment;
}
@TargetApi(21)
@Override
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) {
// There's nothing to show
@ -167,10 +188,20 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
FloatingActionButton fab = (FloatingActionButton)fabButton;
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
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false);
return root;
}
@ -209,9 +240,18 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
public void onResume() {
// Force the exit view to 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();
}
@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
protected void onSyncProcessEnded(MediaSyncEvent event) {
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
@ -456,12 +496,8 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
mediaTitle.setText(movieTitle);
mediaUndertitle.setText(cursor.getString(MovieDetailsQuery.TAGLINE));
int runtime = cursor.getInt(MovieDetailsQuery.RUNTIME) / 60;
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);
setMediaYear(cursor.getInt(MovieDetailsQuery.RUNTIME) / 60, cursor.getInt(MovieDetailsQuery.YEAR));
mediaGenres.setText(cursor.getString(MovieDetailsQuery.GENRES));
double rating = cursor.getDouble(MovieDetailsQuery.RATING);
@ -469,8 +505,7 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
mediaRating.setVisibility(View.VISIBLE);
mediaMaxRating.setVisibility(View.VISIBLE);
mediaRatingVotes.setVisibility(View.VISIBLE);
mediaRating.setText(String.format("%01.01f", rating));
mediaMaxRating.setText(getString(R.string.max_rating_video));
setMediaRating(rating);
String votes = cursor.getString(MovieDetailsQuery.VOTES);
mediaRatingVotes.setText((TextUtils.isEmpty(votes)) ?
"" : String.format(getString(R.string.votes), votes));
@ -508,7 +543,6 @@ public class MovieDetailsFragment extends AbstractDetailsFragment
movieTitle, cursor.getString(MovieDetailsQuery.FILE));
// Check if downloaded file exists
downloadButton.setVisibility(View.VISIBLE);
if (movieDownloadInfo.downloadFileExists()) {
Resources.Theme theme = getActivity().getTheme();
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) {
// Seen button
if (playcount > 0) {

View File

@ -15,6 +15,7 @@
*/
package org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
@ -47,6 +48,7 @@ import org.xbmc.kore.provider.MediaDatabase;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
/**
* Fragment that presents the movie list
@ -55,7 +57,7 @@ public class MovieListFragment extends AbstractListFragment {
private static final String TAG = LogUtils.makeLogTag(MovieListFragment.class);
public interface OnMovieSelectedListener {
public void onMovieSelected(int movieId, String movieTitle);
public void onMovieSelected(ViewHolder vh);
}
// Activity listener
@ -72,7 +74,7 @@ public class MovieListFragment extends AbstractListFragment {
// Get the movie id from the tag
ViewHolder tag = (ViewHolder) view.getTag();
// 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
Resources resources = context.getResources();
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) /
UIUtils.IMAGE_RESIZE_FACTOR);
UIUtils.IMAGE_RESIZE_FACTOR);
}
/** {@inheritDoc} */
@Override
public View newView(Context context, final Cursor cursor, ViewGroup parent) {
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
ViewHolder viewHolder = new ViewHolder();
@ -277,6 +279,7 @@ public class MovieListFragment extends AbstractListFragment {
}
/** {@inheritDoc} */
@TargetApi(21)
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewHolder viewHolder = (ViewHolder)view.getTag();
@ -284,36 +287,50 @@ public class MovieListFragment extends AbstractListFragment {
// Save the movie id
viewHolder.movieId = cursor.getInt(MovieListQuery.MOVIEID);
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);
String details = TextUtils.isEmpty(cursor.getString(MovieListQuery.TAGLINE)) ?
cursor.getString(MovieListQuery.GENRES) :
cursor.getString(MovieListQuery.TAGLINE);
viewHolder.movieGenres = cursor.getString(MovieListQuery.GENRES);
String details = TextUtils.isEmpty(viewHolder.movieTagline) ?
viewHolder.movieGenres :
viewHolder.movieTagline;
viewHolder.detailsView.setText(details);
// viewHolder.yearView.setText(String.valueOf(cursor.getInt(MovieListQuery.YEAR)));
int runtime = cursor.getInt(MovieListQuery.RUNTIME) / 60;
String duration = runtime > 0 ?
String.format(context.getString(R.string.minutes_abbrev), String.valueOf(runtime)) +
" | " + String.valueOf(cursor.getInt(MovieListQuery.YEAR)) :
String.valueOf(cursor.getInt(MovieListQuery.YEAR));
viewHolder.movieRuntime = cursor.getInt(MovieListQuery.RUNTIME) / 60;
String duration = viewHolder.movieRuntime > 0 ?
String.format(context.getString(R.string.minutes_abbrev), String.valueOf(viewHolder.movieRuntime)) +
" | " + viewHolder.movieYear :
String.valueOf(viewHolder.movieYear);
viewHolder.durationView.setText(duration);
UIUtils.loadImageWithCharacterAvatar(context, hostManager,
cursor.getString(MovieListQuery.THUMBNAIL), viewHolder.movieTitle,
viewHolder.artView, artWidth, artHeight);
if(Utils.isLollipopOrLater()) {
viewHolder.artView.setTransitionName("a"+viewHolder.movieId);
}
}
}
/**
* View holder pattern
*/
private static class ViewHolder {
public static class ViewHolder {
TextView titleView;
TextView detailsView;
// TextView yearView;
// TextView yearView;
TextView durationView;
ImageView artView;
int movieId;
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.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.transition.Transition;
import android.transition.TransitionInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -67,10 +68,14 @@ public class MoviesActivity extends BaseActivity
// Setup animations
if (Utils.isLollipopOrLater()) {
movieListFragment.setExitTransition(null);
movieListFragment.setReenterTransition(TransitionInflater
//Fade added to prevent shared element from disappearing very shortly at the start of the transition.
Transition fade = TransitionInflater
.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()
.beginTransaction()
@ -168,31 +173,43 @@ public class MoviesActivity extends BaseActivity
/**
* Callback from movielist fragment when a movie is selected.
* Switch fragment in portrait
* @param movieId Movie selected
* @param movieTitle Title
* @param vh ViewHolder holding movie info of item clicked
*/
@TargetApi(21)
public void onMovieSelected(int movieId, String movieTitle) {
selectedMovieId = movieId;
selectedMovieTitle = movieTitle;
MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(movieId);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
public void onMovieSelected(MovieListFragment.ViewHolder vh) {
selectedMovieTitle = vh.movieTitle;
selectedMovieId = vh.movieId;
// Set up transitions
if (Utils.isLollipopOrLater()) {
MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
movieDetailsFragment.setEnterTransition(TransitionInflater
.from(this)
.inflateTransition(R.transition.media_details));
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 {
MovieDetailsFragment movieDetailsFragment = MovieDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 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);
}
}

View File

@ -125,7 +125,7 @@
style="@style/Widget.Button.Borderless"
android:src="?attr/iconDownload"
android:contentDescription="@string/download"
android:visibility="gone"/>
/>
<ImageButton
android:id="@+id/seen"
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>
<targets>
<target android:excludeId="@id/media_panel_group"/>
<target android:excludeId="@id/fab"/>
</targets>
</fade>
<!--<slide android:slideEdge="top">-->