Merge pull request #153 from poisdeux/sharedelementtransition_addons

Implemented shared element transitions for addons
This commit is contained in:
Synced Synapse 2015-12-16 19:36:31 +00:00
commit 8f71f4e2dc
5 changed files with 164 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,16 @@ 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";
public static final String BUNDLE_KEY_ENABLED = "enabled";
private HostManager hostManager;
private HostInfo hostInfo;
@ -87,11 +97,24 @@ 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);
args.putBoolean(BUNDLE_KEY_ENABLED, vh.enabled);
if( Utils.isLollipopOrLater()) {
args.putString(POSTER_TRANS_NAME, vh.artView.getTransitionName());
}
fragment.setArguments(args);
return fragment;
}
@ -101,9 +124,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 +156,20 @@ 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));
setupEnableButton(bundle.getBoolean(BUNDLE_KEY_ENABLED, false));
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), mediaPanel, false, false, true);
// mediaPanel.setClipToPadding(false);
@ -142,31 +181,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 +239,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 +281,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,24 @@ 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.enabled = addonDetails.enabled;
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 +283,19 @@ 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;
Boolean enabled;
}
}

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>