I'm using custom bitmaps as the icon of markers on the map. when the user clicks on any marker, I want to have a view corresponding to the clicked bitmap and add it as a shared element to the fragment transition. but I can't see any method to retrieve the bitmap from the marker. so how to start a shared element transition from the marker?
In short: use setTag()/getTag()
methods to save/restore bitmap in marker object and additional ImageView
with this bitmap as shared element for transitions between "map" and "details" fragments:
TLDR;
In case you "using custom bitmaps as the icon of markers" you can store them in Marker
objects with setTag()
method and then retrieve marker's icon bitmap in onMarkerClick(Marker marker)
method. But bitmap is not enough for shared element transitions, because object of View
class needed to perform it. So, you need to create additional View
(e.g. ImageView
) and use it as shared element for transitions between "map" and "details" fragments.
In general you should:
MainActivity
):ImageView
for shared element transition perform;ImageView
for shared element transition perform;in "map" fragment:
ImageView
;ImageView
and move it to set exactly over the marker icon;ImageView
with marker icon instead of marker;FragmentTransaction
;in "details" fragment:
ImageView
and TextView
;ImageView
.Most challenged part of that is create shared view inside "map" fragment, because SupportMapFragment
did not have such element "from the box". So you need to create custom CustomSupportMapFragment
that extends SupportMapFragment
and have additional ImageView
for shared element transitions:
public class CustomSupportMapFragment extends SupportMapFragment {
private Bitmap mBitmap;
private float mY;
private float mX;
private RelativeLayout mRelativeLayout;
private ImageView mSharedImageView;
private Marker mMarker;
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View root = super.onCreateView(inflater, container, savedInstanceState);
mRelativeLayout = new RelativeLayout(root.getContext());
mRelativeLayout.addView(root, new RelativeLayout.LayoutParams(-1, -1));
mSharedImageView = new ImageView(root.getContext());
mSharedImageView.setId(View.generateViewId());
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
mSharedImageView.setLayoutParams(layoutParams);
mSharedImageView.setTransitionName("sharedImageView");
mRelativeLayout.addView(mSharedImageView);
return mRelativeLayout;
}
@Override
public void onStart() {
super.onStart();
if (mBitmap != null) {
mSharedImageView.setImageBitmap(mBitmap);
mSharedImageView.setX(mX);
mSharedImageView.setY(mY);
}
}
@Override
public void onStop() {
super.onStop();
mBitmap = ((BitmapDrawable)mSharedImageView.getDrawable()).getBitmap();
mX = mSharedImageView.getX();
mY = mSharedImageView.getY();
}
public void setSharedMarker(Marker marker) {
mMarker = marker;
}
public void setSharedViewInitialPosition(float x, float y) {
mSharedImageView.setX(x);
mSharedImageView.setY(y);
}
public void setSharedBitmap(Bitmap bitmap) {
mSharedImageView.setImageBitmap(bitmap);
}
public ImageView getSharedView() {
return mSharedImageView;
}
public void showMarker() {
if (mMarker != null) mMarker.setVisible(true);
}
}
"Details" fragment can be typical like that:
public class DetailsFragment extends Fragment {
private ImageView mImageView;
private TextView mTextView;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_details, container, false);
mImageView = view.findViewById(R.id.picture_iv);
mTextView = view.findViewById(R.id.details_tv);
Bundle bundle = getArguments();
if (bundle != null) {
Bitmap bitmap = getArguments().getParcelable("image");
mImageView.setImageBitmap(bitmap);
String description = getArguments().getString("description");
mTextView.setText(description);
}
return view;
}
}
with layout like:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView
android:id="@+id/picture_iv"
android:layout_width="300dp"
android:layout_height="300dp"
android:transitionName="sharedImageView"
android:layout_centerInParent="true"/>
<TextView
android:id="@+id/details_tv"
android:layout_below="@+id/picture_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:textSize="28dp"
android:text="Description"/>
</RelativeLayout>
NB! Same "sharedImageView"
name of transition needed across all "map" and "details" fragments views.
And MainActivity
should implement marker click processing logic:
public class MainActivity extends AppCompatActivity {
static final LatLng KYIV = new LatLng(50.450311, 30.523730);
static final LatLng DNIPRO = new LatLng(48.466111, 35.025278);
private GoogleMap mGoogleMap;
private CustomSupportMapFragment mapFragment;
private DetailsFragment detailsFragment;
public class DetailsEnterTransition extends TransitionSet {
public DetailsEnterTransition() {
setOrdering(ORDERING_TOGETHER);
addTransition(new ChangeBounds()).
addTransition(new ChangeTransform()).
addTransition(new ChangeImageTransform());
}
}
public class DetailsExitTransition extends TransitionSet {
public DetailsExitTransition(final CustomSupportMapFragment mapFragment) {
setOrdering(ORDERING_TOGETHER);
addTransition(new ChangeBounds()).
addTransition(new ChangeTransform()).
addTransition(new ChangeImageTransform());
addListener(new TransitionListener() {
@Override
public void onTransitionStart(Transition transition) {
}
@Override
public void onTransitionEnd(Transition transition) {
if (mapFragment != null) {
mapFragment.showMarker();
mapFragment.setSharedBitmap(null);
}
}
@Override
public void onTransitionCancel(Transition transition) {
}
@Override
public void onTransitionPause(Transition transition) {
}
@Override
public void onTransitionResume(Transition transition) {
}
});
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// create "map" fragment
mapFragment = new CustomSupportMapFragment();
// create "details" fragment and transitions animations
detailsFragment = new DetailsFragment();
detailsFragment.setSharedElementEnterTransition(new DetailsEnterTransition());
detailsFragment.setSharedElementReturnTransition(new DetailsExitTransition(mapFragment));
// show "map" fragment
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, mapFragment, "map")
.commit();
// get GoogleMap object
mapFragment.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_kyiv);
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, 200, 250, false);
Marker marker = mGoogleMap.addMarker(new MarkerOptions()
.position(KYIV)
.icon(BitmapDescriptorFactory.fromBitmap(resizedBitmap))
.title("Kyiv"));
marker.setTag(resizedBitmap); // save bitmap1 as tag of marker object
mGoogleMap.animateCamera(CameraUpdateFactory.newLatLng(KYIV));
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_dnipro);
resizedBitmap = Bitmap.createScaledBitmap(bitmap, 200, 250, false);
marker = mGoogleMap.addMarker(new MarkerOptions()
.position(DNIPRO)
.icon(BitmapDescriptorFactory.fromBitmap(resizedBitmap))
.title("Dnipro"));
marker.setTag(resizedBitmap); // save bitmap2 as tag of marker object
mGoogleMap.animateCamera(CameraUpdateFactory.newLatLng(KYIV));
mGoogleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
// retrieve bitmap for marker
Bitmap sharedBitmap = (Bitmap)marker.getTag();
// determine position of marker and shared element on screen
Projection projection = mGoogleMap.getProjection();
Point viewPosition = projection.toScreenLocation(marker.getPosition());
final float x = viewPosition.x - sharedBitmap.getWidth() / 2.0f;
final float y = viewPosition.y - sharedBitmap.getHeight();
// show shared ImageView and hide marker
mapFragment.setSharedMarker(marker);
mapFragment.setSharedBitmap(sharedBitmap);
mapFragment.setSharedViewInitialPosition(x, y);
mapFragment.getSharedView().setVisibility(View.VISIBLE);
mapFragment.getSharedView().invalidate();
marker.setVisible(false);
// prepare data for "details" fragment
Bundle bundle = new Bundle();
bundle.putParcelable("image", sharedBitmap);
bundle.putString("description", marker.getTitle());
detailsFragment.setArguments(bundle);
// create and start shared element transition animation
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.addSharedElement(mapFragment.getSharedView(), mapFragment.getSharedView().getTransitionName());
ft.replace(R.id.container, detailsFragment, "details");
ft.addToBackStack("details");
ft.commit();
return true; // prevent centring map on marker
}
});
}
});
}
}
And that's it. Note: this is not fully-functional commercial code, just illustration.