Implemented shared element transitions for addons

Removed getting details in AddonDetailsFragment as the
AddonListFragment already provides all information required
in the AddonDetailsFragment. Therefore, we do not need to
retrieve the same information in the AddonDetailsFragment as well.
This commit is contained in:
Martijn Brekhof 2015-12-11 17:09:44 +01:00
parent 4d7acd80d0
commit a908e1e466
5 changed files with 158 additions and 103 deletions

View File

@ -15,6 +15,7 @@
*/
package org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Bundle;
@ -39,9 +40,9 @@ import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.method.Addons;
import org.xbmc.kore.jsonrpc.type.AddonType;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import butterknife.ButterKnife;
import butterknife.InjectView;
@ -53,7 +54,15 @@ import butterknife.OnClick;
public class AddonDetailsFragment extends Fragment {
private static final String TAG = LogUtils.makeLogTag(AddonDetailsFragment.class);
public static final String ADDONID = "addon_id";
public static final String BUNDLE_KEY_ADDONID = "addon_id";
public static final String POSTER_TRANS_NAME = "POSTER_TRANS_NAME";
public static final String BUNDLE_KEY_NAME = "name";
public static final String BUNDLE_KEY_AUTHOR = "author";
public static final String BUNDLE_KEY_SUMMARY = "summary";
public static final String BUNDLE_KEY_VERSION = "version";
public static final String BUNDLE_KEY_DESCRIPTION = "description";
public static final String BUNDLE_KEY_FANART = "fanart";
public static final String BUNDLE_KEY_POSTER = "poster";
private HostManager hostManager;
private HostInfo hostInfo;
@ -87,11 +96,23 @@ public class AddonDetailsFragment extends Fragment {
/**
* Create a new instance of this, initialized to show the addon addonId
*/
public static AddonDetailsFragment newInstance(String addonId) {
@TargetApi(21)
public static AddonDetailsFragment newInstance(AddonListFragment.ViewHolder vh) {
AddonDetailsFragment fragment = new AddonDetailsFragment();
Bundle args = new Bundle();
args.putString(ADDONID, addonId);
args.putString(BUNDLE_KEY_ADDONID, vh.addonId);
args.putString(BUNDLE_KEY_NAME, vh.addonName);
args.putString(BUNDLE_KEY_AUTHOR, vh.author);
args.putString(BUNDLE_KEY_VERSION, vh.version);
args.putString(BUNDLE_KEY_SUMMARY, vh.summary);
args.putString(BUNDLE_KEY_DESCRIPTION, vh.description);
args.putString(BUNDLE_KEY_FANART, vh.fanart);
args.putString(BUNDLE_KEY_POSTER, vh.poster);
if( Utils.isLollipopOrLater()) {
args.putString(POSTER_TRANS_NAME, vh.artView.getTransitionName());
}
fragment.setArguments(args);
return fragment;
}
@ -101,9 +122,11 @@ public class AddonDetailsFragment extends Fragment {
super.onCreate(savedInstanceState);
}
@TargetApi(21)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
addonId = getArguments().getString(ADDONID, null);
Bundle bundle = getArguments();
addonId = bundle.getString(BUNDLE_KEY_ADDONID, null);
if ((container == null) || (addonId == null)) {
// We're not being shown or there's nothing to show
@ -131,6 +154,18 @@ public class AddonDetailsFragment extends Fragment {
FloatingActionButton fab = (FloatingActionButton)fabButton;
fab.attachToScrollView((ObservableScrollView) mediaPanel);
if(Utils.isLollipopOrLater()) {
mediaPoster.setTransitionName(bundle.getString(POSTER_TRANS_NAME));
}
mediaTitle.setText(bundle.getString(BUNDLE_KEY_NAME));
mediaUndertitle.setText(bundle.getString(BUNDLE_KEY_SUMMARY));
mediaAuthor.setText(bundle.getString(BUNDLE_KEY_AUTHOR));
mediaVersion.setText(bundle.getString(BUNDLE_KEY_VERSION));
mediaDescription.setText(bundle.getString(BUNDLE_KEY_DESCRIPTION));
setImages(bundle.getString(BUNDLE_KEY_POSTER), bundle.getString(BUNDLE_KEY_FANART));
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false);
@ -142,31 +177,6 @@ public class AddonDetailsFragment extends Fragment {
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(false);
// Get the addon details, this is done asyhnchronously
String[] properties = new String[] {
AddonType.Fields.NAME, AddonType.Fields.VERSION, AddonType.Fields.SUMMARY,
AddonType.Fields.DESCRIPTION, AddonType.Fields.PATH, AddonType.Fields.AUTHOR,
AddonType.Fields.THUMBNAIL, AddonType.Fields.DISCLAIMER, AddonType.Fields.FANART,
//AddonType.Fields.DEPENDENCIES, AddonType.Fields.BROKEN, AddonType.Fields.EXTRAINFO,
AddonType.Fields.RATING, AddonType.Fields.ENABLED
};
Addons.GetAddonDetails action = new Addons.GetAddonDetails(addonId, properties);
action.execute(hostManager.getConnection(), new ApiCallback<AddonType.Details>() {
@Override
public void onSuccess(AddonType.Details result) {
if (!isAdded()) return;
displayAddonDetails(result);
}
@Override
public void onError(int errorCode, String description) {
if (!isAdded()) return;
Toast.makeText(getActivity(),
String.format(getString(R.string.error_getting_addon_info), description),
Toast.LENGTH_SHORT).show();
}
}, callbackHandler);
}
@Override
@ -225,70 +235,39 @@ public class AddonDetailsFragment extends Fragment {
public void onError(int errorCode, String description) {
if (!isAdded()) return;
Toast.makeText(getActivity(),
String.format(getString(R.string.general_error_executing_action), description),
Toast.LENGTH_SHORT)
.show();
String.format(getString(R.string.general_error_executing_action), description),
Toast.LENGTH_SHORT)
.show();
}
}, callbackHandler);
}
/**
* Display the addon details
*
* @param addonDetails Addon details
*/
private void displayAddonDetails(AddonType.Details addonDetails) {
mediaTitle.setText(addonDetails.name);
mediaUndertitle.setText(addonDetails.summary);
mediaAuthor.setText(addonDetails.author);
mediaVersion.setText(addonDetails.version);
mediaDescription.setText(addonDetails.description);
// Images
private void setImages(String poster, String fanart) {
Resources resources = getActivity().getResources();
DisplayMetrics displayMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int artHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_art_height),
artWidth = displayMetrics.widthPixels;
if (!TextUtils.isEmpty(addonDetails.fanart)) {
int posterWidth = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_heigth);
mediaPoster.setVisibility(View.VISIBLE);
UIUtils.loadImageIntoImageview(hostManager,
addonDetails.thumbnail,
mediaPoster, posterWidth, posterHeight);
UIUtils.loadImageIntoImageview(hostManager,
addonDetails.fanart,
mediaArt, artWidth, artHeight);
} else {
// No fanart, just present the poster
mediaPoster.setVisibility(View.GONE);
UIUtils.loadImageIntoImageview(hostManager,
addonDetails.thumbnail,
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.addondetail_poster_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_height);
UIUtils.loadImageIntoImageview(hostManager,
TextUtils.isEmpty(fanart)? poster : fanart,
mediaArt, artWidth, artHeight);
UIUtils.loadImageIntoImageview(hostManager,
poster,
mediaPoster, posterWidth, posterHeight);
setupEnableButton(addonDetails.enabled);
}
private void setupEnableButton(boolean enabled) {
// Enabled button
if (enabled) {
Resources.Theme theme = getActivity().getTheme();
TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] {
TypedArray styledAttributes = theme.obtainStyledAttributes(new int[]{
R.attr.colorAccent});
enabledButton.setColorFilter(styledAttributes.getColor(0,
getActivity().getResources().getColor(R.color.accent_default)));
getActivity().getResources().getColor(R.color.accent_default)));
styledAttributes.recycle();
fabButton.setVisibility(View.VISIBLE);
@ -298,4 +277,16 @@ public class AddonDetailsFragment extends Fragment {
}
enabledButton.setTag(enabled);
}
/**
* Returns the shared element if visible
* @return View if visible, null otherwise
*/
public View getSharedElement() {
if (UIUtils.isViewInBounds(mediaPanel, mediaPoster)) {
return mediaPoster;
}
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.res.Resources;
@ -39,6 +40,7 @@ import org.xbmc.kore.jsonrpc.method.Addons;
import org.xbmc.kore.jsonrpc.type.AddonType;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import java.util.List;
@ -53,7 +55,7 @@ public class AddonListFragment extends Fragment
private static final String TAG = LogUtils.makeLogTag(AddonListFragment.class);
public interface OnAddonSelectedListener {
public void onAddonSelected(String addonId, String addonTitle);
public void onAddonSelected(ViewHolder vh);
}
// Activity listener
@ -99,7 +101,7 @@ public class AddonListFragment extends Fragment
// Get the movie id from the tag
ViewHolder tag = (ViewHolder) view.getTag();
// Notify the activity
listenerActivity.onAddonSelected(tag.addonId, tag.addonName);
listenerActivity.onAddonSelected(tag);
}
});
@ -210,8 +212,8 @@ public class AddonListFragment extends Fragment
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_addons_found_refresh));
Toast.makeText(getActivity(),
String.format(getString(R.string.error_getting_addon_info), description),
Toast.LENGTH_SHORT).show();
String.format(getString(R.string.error_getting_addon_info), description),
Toast.LENGTH_SHORT).show();
swipeRefreshLayout.setRefreshing(false);
}
}, callbackHandler);
@ -230,13 +232,12 @@ public class AddonListFragment extends Fragment
// 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.addondetail_poster_width) /
UIUtils.IMAGE_RESIZE_FACTOR);
artHeight = (int)(resources.getDimension(R.dimen.addondetail_poster_heigth) /
UIUtils.IMAGE_RESIZE_FACTOR);
artWidth = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_width);;
artHeight = resources.getDimensionPixelOffset(R.dimen.addondetail_poster_height);;
}
/** {@inheritDoc} */
@TargetApi(21)
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
@ -257,13 +258,23 @@ public class AddonListFragment extends Fragment
// Save the movie id
viewHolder.addonId = addonDetails.addonid;
viewHolder.addonName = addonDetails.name;
viewHolder.author = addonDetails.author;
viewHolder.description = addonDetails.description;
viewHolder.summary = addonDetails.summary;
viewHolder.version = addonDetails.version;
viewHolder.fanart = addonDetails.fanart;
viewHolder.poster = addonDetails.thumbnail;
viewHolder.titleView.setText(viewHolder.addonName);
viewHolder.detailsView.setText(addonDetails.summary);
UIUtils.loadImageWithCharacterAvatar(getContext(), hostManager,
addonDetails.thumbnail, viewHolder.addonName,
viewHolder.artView, artWidth, artHeight);
addonDetails.thumbnail, viewHolder.addonName,
viewHolder.artView, artWidth, artHeight);
if(Utils.isLollipopOrLater()) {
viewHolder.artView.setTransitionName("a"+viewHolder.addonId);
}
return convertView;
}
}
@ -271,12 +282,18 @@ public class AddonListFragment extends Fragment
/**
* View holder pattern
*/
private static class ViewHolder {
public static class ViewHolder {
TextView titleView;
TextView detailsView;
ImageView artView;
String addonId;
String addonName;
String summary;
String author;
String version;
String description;
String fanart;
String poster;
}
}

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 Addons information (list, details)
* All the information is presented by specific fragments
@ -47,6 +52,8 @@ public class AddonsActivity extends BaseActivity
private NavigationDrawerFragment navigationDrawerFragment;
private boolean clearSharedElements;
@TargetApi(21)
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -67,10 +74,26 @@ public class AddonsActivity extends BaseActivity
// Setup animations
if (Utils.isLollipopOrLater()) {
addonListFragment.setExitTransition(null);
addonListFragment.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);
addonListFragment.setExitTransition(fade);
addonListFragment.setReenterTransition(fade);
addonListFragment.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;
}
}
};
addonListFragment.setExitSharedElementCallback(seCallback);
}
getSupportFragmentManager()
.beginTransaction()
@ -175,32 +198,56 @@ public class AddonsActivity extends BaseActivity
/**
* Callback from list fragment when a addon is selected.
* Switch fragment in portrait
* @param addonId Addon selected
* @param addonTitle Title
* @param vh
*/
@TargetApi(21)
public void onAddonSelected(String addonId, String addonTitle) {
selectedAddonId = addonId;
selectedAddonTitle = addonTitle;
public void onAddonSelected(AddonListFragment.ViewHolder vh) {
selectedAddonId = vh.addonId;
selectedAddonTitle = vh.addonName;
// Replace list fragment
AddonDetailsFragment addonDetailsFragment = AddonDetailsFragment.newInstance(addonId);
final AddonDetailsFragment addonDetailsFragment = AddonDetailsFragment.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 (addonDetailsFragment.isVisible()) {
View sharedView = addonDetailsFragment.getSharedElement();
if (sharedView == null) { // shared element not visible
clearSharedElements = true;
}
}
}
};
addonDetailsFragment.setEnterSharedElementCallback(seCallback);
addonDetailsFragment.setEnterTransition(TransitionInflater
.from(this)
.inflateTransition(R.transition.media_details));
.from(this)
.inflateTransition(R.transition.media_details));
addonDetailsFragment.setReturnTransition(null);
Transition changeImageTransition = TransitionInflater.from(
this).inflateTransition(R.transition.change_image);
addonDetailsFragment.setSharedElementReturnTransition(changeImageTransition);
addonDetailsFragment.setSharedElementEnterTransition(changeImageTransition);
fragTrans.addSharedElement(vh.artView, vh.artView.getTransitionName());
} else {
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, addonDetailsFragment)
.addToBackStack(null)
.commit();
.addToBackStack(null)
.commit();
setupActionBar(selectedAddonTitle);
}

View File

@ -69,7 +69,7 @@
<dimen name="addonlist_art_heigth">88dp</dimen>
<dimen name="addondetail_poster_width">146dp</dimen>
<dimen name="addondetail_poster_heigth">146dp</dimen>
<dimen name="addondetail_poster_height">146dp</dimen>
<dimen name="channellist_art_width">88dp</dimen>
<dimen name="channellist_art_heigth">88dp</dimen>

View File

@ -114,7 +114,7 @@
<dimen name="addonlist_art_heigth">74dp</dimen>
<dimen name="addondetail_poster_width">112dp</dimen>
<dimen name="addondetail_poster_heigth">112dp</dimen>
<dimen name="addondetail_poster_height">112dp</dimen>
<dimen name="filelist_art_width">52dp</dimen>
<dimen name="filelist_art_heigth">76dp</dimen>