Implemented shared element transitions for albums

I removed using the poster as fanart if no fanart is available.
The reason is that the shared element transition is not very smooth
when the aspect ratio of the two image states is not the same.
Going from AlbumListFragment to AlbumDetailsFragment the poster
is resized correctly to the fanart size. However, returning to the
AlbumListFragment from the AlbumDetailsFragment the poster is
abruptly resized to the square aspect ratio needed for the list
fragment.
This commit is contained in:
Martijn Brekhof 2015-12-03 16:04:56 +01:00
parent eb61844a8e
commit ffa96521f6
4 changed files with 151 additions and 103 deletions

View File

@ -59,6 +59,7 @@ import org.xbmc.kore.provider.MediaContract;
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;
@ -76,7 +77,13 @@ public class AlbumDetailsFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = LogUtils.makeLogTag(AlbumDetailsFragment.class);
public static final String ALBUMID = "album_id";
public static final String BUNDLE_KEY_ALBUMID = "album_id";
public static final String POSTER_TRANS_NAME = "POSTER_TRANS_NAME";
public static final String BUNDLE_KEY_ALBUMARTIST = "album_artist";
public static final String BUNDLE_KEY_ALBUMTITLE = "album_title";
public static final String BUNDLE_KEY_ALBUMGENRE = "album_genre";
public static final String BUNDLE_KEY_ALBUMYEAR = "album_year";
public static final String BUNDLE_KEY_ALBUMRATING = "album_rating";
// Loader IDs
private static final int LOADER_ALBUM = 0,
@ -84,7 +91,6 @@ public class AlbumDetailsFragment extends Fragment
private HostManager hostManager;
private HostInfo hostInfo;
private EventBus bus;
/**
* Handler on which to post RPC callbacks
@ -128,11 +134,20 @@ public class AlbumDetailsFragment extends Fragment
/**
* Create a new instance of this, initialized to show the album albumId
*/
public static AlbumDetailsFragment newInstance(int albumId) {
public static AlbumDetailsFragment newInstance(AlbumListFragment.ViewHolder vh) {
AlbumDetailsFragment fragment = new AlbumDetailsFragment();
Bundle args = new Bundle();
args.putInt(ALBUMID, albumId);
args.putInt(BUNDLE_KEY_ALBUMID, vh.albumId);
args.putString(BUNDLE_KEY_ALBUMTITLE, vh.albumTitle);
args.putString(BUNDLE_KEY_ALBUMARTIST, vh.albumArtist);
args.putString(BUNDLE_KEY_ALBUMGENRE, vh.albumGenre);
args.putInt(BUNDLE_KEY_ALBUMYEAR, vh.albumYear);
args.putDouble(BUNDLE_KEY_ALBUMRATING, vh.albumRating);
if( Utils.isLollipopOrLater()) {
args.putString(POSTER_TRANS_NAME, vh.artView.getTransitionName());
}
fragment.setArguments(args);
return fragment;
}
@ -144,7 +159,7 @@ public class AlbumDetailsFragment extends Fragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
albumId = getArguments().getInt(ALBUMID, -1);
albumId = getArguments().getInt(BUNDLE_KEY_ALBUMID, -1);
if ((container == null) || (albumId == -1)) {
// We're not being shown or there's nothing to show
@ -154,7 +169,6 @@ public class AlbumDetailsFragment extends Fragment
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_album_details, container, false);
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
hostManager = HostManager.getInstance(getActivity());
hostInfo = hostManager.getHostInfo();
@ -173,6 +187,17 @@ public class AlbumDetailsFragment extends Fragment
FloatingActionButton fab = (FloatingActionButton)fabButton;
fab.attachToScrollView((ObservableScrollView) mediaPanel);
Bundle bundle = getArguments();
if(Utils.isLollipopOrLater()) {
mediaPoster.setTransitionName(bundle.getString(POSTER_TRANS_NAME));
}
mediaTitle.setText(bundle.getString(BUNDLE_KEY_ALBUMTITLE));
mediaUndertitle.setText(bundle.getString(BUNDLE_KEY_ALBUMARTIST));
setMediaYear(bundle.getString(BUNDLE_KEY_ALBUMGENRE), bundle.getInt(BUNDLE_KEY_ALBUMYEAR));
setMediaRating(bundle.getDouble(BUNDLE_KEY_ALBUMRATING));
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false);
@ -197,11 +222,6 @@ public class AlbumDetailsFragment extends Fragment
super.onResume();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@ -281,7 +301,7 @@ public class AlbumDetailsFragment extends Fragment
if (!isAdded()) return;
// Got an error, show toast
Toast.makeText(getActivity(), R.string.unable_to_connect_to_xbmc, Toast.LENGTH_SHORT)
.show();
.show();
}
}, callbackHandler);
}
@ -294,7 +314,7 @@ public class AlbumDetailsFragment extends Fragment
@OnClick(R.id.download)
public void onDownloadClicked(View v) {
if ((albumTitle == null) || (albumDisplayArtist == null) ||
(songInfoList == null) || (songInfoList.size() == 0)) {
(songInfoList == null) || (songInfoList.size() == 0)) {
// Nothing to download
Toast.makeText(getActivity(), R.string.no_files_to_download, Toast.LENGTH_SHORT).show();
return;
@ -311,16 +331,16 @@ public class AlbumDetailsFragment extends Fragment
if (file.exists()) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.download)
.setMessage(R.string.download_dir_exists)
.setPositiveButton(R.string.overwrite,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FileDownloadHelper.downloadFiles(getActivity(), hostInfo,
songInfoList, FileDownloadHelper.OVERWRITE_FILES,
callbackHandler);
}
})
.setMessage(R.string.download_dir_exists)
.setPositiveButton(R.string.overwrite,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FileDownloadHelper.downloadFiles(getActivity(), hostInfo,
songInfoList, FileDownloadHelper.OVERWRITE_FILES,
callbackHandler);
}
})
.setNeutralButton(R.string.download_with_new_name,
new DialogInterface.OnClickListener() {
@Override
@ -365,21 +385,13 @@ public class AlbumDetailsFragment extends Fragment
mediaTitle.setText(albumTitle);
mediaUndertitle.setText(albumDisplayArtist);
int year = cursor.getInt(AlbumDetailsQuery.YEAR);
String genres = cursor.getString(AlbumDetailsQuery.GENRE);
String label = (year > 0) ?
(!TextUtils.isEmpty(genres) ?
genres + " | " + String.valueOf(year) :
String.valueOf(year)) :
genres;
mediaYear.setText(label);
setMediaYear(cursor.getString(AlbumDetailsQuery.GENRE), cursor.getInt(AlbumDetailsQuery.YEAR));
double rating = cursor.getDouble(AlbumDetailsQuery.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_music));
setMediaRating(rating);
} else {
mediaRating.setVisibility(View.GONE);
mediaMaxRating.setVisibility(View.GONE);
@ -427,32 +439,32 @@ public class AlbumDetailsFragment extends Fragment
int artHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_art_height),
artWidth = displayMetrics.widthPixels;
int posterWidth = resources.getDimensionPixelOffset(R.dimen.albumdetail_poster_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.albumdetail_poster_heigth);
UIUtils.loadImageWithCharacterAvatar(getActivity(), hostManager,
poster, albumTitle,
mediaPoster, posterWidth, posterHeight);
if (!TextUtils.isEmpty(fanart)) {
int posterWidth = resources.getDimensionPixelOffset(R.dimen.albumdetail_poster_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.albumdetail_poster_heigth);
mediaPoster.setVisibility(View.VISIBLE);
UIUtils.loadImageWithCharacterAvatar(getActivity(), hostManager,
poster, albumTitle,
mediaPoster, posterWidth, posterHeight);
UIUtils.loadImageIntoImageview(hostManager,
fanart,
mediaArt, artWidth, artHeight);
} else {
// No fanart, just present the poster
mediaPoster.setVisibility(View.GONE);
UIUtils.loadImageIntoImageview(hostManager,
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);
}
}
private void setMediaRating(double rating) {
mediaRating.setText(String.format("%01.01f", rating));
mediaMaxRating.setText(getString(R.string.max_rating_music));
}
private void setMediaYear(String genres, int year) {
String label = (year > 0) ?
(!TextUtils.isEmpty(genres) ?
genres + " | " + String.valueOf(year) :
String.valueOf(year)) :
genres;
mediaYear.setText(label);
}
/**
* Starts playing the song on XBMC
* @param songId song to play
@ -501,7 +513,7 @@ public class AlbumDetailsFragment extends Fragment
if (!isAdded()) return;
// Got an error, show toast
Toast.makeText(getActivity(), R.string.item_added_to_playlist, Toast.LENGTH_SHORT)
.show();
.show();
}
@Override
@ -509,12 +521,12 @@ public class AlbumDetailsFragment extends Fragment
if (!isAdded()) return;
// Got an error, show toast
Toast.makeText(getActivity(), R.string.unable_to_connect_to_xbmc, Toast.LENGTH_SHORT)
.show();
.show();
}
}, callbackHandler);
} else {
Toast.makeText(getActivity(), R.string.no_suitable_playlist, Toast.LENGTH_SHORT)
.show();
.show();
}
}
@ -523,7 +535,7 @@ public class AlbumDetailsFragment extends Fragment
if (!isAdded()) return;
// Got an error, show toast
Toast.makeText(getActivity(), R.string.unable_to_connect_to_xbmc, Toast.LENGTH_SHORT)
.show();
.show();
}
}, callbackHandler);
}
@ -559,31 +571,31 @@ public class AlbumDetailsFragment extends Fragment
if (file.exists()) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.download)
.setMessage(R.string.download_file_exists)
.setPositiveButton(R.string.overwrite,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FileDownloadHelper.downloadFiles(getActivity(), hostInfo,
songInfo, FileDownloadHelper.OVERWRITE_FILES,
callbackHandler);
}
})
.setNeutralButton(R.string.download_with_new_name,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FileDownloadHelper.downloadFiles(getActivity(), hostInfo,
songInfo, FileDownloadHelper.DOWNLOAD_WITH_NEW_NAME,
callbackHandler);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) { }
})
.show();
.setMessage(R.string.download_file_exists)
.setPositiveButton(R.string.overwrite,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FileDownloadHelper.downloadFiles(getActivity(), hostInfo,
songInfo, FileDownloadHelper.OVERWRITE_FILES,
callbackHandler);
}
})
.setNeutralButton(R.string.download_with_new_name,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
FileDownloadHelper.downloadFiles(getActivity(), hostInfo,
songInfo, FileDownloadHelper.DOWNLOAD_WITH_NEW_NAME,
callbackHandler);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) { }
})
.show();
} else {
FileDownloadHelper.downloadFiles(getActivity(), hostInfo,
songInfo, FileDownloadHelper.DOWNLOAD_WITH_NEW_NAME,
@ -608,7 +620,7 @@ public class AlbumDetailsFragment extends Fragment
songInfoList = new ArrayList<FileDownloadHelper.SongInfo>(cursor.getCount());
do {
View songView = LayoutInflater.from(getActivity())
.inflate(R.layout.list_item_song, songListView, false);
.inflate(R.layout.list_item_song, songListView, false);
TextView songTitle = (TextView)songView.findViewById(R.id.song_title);
TextView trackNumber = (TextView)songView.findViewById(R.id.track_number);
TextView duration = (TextView)songView.findViewById(R.id.duration);
@ -637,7 +649,6 @@ public class AlbumDetailsFragment extends Fragment
} while (cursor.moveToNext());
if (songInfoList.size() > 0) {
downloadButton.setVisibility(View.VISIBLE);
// Check if download dir exists
FileDownloadHelper.SongInfo songInfo = new FileDownloadHelper.SongInfo
(albumDisplayArtist, albumTitle, 0, 0, null, 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.res.Resources;
@ -48,6 +49,7 @@ import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.MediaPlayerUtils;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
/**
* Fragment that presents the albums list
@ -56,7 +58,7 @@ public class AlbumListFragment extends AbstractListFragment {
private static final String TAG = LogUtils.makeLogTag(AlbumListFragment.class);
public interface OnAlbumSelectedListener {
public void onAlbumSelected(int albumId, String albumTitle);
public void onAlbumSelected(ViewHolder vh);
}
private static final String GENREID = "genreid",
@ -103,7 +105,7 @@ public class AlbumListFragment extends AbstractListFragment {
// Get the movie id from the tag
ViewHolder tag = (ViewHolder) view.getTag();
// Notify the activity
listenerActivity.onAlbumSelected(tag.albumId, tag.albumTitle);
listenerActivity.onAlbumSelected(tag);
}
};
}
@ -194,6 +196,7 @@ public class AlbumListFragment extends AbstractListFragment {
MediaContract.Albums.GENRE,
MediaContract.Albums.THUMBNAIL,
MediaContract.Albums.YEAR,
MediaContract.Albums.RATING,
};
String SORT = MediaDatabase.sortCommonTokens(MediaContract.Albums.TITLE) + " ASC";
@ -205,6 +208,7 @@ public class AlbumListFragment extends AbstractListFragment {
final int GENRE = 4;
final int THUMBNAIL = 5;
final int YEAR = 6;
final int RATING = 7;
}
private class AlbumsAdapter extends CursorAdapter {
@ -220,10 +224,8 @@ public class AlbumListFragment extends AbstractListFragment {
// Use the same dimensions as in the details fragment, so that it hits Picasso's cache when
// the user transitions to that fragment, avoiding another call and imediatelly showing the image
Resources resources = context.getResources();
artWidth = (int)(resources.getDimension(R.dimen.albumdetail_poster_width) /
UIUtils.IMAGE_RESIZE_FACTOR);
artHeight = (int)(resources.getDimension(R.dimen.albumdetail_poster_heigth) /
UIUtils.IMAGE_RESIZE_FACTOR);
artWidth = (int) resources.getDimensionPixelOffset(R.dimen.albumdetail_poster_width);
artHeight = (int) resources.getDimensionPixelOffset(R.dimen.albumdetail_poster_heigth);
}
/** {@inheritDoc} */
@ -244,12 +246,17 @@ public class AlbumListFragment extends AbstractListFragment {
}
/** {@inheritDoc} */
@TargetApi(21)
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewHolder viewHolder = (ViewHolder)view.getTag();
viewHolder.albumId = cursor.getInt(AlbumListQuery.ALBUMID);
viewHolder.albumTitle = cursor.getString(AlbumListQuery.TITLE);
viewHolder.albumArtist = cursor.getString(AlbumListQuery.DISPLAYARTIST);
viewHolder.albumGenre = cursor.getString(AlbumListQuery.GENRE);
viewHolder.albumYear = cursor.getInt(AlbumListQuery.YEAR);
viewHolder.albumRating = cursor.getDouble(AlbumListQuery.RATING);
viewHolder.titleView.setText(viewHolder.albumTitle);
viewHolder.artistView.setText(cursor.getString(AlbumListQuery.DISPLAYARTIST));
@ -269,13 +276,17 @@ public class AlbumListFragment extends AbstractListFragment {
ImageView contextMenu = (ImageView)view.findViewById(R.id.list_context_menu);
contextMenu.setTag(viewHolder);
contextMenu.setOnClickListener(albumlistItemMenuClickListener);
if(Utils.isLollipopOrLater()) {
viewHolder.artView.setTransitionName("a"+viewHolder.albumId);
}
}
}
/**
* View holder pattern
*/
private static class ViewHolder {
public static class ViewHolder {
TextView titleView;
TextView artistView;
TextView genresView;
@ -283,6 +294,10 @@ public class AlbumListFragment extends AbstractListFragment {
int albumId;
String albumTitle;
String albumArtist;
int albumYear;
String albumGenre;
double albumRating;
}
private View.OnClickListener albumlistItemMenuClickListener = new View.OnClickListener() {

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;
@ -243,29 +244,44 @@ public class MusicActivity extends BaseActivity
}
@TargetApi(21)
public void onArtistSelected(int artistId, String artistName) {
selectedArtistId = artistId;
selectedArtistName = artistName;
// Replace list fragment
AlbumListFragment albumListFragment = AlbumListFragment.newInstanceForArtist(artistId);
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.fragment_details_enter, 0, R.anim.fragment_list_popenter, 0)
.replace(R.id.fragment_container, albumListFragment)
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
// Setup animations
if (Utils.isLollipopOrLater()) {
//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);
albumListFragment.setExitTransition(fade);
albumListFragment.setReenterTransition(fade);
albumListFragment.setSharedElementReturnTransition(TransitionInflater.from(
this).inflateTransition(R.transition.change_image));
} else {
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0, R.anim.fragment_list_popenter, 0);
}
fragTrans.replace(R.id.fragment_container, albumListFragment)
.addToBackStack(null)
.commit();
navigationDrawerFragment.animateDrawerToggle(true);
setupActionBar(null, artistName, null, null);
}
@TargetApi(21)
public void onAlbumSelected(int albumId, String albumTitle) {
selectedAlbumId = albumId;
selectedAlbumTitle = albumTitle;
public void onAlbumSelected(AlbumListFragment.ViewHolder vh) {
selectedAlbumId = vh.albumId;
selectedAlbumTitle = vh.albumTitle;
// Replace list fragment
AlbumDetailsFragment albumDetailsFragment = AlbumDetailsFragment.newInstance(albumId);
AlbumDetailsFragment albumDetailsFragment = AlbumDetailsFragment.newInstance(vh);
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
// Set up transitions
@ -274,15 +290,22 @@ public class MusicActivity extends BaseActivity
.from(this)
.inflateTransition(R.transition.media_details));
albumDetailsFragment.setReturnTransition(null);
Transition changeImageTransition = TransitionInflater.from(
this).inflateTransition(R.transition.change_image);
albumDetailsFragment.setSharedElementReturnTransition(changeImageTransition);
albumDetailsFragment.setSharedElementEnterTransition(changeImageTransition);
fragTrans.addSharedElement(vh.artView, vh.artView.getTransitionName());
} else {
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0,
R.anim.fragment_list_popenter, 0);
}
fragTrans.replace(R.id.fragment_container, albumDetailsFragment)
.addToBackStack(null)
.addToBackStack(null)
.commit();
setupActionBar(albumTitle, null, null, null);
setupActionBar(selectedAlbumTitle, null, null, null);
}
public void onAudioGenreSelected(int genreId, String genreTitle) {
@ -321,7 +344,7 @@ public class MusicActivity extends BaseActivity
}
fragTrans.replace(R.id.fragment_container, detailsFragment)
.addToBackStack(null)
.addToBackStack(null)
.commit();
setupActionBar(null, null, null, musicVideoTitle);
}

View File

@ -110,8 +110,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