Merge pull request #145 from poisdeux/sharedelementtransitions_tvshows

Implemented shared element transition for TV shows
This commit is contained in:
Synced Synapse 2015-12-10 23:04:16 +00:00
commit fce521a9bb
6 changed files with 207 additions and 67 deletions

View File

@ -15,6 +15,7 @@
*/
package org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
@ -23,9 +24,12 @@ import android.view.View;
import android.view.ViewGroup;
import com.astuetz.PagerSlidingTabStrip;
import org.xbmc.kore.R;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.TabsAdapter;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import butterknife.ButterKnife;
import butterknife.InjectView;
@ -36,22 +40,45 @@ import butterknife.InjectView;
public class TVShowDetailsFragment extends Fragment {
private static final String TAG = LogUtils.makeLogTag(TVShowDetailsFragment.class);
public static final String TVSHOWID = "tvshow_id";
public static final String POSTER_TRANS_NAME = "POSTER_TRANS_NAME";
public static final String BUNDLE_KEY_TVSHOWID = "tvshow_id";
public static final String BUNDLE_KEY_TITLE = "title";
public static final String BUNDLE_KEY_PREMIERED = "premiered";
public static final String BUNDLE_KEY_STUDIO = "studio";
public static final String BUNDLE_KEY_EPISODE = "episode";
public static final String BUNDLE_KEY_WATCHEDEPISODES = "watchedepisodes";
public static final String BUNDLE_KEY_RATING = "rating";
public static final String BUNDLE_KEY_PLOT = "plot";
public static final String BUNDLE_KEY_GENRES = "genres";
// Displayed movie id
private int tvshowId = -1;
private TabsAdapter tabsAdapter;
@InjectView(R.id.pager_tab_strip) PagerSlidingTabStrip pagerTabStrip;
@InjectView(R.id.pager) ViewPager viewPager;
/**
* Create a new instance of this, initialized to show tvshowId
*/
public static TVShowDetailsFragment newInstance(int tvshowId) {
@TargetApi(21)
public static TVShowDetailsFragment newInstance(TVShowListFragment.ViewHolder vh) {
TVShowDetailsFragment fragment = new TVShowDetailsFragment();
Bundle args = new Bundle();
args.putInt(TVSHOWID, tvshowId);
args.putInt(BUNDLE_KEY_TVSHOWID, vh.tvshowId);
args.putInt(BUNDLE_KEY_EPISODE, vh.episode);
args.putString(BUNDLE_KEY_GENRES, vh.genres);
args.putString(BUNDLE_KEY_PLOT, vh.plot);
args.putString(BUNDLE_KEY_PREMIERED, vh.premiered);
args.putDouble(BUNDLE_KEY_RATING, vh.rating);
args.putString(BUNDLE_KEY_STUDIO, vh.studio);
args.putString(BUNDLE_KEY_TITLE, vh.tvshowTitle);
args.putInt(BUNDLE_KEY_WATCHEDEPISODES, vh.watchedEpisodes);
if( Utils.isLollipopOrLater()) {
args.putString(POSTER_TRANS_NAME, vh.artView.getTransitionName());
}
fragment.setArguments(args);
return fragment;
}
@ -63,7 +90,7 @@ public class TVShowDetailsFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
tvshowId = getArguments().getInt(TVSHOWID, -1);
tvshowId = getArguments().getInt(BUNDLE_KEY_TVSHOWID, -1);
if ((container == null) || (tvshowId == -1)) {
// We're not being shown or there's nothing to show
@ -74,7 +101,7 @@ public class TVShowDetailsFragment extends Fragment {
ButterKnife.inject(this, root);
long baseFragmentId = tvshowId * 10;
TabsAdapter tabsAdapter = new TabsAdapter(getActivity(), getChildFragmentManager())
tabsAdapter = new TabsAdapter(getActivity(), getChildFragmentManager())
.addTab(TVShowOverviewFragment.class, getArguments(), R.string.tvshow_overview,
baseFragmentId)
.addTab(TVShowEpisodeListFragment.class, getArguments(),
@ -92,19 +119,26 @@ public class TVShowDetailsFragment extends Fragment {
setHasOptionsMenu(false);
}
@Override
public void onResume() {
super.onResume();
public Fragment getCurrentTabFragment() {
return tabsAdapter.getItem(viewPager.getCurrentItem());
}
@Override
public void onPause() {
super.onPause();
}
public View getSharedElement() {
View view = getView();
if (view == null)
return null;
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// outState.putInt(TVSHOWID, tvshowId);
//Note: this works as R.id.poster is only used in TVShowOverviewFragment.
//If the same id is used in other fragments in the TabsAdapter we
//need to check which fragment is currently displayed
View artView = view.findViewById(R.id.poster);
View scrollView = view.findViewById(R.id.media_panel);
if (( artView != null ) &&
( scrollView != null ) &&
UIUtils.isViewInBounds(scrollView, artView)) {
return artView;
}
return null;
}
}

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 tv show list
@ -55,7 +57,7 @@ public class TVShowListFragment extends AbstractListFragment {
private static final String TAG = LogUtils.makeLogTag(TVShowListFragment.class);
public interface OnTVShowSelectedListener {
public void onTVShowSelected(int tvshowId, String tvshowTitle);
public void onTVShowSelected(TVShowListFragment.ViewHolder vh);
}
// Activity listener
@ -72,7 +74,7 @@ public class TVShowListFragment extends AbstractListFragment {
// Get the movie id from the tag
ViewHolder tag = (ViewHolder) view.getTag();
// Notify the activity
listenerActivity.onTVShowSelected(tag.tvshowId, tag.tvshowTitle);
listenerActivity.onTVShowSelected(tag);
}
};
}
@ -225,9 +227,16 @@ public class TVShowListFragment extends AbstractListFragment {
MediaContract.TVShows.TVSHOWID,
MediaContract.TVShows.TITLE,
MediaContract.TVShows.THUMBNAIL,
MediaContract.TVShows.FANART,
MediaContract.TVShows.PREMIERED,
MediaContract.TVShows.STUDIO,
MediaContract.TVShows.EPISODE,
MediaContract.TVShows.WATCHEDEPISODES,
MediaContract.TVShows.RATING,
MediaContract.TVShows.PLOT,
MediaContract.TVShows.PLAYCOUNT,
MediaContract.TVShows.IMDBNUMBER,
MediaContract.TVShows.GENRES,
};
String SORT_BY_NAME = MediaContract.TVShows.TITLE + " ASC";
@ -238,9 +247,16 @@ public class TVShowListFragment extends AbstractListFragment {
final int TVSHOWID = 1;
final int TITLE = 2;
final int THUMBNAIL = 3;
final int PREMIERED = 4;
final int EPISODE = 5;
final int WATCHEDEPISODES = 6;
final int FANART = 4;
final int PREMIERED = 5;
final int STUDIO = 6;
final int EPISODE = 7;
final int WATCHEDEPISODES = 8;
final int RATING = 9;
final int PLOT = 10;
final int PLAYCOUNT = 11;
final int IMDBNUMBER = 12;
final int GENRES = 13;
}
private static class TVShowsAdapter extends CursorAdapter {
@ -257,16 +273,16 @@ public class TVShowListFragment 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_tvshow, parent, false);
.inflate(R.layout.grid_item_tvshow, parent, false);
// Setup View holder pattern
ViewHolder viewHolder = new ViewHolder();
@ -281,6 +297,7 @@ public class TVShowListFragment extends AbstractListFragment {
}
/** {@inheritDoc} */
@TargetApi(21)
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewHolder viewHolder = (ViewHolder)view.getTag();
@ -288,16 +305,25 @@ public class TVShowListFragment extends AbstractListFragment {
// Save the movie id
viewHolder.tvshowId = cursor.getInt(TVShowListQuery.TVSHOWID);
viewHolder.tvshowTitle = cursor.getString(TVShowListQuery.TITLE);
viewHolder.episode = cursor.getInt(TVShowListQuery.EPISODE);
viewHolder.genres = cursor.getString(TVShowListQuery.GENRES);
viewHolder.plot = cursor.getString(TVShowListQuery.PLOT);
viewHolder.premiered = cursor.getString(TVShowListQuery.PREMIERED);
viewHolder.rating = cursor.getInt(TVShowListQuery.RATING);
viewHolder.studio = cursor.getString(TVShowListQuery.STUDIO);
viewHolder.watchedEpisodes = cursor.getInt(TVShowListQuery.WATCHEDEPISODES);
if(Utils.isLollipopOrLater()) {
viewHolder.artView.setTransitionName("a"+viewHolder.tvshowId);
}
viewHolder.titleView.setText(viewHolder.tvshowTitle);
int numEpisodes = cursor.getInt(TVShowListQuery.EPISODE),
watchedEpisodes = cursor.getInt(TVShowListQuery.WATCHEDEPISODES);
String details = String.format(context.getString(R.string.num_episodes),
numEpisodes, numEpisodes - watchedEpisodes);
viewHolder.episode, viewHolder.episode - viewHolder.watchedEpisodes);
viewHolder.detailsView.setText(details);
String premiered = String.format(context.getString(R.string.premiered),
cursor.getString(TVShowListQuery.PREMIERED));
viewHolder.premiered);
viewHolder.premieredView.setText(premiered);
UIUtils.loadImageWithCharacterAvatar(context, hostManager,
cursor.getString(TVShowListQuery.THUMBNAIL), viewHolder.tvshowTitle,
@ -308,14 +334,21 @@ public class TVShowListFragment extends AbstractListFragment {
/**
* View holder pattern
*/
private static class ViewHolder {
public static class ViewHolder {
TextView titleView;
TextView detailsView;
// TextView yearView;
// TextView yearView;
TextView premieredView;
ImageView artView;
int tvshowId;
String tvshowTitle;
String premiered;
String studio;
int episode;
int watchedEpisodes;
double rating;
String plot;
String genres;
}
}

View File

@ -15,6 +15,7 @@
*/
package org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
@ -42,6 +43,7 @@ import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import java.util.ArrayList;
@ -105,8 +107,10 @@ public class TVShowOverviewFragment extends AbstractDetailsFragment
}
@Override
@TargetApi(21)
protected View createView(LayoutInflater inflater, ViewGroup container) {
tvshowId = getArguments().getInt(TVSHOWID, -1);
Bundle bundle = getArguments();
tvshowId = bundle.getInt(TVShowDetailsFragment.BUNDLE_KEY_TVSHOWID, -1);
if (tvshowId == -1) {
// There's nothing to show
@ -130,6 +134,18 @@ public class TVShowOverviewFragment extends AbstractDetailsFragment
}
});
tvshowTitle = bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_TITLE);
mediaTitle.setText(tvshowTitle);
setMediaUndertitle(bundle.getInt(TVShowDetailsFragment.BUNDLE_KEY_EPISODE), bundle.getInt(TVShowDetailsFragment.BUNDLE_KEY_WATCHEDEPISODES));
setMediaPremiered(bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_PREMIERED), bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_STUDIO));
mediaGenres.setText(bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_GENRES));
setMediaRating(bundle.getDouble(TVShowDetailsFragment.BUNDLE_KEY_RATING));
mediaDescription.setText(bundle.getString(TVShowDetailsFragment.BUNDLE_KEY_PLOT));
if(Utils.isLollipopOrLater()) {
mediaPoster.setTransitionName(getArguments().getString(TVShowDetailsFragment.POSTER_TRANS_NAME));
}
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false);
@ -244,26 +260,13 @@ public class TVShowOverviewFragment extends AbstractDetailsFragment
mediaTitle.setText(tvshowTitle);
int numEpisodes = cursor.getInt(TVShowDetailsQuery.EPISODE),
watchedEpisodes = cursor.getInt(TVShowDetailsQuery.WATCHEDEPISODES);
String episodes = String.format(getString(R.string.num_episodes),
numEpisodes, numEpisodes - watchedEpisodes);
mediaUndertitle.setText(episodes);
setMediaUndertitle(numEpisodes, watchedEpisodes);
setMediaPremiered(cursor.getString(TVShowDetailsQuery.PREMIERED), cursor.getString(TVShowDetailsQuery.STUDIO));
String premiered = String.format(getString(R.string.premiered),
cursor.getString(TVShowDetailsQuery.PREMIERED)) +
" | " + cursor.getString(TVShowDetailsQuery.STUDIO);
mediaPremiered.setText(premiered);
mediaGenres.setText(cursor.getString(TVShowDetailsQuery.GENRES));
double rating = cursor.getDouble(TVShowDetailsQuery.RATING);
if (rating > 0) {
mediaRating.setVisibility(View.VISIBLE);
mediaMaxRating.setVisibility(View.VISIBLE);
mediaRating.setText(String.format("%01.01f", rating));
mediaMaxRating.setText(getString(R.string.max_rating_video));
} else {
mediaRating.setVisibility(View.INVISIBLE);
mediaMaxRating.setVisibility(View.INVISIBLE);
}
setMediaRating(cursor.getDouble(TVShowDetailsQuery.RATING));
mediaDescription.setText(cursor.getString(TVShowDetailsQuery.PLOT));
@ -286,6 +289,28 @@ public class TVShowOverviewFragment extends AbstractDetailsFragment
mediaArt, displayMetrics.widthPixels, artHeight);
}
private void setMediaUndertitle(int numEpisodes, int watchedEpisodes) {
String episodes = String.format(getString(R.string.num_episodes),
numEpisodes, numEpisodes - watchedEpisodes);
mediaUndertitle.setText(episodes);
}
private void setMediaPremiered(String premiered, String studio) {
mediaPremiered.setText(String.format(getString(R.string.premiered),
premiered) + " | " + studio);
}
private void setMediaRating(double rating) {
if (rating > 0) {
mediaRating.setVisibility(View.VISIBLE);
mediaMaxRating.setVisibility(View.VISIBLE);
mediaRating.setText(String.format("%01.01f", rating));
mediaMaxRating.setText(getString(R.string.max_rating_video));
} else {
mediaRating.setVisibility(View.INVISIBLE);
mediaMaxRating.setVisibility(View.INVISIBLE);
}
}
/**
* Display the cast details
*

View File

@ -22,15 +22,20 @@ 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;
import android.view.View;
import android.view.Window;
import org.xbmc.kore.R;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.Utils;
import java.util.List;
import java.util.Map;
/**
* Controls the presentation of TV Shows information (list, details)
* All the information is presented by specific fragments
@ -48,6 +53,9 @@ public class TVShowsActivity extends BaseActivity
private String selectedTVShowTitle = null;
private int selectedEpisodeId = -1;
private TVShowDetailsFragment tvshowDetailsFragment;
private boolean clearSharedElements;
private NavigationDrawerFragment navigationDrawerFragment;
@TargetApi(21)
@ -70,10 +78,27 @@ public class TVShowsActivity extends BaseActivity
// Setup animations
if (Utils.isLollipopOrLater()) {
tvshowListFragment.setExitTransition(null);
tvshowListFragment.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);
tvshowListFragment.setExitTransition(fade);
tvshowListFragment.setReenterTransition(fade);
tvshowListFragment.setSharedElementReturnTransition(TransitionInflater.from(
this).inflateTransition(R.transition.change_image));
android.support.v4.app.SharedElementCallback seCallback = new android.support.v4.app.SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
if (clearSharedElements) {
names.clear();
sharedElements.clear();
clearSharedElements = false;
}
}
};
tvshowListFragment.setExitSharedElementCallback(seCallback);
}
getSupportFragmentManager()
.beginTransaction()
@ -93,16 +118,6 @@ public class TVShowsActivity extends BaseActivity
// UIUtils.setPaddingForSystemBars(this, findViewById(R.id.drawer_layout), true, true, true);
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onPause() {
super.onPause();
}
@Override
protected void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
@ -192,24 +207,46 @@ public class TVShowsActivity extends BaseActivity
/**
* Callback from tvshows list fragment when a show is selected.
* Switch fragment in portrait
* @param tvshowId Selected show
* @param tvshowTitle Title
* @param vh
*/
@TargetApi(21)
public void onTVShowSelected(int tvshowId, String tvshowTitle) {
selectedTVShowId = tvshowId;
selectedTVShowTitle = tvshowTitle;
public void onTVShowSelected(TVShowListFragment.ViewHolder vh) {
selectedTVShowId = vh.tvshowId;
selectedTVShowTitle = vh.tvshowTitle;
// Replace list fragment
TVShowDetailsFragment tvshowDetailsFragment = TVShowDetailsFragment.newInstance(tvshowId);
tvshowDetailsFragment = TVShowDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
// Set up transitions
if (Utils.isLollipopOrLater()) {
android.support.v4.app.SharedElementCallback seCallback = new android.support.v4.app.SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
//On returning onMapSharedElements for the exiting fragment is called before the onMapSharedElements
// for the reentering fragment. We use this to determine if we are returning and if
// we should clear the shared element lists. Note that, clearing must be done in the reentering fragment
// as this is called last. Otherwise it the app will crash during transition setup. Not sure, but might
// be a v4 support package bug.
if (tvshowDetailsFragment.isVisible()) {
View sharedView = tvshowDetailsFragment.getSharedElement();
if (sharedView == null) { // shared element not visible
clearSharedElements = true;
}
}
}
};
tvshowDetailsFragment.setEnterSharedElementCallback(seCallback);
tvshowDetailsFragment.setEnterTransition(TransitionInflater
.from(this)
.inflateTransition(R.transition.media_details));
tvshowDetailsFragment.setReturnTransition(null);
Transition changeImageTransition = TransitionInflater.from(
this).inflateTransition(R.transition.change_image);
tvshowDetailsFragment.setSharedElementReturnTransition(changeImageTransition);
tvshowDetailsFragment.setSharedElementEnterTransition(changeImageTransition);
fragTrans.addSharedElement(vh.artView, vh.artView.getTransitionName());
} else {
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0,
R.anim.fragment_list_popenter, 0);

View File

@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
@ -462,4 +463,13 @@ public class UIUtils {
}
});
}
/**
* Returns true if {@param view} is contained within {@param container}'s bounds.
*/
public static boolean isViewInBounds(@NonNull View container, @NonNull View view) {
Rect containerBounds = new Rect();
container.getHitRect(containerBounds);
return view.getLocalVisibleRect(containerBounds);
}
}

View File

@ -18,6 +18,7 @@
<fade>
<targets>
<target android:targetId="@id/art"/>
<target android:targetId="@id/pager_tab_strip"/>
</targets>
</fade>