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):
  1. 使用 ImageView 创建“地图”片段以执行共享元素转换;
  2. 为共享元素转换执行创建具有相应ImageView的“详细信息”片段;
  3. 创建共享元素过渡动画;



  • 标记创建时在“地图”片段中):
  1. 只需将位图保存在标记对象中即可。



  • 当用户点击标记时,

在“地图”片段:

  1. 正在从标记对象中检索保存的位图;
  2. 将检索到的位图设置为共享 ImageView;
  3. 调整大小 ImageView 并将其移动到恰好位于标记图标上方;
  4. 隐藏标记图标并用标记图标而不是标记显示 ImageView
  5. 通过参数将标记位图(和例如描述)放入“细节”片段;
  6. 创建并启动 FragmentTransaction;

在“细节”片段:

  1. 从参数中获取标记位图和描述,并将它们显示在相应的 ImageViewTextView 上;



  • 当用户关闭“详细信息”片段时在“地图”片段中),
  1. 等待过渡动画结束并显示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
                    }
                });
            }
        });

    }

}

就是这样。注意:这不是功能齐全的商业代码,只是示例。