Implemented shared element transitions for music videos

This commit is contained in:
Martijn Brekhof 2015-12-11 08:29:48 +01:00
parent 32171ebfba
commit 9e3a49126d
4 changed files with 153 additions and 63 deletions

View File

@ -19,6 +19,7 @@ import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.SharedElementCallback;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
@ -66,6 +67,8 @@ public class MusicActivity extends BaseActivity
private NavigationDrawerFragment navigationDrawerFragment;
private MusicListFragment musicListFragment;
private boolean clearSharedElements;
@TargetApi(21)
@ -80,7 +83,7 @@ public class MusicActivity extends BaseActivity
navigationDrawerFragment.setUp(R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout));
if (savedInstanceState == null) {
MusicListFragment musicListFragment = new MusicListFragment();
musicListFragment = new MusicListFragment();
// Setup animations
if (Utils.isLollipopOrLater()) {
@ -88,6 +91,16 @@ public class MusicActivity extends BaseActivity
musicListFragment.setReenterTransition(TransitionInflater
.from(this)
.inflateTransition(android.R.transition.fade));
musicListFragment.setExitSharedElementCallback(new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
if (clearSharedElements) {
names.clear();
sharedElements.clear();
clearSharedElements = false;
}
}
});
}
getSupportFragmentManager()
.beginTransaction()
@ -360,20 +373,46 @@ public class MusicActivity extends BaseActivity
}
@TargetApi(21)
public void onMusicVideoSelected(int musicVideoId, String musicVideoTitle) {
selectedMusicVideoId = musicVideoId;
selectedMusicVideoTitle = musicVideoTitle;
public void onMusicVideoSelected(MusicVideoListFragment.ViewHolder vh) {
selectedMusicVideoId = vh.musicVideoId;
selectedMusicVideoTitle = vh.musicVideoTitle;
// Replace list fragment
MusicVideoDetailsFragment detailsFragment = MusicVideoDetailsFragment.newInstance(musicVideoId);
final MusicVideoDetailsFragment detailsFragment = MusicVideoDetailsFragment.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 (detailsFragment.isVisible()) {
View sharedView = detailsFragment.getSharedElement();
if (sharedView == null) { // shared element not visible
LogUtils.LOGD(TAG, "onMusicVideoSelected: setting clearedSharedElements to true");
clearSharedElements = true;
}
}
}
};
detailsFragment.setEnterSharedElementCallback(seCallback);
detailsFragment.setEnterTransition(TransitionInflater
.from(this)
.inflateTransition(R.transition.media_details));
detailsFragment.setReturnTransition(null);
Transition changeImageTransition = TransitionInflater.from(
this).inflateTransition(R.transition.change_image);
detailsFragment.setSharedElementReturnTransition(changeImageTransition);
detailsFragment.setSharedElementEnterTransition(changeImageTransition);
fragTrans.addSharedElement(vh.artView, vh.artView.getTransitionName());
} else {
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0,
R.anim.fragment_list_popenter, 0);
@ -382,6 +421,6 @@ public class MusicActivity extends BaseActivity
fragTrans.replace(R.id.fragment_container, detailsFragment)
.addToBackStack(null)
.commit();
setupActionBar(null, null, null, musicVideoTitle);
setupActionBar(null, null, null, selectedMusicVideoTitle);
}
}

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;
@ -55,6 +56,7 @@ import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.FileDownloadHelper;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import java.io.File;
import java.util.ArrayList;
@ -70,7 +72,15 @@ public class MusicVideoDetailsFragment extends AbstractDetailsFragment
implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = LogUtils.makeLogTag(MusicVideoDetailsFragment.class);
public static final String MUSICVIDEOID = "music_video_id";
public static final String BUNDLE_KEY_ID = "music_video_id";
public static final String BUNDLE_KEY_ALBUM = "album";
public static final String POSTER_TRANS_NAME = "POSTER_TRANS_NAME";
public static final String BUNDLE_KEY_ARTIST = "artist";
public static final String BUNDLE_KEY_TITLE = "title";
public static final String BUNDLE_KEY_GENRES = "genre";
public static final String BUNDLE_KEY_YEAR = "year";
public static final String BUNDLE_KEY_PLOT = "plot";
public static final String BUNDLE_KEY_RUNTIME = "runtime";
// Loader IDs
private static final int LOADER_MUSIC_VIDEO = 0;
@ -114,18 +124,32 @@ public class MusicVideoDetailsFragment extends AbstractDetailsFragment
/**
* Create a new instance of this, initialized to show the video musicVideoId
*/
public static MusicVideoDetailsFragment newInstance(int musicVideoId) {
@TargetApi(21)
public static MusicVideoDetailsFragment newInstance(MusicVideoListFragment.ViewHolder vh) {
MusicVideoDetailsFragment fragment = new MusicVideoDetailsFragment();
Bundle args = new Bundle();
args.putInt(MUSICVIDEOID, musicVideoId);
args.putInt(BUNDLE_KEY_ID, vh.musicVideoId);
args.putString(BUNDLE_KEY_TITLE, vh.musicVideoTitle);
args.putString(BUNDLE_KEY_ALBUM, vh.album);
args.putString(BUNDLE_KEY_ARTIST, vh.artist);
args.putString(BUNDLE_KEY_GENRES, vh.genres);
args.putString(BUNDLE_KEY_PLOT, vh.plot);
args.putInt(BUNDLE_KEY_RUNTIME, vh.runtime);
args.putInt(BUNDLE_KEY_YEAR, vh.year);
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) {
musicVideoId = getArguments().getInt(MUSICVIDEOID, -1);
Bundle bundle = getArguments();
musicVideoId = bundle.getInt(BUNDLE_KEY_ID, -1);
if (musicVideoId == -1) {
// There's nothing to show
@ -152,6 +176,16 @@ public class MusicVideoDetailsFragment extends AbstractDetailsFragment
FloatingActionButton fab = (FloatingActionButton)fabButton;
fab.attachToScrollView((ObservableScrollView) mediaPanel);
mediaTitle.setText(bundle.getString(BUNDLE_KEY_TITLE));
setMediaUndertitle(bundle.getString(BUNDLE_KEY_ARTIST), bundle.getString(BUNDLE_KEY_ALBUM));
setMediaYear(bundle.getInt(BUNDLE_KEY_RUNTIME), bundle.getInt(BUNDLE_KEY_YEAR));
mediaGenres.setText(bundle.getString(BUNDLE_KEY_GENRES));
mediaDescription.setText(bundle.getString(BUNDLE_KEY_PLOT));
if(Utils.isLollipopOrLater()) {
mediaPoster.setTransitionName(bundle.getString(POSTER_TRANS_NAME));
}
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false);
@ -380,16 +414,11 @@ public class MusicVideoDetailsFragment extends AbstractDetailsFragment
cursor.moveToFirst();
String musicVideoTitle = cursor.getString(MusicVideoDetailsQuery.TITLE);
mediaTitle.setText(musicVideoTitle);
String artistAlbum = cursor.getString(MusicVideoDetailsQuery.ARTIST) + " | " +
cursor.getString(MusicVideoDetailsQuery.ALBUM);
mediaUndertitle.setText(artistAlbum);
int runtime = cursor.getInt(MusicVideoDetailsQuery.RUNTIME);
String durationYear = runtime > 0 ?
UIUtils.formatTime(runtime) + " | " +
String.valueOf(cursor.getInt(MusicVideoDetailsQuery.YEAR)) :
String.valueOf(cursor.getInt(MusicVideoDetailsQuery.YEAR));
mediaYear.setText(durationYear);
setMediaUndertitle(cursor.getString(MusicVideoDetailsQuery.ARTIST), cursor.getString(MusicVideoDetailsQuery.ALBUM));
setMediaYear(cursor.getInt(MusicVideoDetailsQuery.RUNTIME), cursor.getInt(MusicVideoDetailsQuery.YEAR));
mediaGenres.setText(cursor.getString(MusicVideoDetailsQuery.GENRES));
mediaDescription.setText(cursor.getString(MusicVideoDetailsQuery.PLOT));
@ -404,41 +433,20 @@ public class MusicVideoDetailsFragment extends AbstractDetailsFragment
int artHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_art_height),
artWidth = displayMetrics.widthPixels;
if (!TextUtils.isEmpty(fanart)) {
int posterWidth = resources.getDimensionPixelOffset(R.dimen.musicvideodetail_poster_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.musicvideodetail_poster_heigth);
mediaPoster.setVisibility(View.VISIBLE);
UIUtils.loadImageWithCharacterAvatar(getActivity(), getHostManager(),
poster, musicVideoTitle,
mediaPoster, posterWidth, posterHeight);
UIUtils.loadImageIntoImageview(getHostManager(),
fanart,
mediaArt, artWidth, artHeight);
} else {
// No fanart, just present the poster
mediaPoster.setVisibility(View.GONE);
UIUtils.loadImageIntoImageview(getHostManager(),
poster,
mediaArt, artWidth, artHeight);
// Reset padding
int paddingLeft = mediaTitle.getPaddingRight(),
paddingRight = mediaTitle.getPaddingRight(),
paddingTop = mediaTitle.getPaddingTop(),
paddingBottom = mediaTitle.getPaddingBottom();
mediaTitle.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
mediaUndertitle.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
int posterWidth = resources.getDimensionPixelOffset(R.dimen.musicvideodetail_poster_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.musicvideodetail_poster_width);
UIUtils.loadImageWithCharacterAvatar(getActivity(), getHostManager(),
poster, musicVideoTitle,
mediaPoster, posterWidth, posterHeight);
UIUtils.loadImageIntoImageview(getHostManager(),
TextUtils.isEmpty(fanart)? poster : fanart,
mediaArt, artWidth, artHeight);
// Setup download info
musicVideoDownloadInfo = new FileDownloadHelper.MusicVideoInfo(
musicVideoTitle, cursor.getString(MusicVideoDetailsQuery.FILE));
// Check if downloaded file exists
downloadButton.setVisibility(View.VISIBLE);
if (musicVideoDownloadInfo.downloadFileExists()) {
Resources.Theme theme = getActivity().getTheme();
TypedArray styledAttributes = theme.obtainStyledAttributes(new int[]{
@ -452,6 +460,30 @@ public class MusicVideoDetailsFragment extends AbstractDetailsFragment
}
}
private void setMediaUndertitle(String artist, String album) {
mediaUndertitle.setText(artist + " | " + album);
}
private void setMediaYear(int runtime, int year) {
String durationYear = runtime > 0 ?
UIUtils.formatTime(runtime) + " | " +
String.valueOf(year) :
String.valueOf(year);
mediaYear.setText(durationYear);
}
/**
* Returns the shared element if visible
* @return View if visible, null otherwise
*/
public View getSharedElement() {
if (UIUtils.isViewInBounds(mediaPanel, mediaPoster)) {
return mediaPoster;
}
return null;
}
/**
* Video details query parameters.
*/

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.res.Resources;
@ -44,6 +45,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 artists list
@ -52,7 +54,7 @@ public class MusicVideoListFragment extends AbstractListFragment {
private static final String TAG = LogUtils.makeLogTag(MusicVideoListFragment.class);
public interface OnMusicVideoSelectedListener {
public void onMusicVideoSelected(int musicVideoId, String musicVideoTitle);
public void onMusicVideoSelected(ViewHolder vh);
}
// Activity listener
@ -69,7 +71,7 @@ public class MusicVideoListFragment extends AbstractListFragment {
// Get the movie id from the tag
ViewHolder tag = (ViewHolder)view.getTag();
// Notify the activity
listenerActivity.onMusicVideoSelected(tag.musicVideoId, tag.musicVideoTitle);
listenerActivity.onMusicVideoSelected(tag);
}
};
}
@ -141,6 +143,8 @@ public class MusicVideoListFragment extends AbstractListFragment {
MediaContract.MusicVideos.THUMBNAIL,
MediaContract.MusicVideos.RUNTIME,
MediaContract.MusicVideos.GENRES,
MediaContract.MusicVideos.YEAR,
MediaContract.MusicVideos.PLOT,
};
String SORT = MediaDatabase.sortCommonTokens(MediaContract.MusicVideos.TITLE) + " ASC";
@ -153,6 +157,8 @@ public class MusicVideoListFragment extends AbstractListFragment {
final int THUMBNAIL = 5;
final int RUNTIME = 6;
final int GENRES = 7;
final int YEAR = 8;
final int PLOT = 9;
}
private static class MusicVideosAdapter extends CursorAdapter {
@ -166,10 +172,8 @@ public class MusicVideoListFragment extends AbstractListFragment {
// Get the art dimensions
Resources resources = context.getResources();
artWidth = (int)(resources.getDimension(R.dimen.musicvideolist_art_width) /
UIUtils.IMAGE_RESIZE_FACTOR);
artHeight = (int)(resources.getDimension(R.dimen.musicvideolist_art_heigth) /
UIUtils.IMAGE_RESIZE_FACTOR);
artHeight = resources.getDimensionPixelOffset(R.dimen.musicvideodetail_poster_heigth);
artWidth = resources.getDimensionPixelOffset(R.dimen.musicvideodetail_poster_width);
}
/** {@inheritDoc} */
@ -190,6 +194,7 @@ public class MusicVideoListFragment extends AbstractListFragment {
}
/** {@inheritDoc} */
@TargetApi(21)
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewHolder viewHolder = (ViewHolder)view.getTag();
@ -197,28 +202,37 @@ public class MusicVideoListFragment extends AbstractListFragment {
// Save the movie id
viewHolder.musicVideoId = cursor.getInt(MusicVideosListQuery.MUSICVIDEOID);
viewHolder.musicVideoTitle = cursor.getString(MusicVideosListQuery.TITLE);
viewHolder.album = cursor.getString(MusicVideosListQuery.ALBUM);
viewHolder.artist = cursor.getString(MusicVideosListQuery.ARTIST);
viewHolder.genres = cursor.getString(MusicVideosListQuery.GENRES);
viewHolder.plot = cursor.getString(MusicVideosListQuery.PLOT);
viewHolder.runtime = cursor.getInt(MusicVideosListQuery.RUNTIME);
viewHolder.year = cursor.getInt(MusicVideosListQuery.YEAR);
viewHolder.titleView.setText(viewHolder.musicVideoTitle);
String artistAlbum = cursor.getString(MusicVideosListQuery.ARTIST) + " | " +
cursor.getString(MusicVideosListQuery.ALBUM);
String artistAlbum = viewHolder.artist + " | " +
viewHolder.album;
viewHolder.artistAlbumView.setText(artistAlbum);
int runtime = cursor.getInt(MusicVideosListQuery.RUNTIME);
String durationGenres =
runtime > 0 ?
UIUtils.formatTime(runtime) + " | " + cursor.getString(MusicVideosListQuery.GENRES) :
cursor.getString(MusicVideosListQuery.GENRES);
viewHolder.runtime > 0 ?
UIUtils.formatTime(viewHolder.runtime) + " | " + viewHolder.genres :
viewHolder.genres;
viewHolder.durationGenresView.setText(durationGenres);
UIUtils.loadImageWithCharacterAvatar(context, hostManager,
cursor.getString(MusicVideosListQuery.THUMBNAIL), viewHolder.musicVideoTitle,
viewHolder.artView, artWidth, artHeight);
if(Utils.isLollipopOrLater()) {
viewHolder.artView.setTransitionName("a"+viewHolder.musicVideoId);
}
}
}
/**
* View holder pattern
*/
private static class ViewHolder {
public static class ViewHolder {
TextView titleView;
TextView artistAlbumView;
TextView durationGenresView;
@ -226,5 +240,11 @@ public class MusicVideoListFragment extends AbstractListFragment {
int musicVideoId;
String musicVideoTitle;
String artist;
String album;
int runtime;
String genres;
int year;
String plot;
}
}

View File

@ -115,8 +115,7 @@
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconDownload"
android:contentDescription="@string/download"
android:visibility="gone"/>
android:contentDescription="@string/download"/>
</LinearLayout>
<RelativeLayout