android-如何从标记位图开始共享元素过渡?

Posted

技术标签:

【中文标题】android-如何从标记位图开始共享元素过渡?【英文标题】:android-how to start a shared element transition from marker bitmap? 【发布时间】:2021-06-03 00:02:54 【问题描述】:

我使用自定义位图作为地图上标记的图标。当用户单击任何标记时,我希望有一个与单击的位图相对应的视图并将其作为共享元素添加到片段转换中。但我看不到任何从标记中检索位图的方法。那么如何从标记开始共享元素过渡呢?

【问题讨论】:

【参考方案1】:

简而言之:使用setTag()/getTag() 方法在标记对象中保存/恢复位图,并使用此位图作为共享元素的附加ImageView 用于“地图”和“细节”片段之间的转换:

TLDR;

如果您“使用自定义位图作为标记的图标”,您可以使用setTag() 方法将它们存储在Marker 对象中,然后在onMarkerClick(Marker marker) 方法中检索标记的图标位图。但是位图对于shared element transitions 来说是不够的,因为需要View 类的对象来执行它。因此,您需要创建额外的View(例如ImageView)并将其用作“地图”和“详细信息”片段之间转换的共享元素。

一般来说,您应该:

应用程序启动时MainActivity):
    使用ImageView 创建“map”片段以执行共享元素转换; 使用对应的ImageView 创建“详细信息”片段以执行共享元素转换; 创建共享元素过渡动画;

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

当用户点击标记时

在“地图”片段中

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

在“细节”片段中

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

当用户关闭“详细信息”片段时在“地图”片段中),
    等待过渡动画结束并显示标记/隐藏ImageView

其中最具挑战性的部分是在“地图”片段内创建共享视图,因为SupportMapFragment 没有“来自盒子”的此类元素。因此,您需要创建自定义 CustomSupportMapFragment 以扩展 SupportMapFragment 并为共享元素转换添加额外的 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_ android:layout_>

    <ImageView
        android:id="@+id/picture_iv"
        android:layout_
        android:layout_
        android:transitionName="sharedImageView"
        android:layout_centerInParent="true"/>

    <TextView
        android:id="@+id/details_tv"
        android:layout_below="@+id/picture_iv"
        android:layout_
        android:layout_
        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
                    
                );
            
        );

    


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

【讨论】:

以上是关于android-如何从标记位图开始共享元素过渡?的主要内容,如果未能解决你的问题,请参考以下文章

如何实现与视频的共享元素过渡

Android棒棒糖共享元素过渡闪烁/闪烁

如何启动2活性之间共享单元的过渡?

如何共享元素从片段到活动的过渡

Android共享元素过渡——防止共享元素被绘制

具有 scaleType centerCrop 过渡的共享元素跳跃