android-如何从标记位图开始共享元素过渡?
android-how to start a shared element transition from marker bitmap?
我使用自定义位图作为地图上标记的图标。当用户单击任何标记时,我想要一个与单击的位图相对应的视图,并将其作为共享元素添加到片段转换中。但我看不到任何从标记中检索位图的方法。那么如何从标记开始共享元素转换?
简而言之:对标记对象中的 save/restore 位图使用 setTag()/getTag()
方法,并使用此位图作为“地图”和“详细信息”片段之间转换的共享元素的附加 ImageView
:
TLDR;
如果您“使用自定义位图作为标记的图标”,您可以将它们存储在 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 中,因为 View
class 的对象需要执行它。因此,您需要创建额外的 View
(例如 ImageView
)并将其用作“地图”和“详细信息”片段之间转换的共享元素。
一般来说你应该:
- 应用程序启动时(在
MainActivity
):
- 使用
ImageView
创建“地图”片段以执行共享元素转换;
- 为共享元素转换执行创建具有相应
ImageView
的“详细信息”片段;
- 创建共享元素过渡动画;
- 标记创建时(在“地图”片段中):
- 只需将位图保存在标记对象中即可。
- 当用户点击标记时,
在“地图”片段:
- 正在从标记对象中检索保存的位图;
- 将检索到的位图设置为共享
ImageView
;
- 调整大小
ImageView
并将其移动到恰好位于标记图标上方;
- 隐藏标记图标并用标记图标而不是标记显示
ImageView
;
- 通过参数将标记位图(和例如描述)放入“细节”片段;
- 创建并启动
FragmentTransaction
;
在“细节”片段:
- 从参数中获取标记位图和描述,并将它们显示在相应的
ImageView
和 TextView
上;
- 当用户关闭“详细信息”片段时(在“地图”片段中),
- 等待过渡动画结束并显示marker/hide
ImageView
.
其中最具挑战性的部分是在“地图”片段中创建共享视图,因为 SupportMapFragment
没有这样的元素“开箱即用”。因此,您需要创建扩展 SupportMapFragment
的自定义 CustomSupportMapFragment
并为共享元素转换创建额外的 ImageView
:
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);
}
}
“详细信息”片段可以像这样典型:
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;
}
}
布局如下:
<?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>
注意! 所有“地图”和“详细信息”片段视图都需要相同的 "sharedImageView"
转换名称。
并且MainActivity
应该实现标记点击处理逻辑:
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
}
});
}
});
}
}
就是这样。注意:这不是功能齐全的商业代码,只是示例。
我使用自定义位图作为地图上标记的图标。当用户单击任何标记时,我想要一个与单击的位图相对应的视图,并将其作为共享元素添加到片段转换中。但我看不到任何从标记中检索位图的方法。那么如何从标记开始共享元素转换?
简而言之:对标记对象中的 save/restore 位图使用 setTag()/getTag()
方法,并使用此位图作为“地图”和“详细信息”片段之间转换的共享元素的附加 ImageView
:
TLDR;
如果您“使用自定义位图作为标记的图标”,您可以将它们存储在 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 中,因为 View
class 的对象需要执行它。因此,您需要创建额外的 View
(例如 ImageView
)并将其用作“地图”和“详细信息”片段之间转换的共享元素。
一般来说你应该:
- 应用程序启动时(在
MainActivity
):
- 使用
ImageView
创建“地图”片段以执行共享元素转换; - 为共享元素转换执行创建具有相应
ImageView
的“详细信息”片段; - 创建共享元素过渡动画;
- 标记创建时(在“地图”片段中):
- 只需将位图保存在标记对象中即可。
- 当用户点击标记时,
在“地图”片段:
- 正在从标记对象中检索保存的位图;
- 将检索到的位图设置为共享
ImageView
; - 调整大小
ImageView
并将其移动到恰好位于标记图标上方; - 隐藏标记图标并用标记图标而不是标记显示
ImageView
; - 通过参数将标记位图(和例如描述)放入“细节”片段;
- 创建并启动
FragmentTransaction
;
在“细节”片段:
- 从参数中获取标记位图和描述,并将它们显示在相应的
ImageView
和TextView
上;
- 当用户关闭“详细信息”片段时(在“地图”片段中),
- 等待过渡动画结束并显示marker/hide
ImageView
.
其中最具挑战性的部分是在“地图”片段中创建共享视图,因为 SupportMapFragment
没有这样的元素“开箱即用”。因此,您需要创建扩展 SupportMapFragment
的自定义 CustomSupportMapFragment
并为共享元素转换创建额外的 ImageView
:
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);
}
}
“详细信息”片段可以像这样典型:
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;
}
}
布局如下:
<?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>
注意! 所有“地图”和“详细信息”片段视图都需要相同的 "sharedImageView"
转换名称。
并且MainActivity
应该实现标记点击处理逻辑:
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
}
});
}
});
}
}
就是这样。注意:这不是功能齐全的商业代码,只是示例。