Создание анимации Overlay ImageView Google Map

Я пытаюсь сделать свой оверлейный образ следующим:

  • OnClick / onDrag карты, показать постоянное изображение в середине карты, которая является выводом
  • OnTouchUp, измените изображение маркера на маркер загрузки и после завершения загрузки данных измените загрузку изображения на новое изображение с текстом.

Вот довольно похожее решение моей проблемы:

animatedMarker

Что я сделал до сих пор?

  • Разместил изображение по моей карте google посередине и получил развязку этого imageView с использованием mMap.getCameraPosition().target которая дает приблизительные координаты средней позиции, используя setOnCameraChanged , я знаю, что это устарело, и у вас есть лучшее решение.
  • Остальной части я не могу понять, хотя я прочитал несколько вопросов SO, утверждая, что они внедрили это, обертывая framelayout над фрагментом карты google и применяя onTouch к этому, но не могут найти аутентичный ответ на этот вопрос.

Мой xml фрагмента, где все это происходит, выглядит так:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".fragments.MovableMapFragment"> <fragment android:id="@+id/map_frag_fmm" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/tablayout_global_tabs" android:layout_alignParentTop="true" /> <! -- long xml ahead --> 

Кто-то знает, как добиться такого поведения?

Solutions Collecting From Web of "Создание анимации Overlay ImageView Google Map"

Пользовательский PIN-код Android

Лучшим решением будет использование RxJava / RxAndroid и выполнение фоновых задач с ним, но я не могу опубликовать решение с использованием rx, не увидев ваш код, и уже есть пример из @ user27799, а также @Manas Chaudhari предоставил большое объяснение логики (внизу).

 //bunch of imports import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback { private GoogleMap mGoogleMap; private FrameLayout frameLayout, circleFrameLayout; private ProgressBar progress; private TextView textView; private int circleRadius; private boolean isMoving = false; private SupportMapFragment mapFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maps); initViews(); } private void initViews() { frameLayout = (FrameLayout) findViewById(R.id.map_container); circleFrameLayout = (FrameLayout) frameLayout.findViewById(R.id.pin_view_circle); textView = (TextView) circleFrameLayout.findViewById(R.id.textView); progress = (ProgressBar) circleFrameLayout.findViewById(R.id.profile_loader); mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); mapFragment.getMapAsync(this); } private void moveMapCamera() { if (mGoogleMap == null) { return; } CameraUpdate center = CameraUpdateFactory .newLatLng(new LatLng(40.76793169992044, -73.98180484771729)); CameraUpdate zoom = CameraUpdateFactory.zoomTo(15); mGoogleMap.moveCamera(center); mGoogleMap.animateCamera(zoom); } @Override public void onMapReady(GoogleMap googleMap) { mGoogleMap = googleMap; mGoogleMap.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() { /* User starts moving map - change your pin's image here */ @Override public void onCameraMoveStarted(int i) { isMoving = true; textView.setVisibility(View.GONE); progress.setVisibility(View.GONE); Drawable mDrawable; if (Build.VERSION.SDK_INT >= 21) mDrawable = getApplicationContext().getResources().getDrawable(R.drawable.circle_background_moving, null); else mDrawable = getApplicationContext().getResources().getDrawable(R.drawable.circle_background_moving); circleFrameLayout.setBackground(mDrawable); resizeLayout(false); } }); /*User stoped moving map -> retrieve location and do your background task */ mGoogleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() { @Override public void onCameraIdle() { isMoving = false; textView.setVisibility(View.INVISIBLE); progress.setVisibility(View.VISIBLE); resizeLayout(true); /* this is just an example that simulates data loading you shoud substitute it with your logic */ new Handler().postDelayed(new Runnable() { public void run() { Drawable mDrawable; if (Build.VERSION.SDK_INT >= 21) mDrawable = getApplicationContext().getResources().getDrawable(R.drawable.circle_background, null); else mDrawable = getApplicationContext().getResources().getDrawable(R.drawable.circle_background); if (!isMoving) { circleFrameLayout.setBackground(mDrawable); textView.setVisibility(View.VISIBLE); progress.setVisibility(View.GONE); } } }, 1500); } }); MapsInitializer.initialize(this); moveMapCamera(); } //resing circle pin private void resizeLayout(boolean backToNormalSize){ FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) circleFrameLayout.getLayoutParams(); ViewTreeObserver vto = circleFrameLayout.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { circleFrameLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this); circleRadius = circleFrameLayout.getMeasuredWidth(); } }); if (backToNormalSize) { params.width = WRAP_CONTENT; params.height = WRAP_CONTENT; params.topMargin = 0; } else { params.topMargin = (int) (circleRadius * 0.3); params.height = circleRadius - circleRadius / 3; params.width = circleRadius - circleRadius / 3; } circleFrameLayout.setLayoutParams(params); } } в //bunch of imports import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback { private GoogleMap mGoogleMap; private FrameLayout frameLayout, circleFrameLayout; private ProgressBar progress; private TextView textView; private int circleRadius; private boolean isMoving = false; private SupportMapFragment mapFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maps); initViews(); } private void initViews() { frameLayout = (FrameLayout) findViewById(R.id.map_container); circleFrameLayout = (FrameLayout) frameLayout.findViewById(R.id.pin_view_circle); textView = (TextView) circleFrameLayout.findViewById(R.id.textView); progress = (ProgressBar) circleFrameLayout.findViewById(R.id.profile_loader); mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); mapFragment.getMapAsync(this); } private void moveMapCamera() { if (mGoogleMap == null) { return; } CameraUpdate center = CameraUpdateFactory .newLatLng(new LatLng(40.76793169992044, -73.98180484771729)); CameraUpdate zoom = CameraUpdateFactory.zoomTo(15); mGoogleMap.moveCamera(center); mGoogleMap.animateCamera(zoom); } @Override public void onMapReady(GoogleMap googleMap) { mGoogleMap = googleMap; mGoogleMap.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() { /* User starts moving map - change your pin's image here */ @Override public void onCameraMoveStarted(int i) { isMoving = true; textView.setVisibility(View.GONE); progress.setVisibility(View.GONE); Drawable mDrawable; if (Build.VERSION.SDK_INT >= 21) mDrawable = getApplicationContext().getResources().getDrawable(R.drawable.circle_background_moving, null); else mDrawable = getApplicationContext().getResources().getDrawable(R.drawable.circle_background_moving); circleFrameLayout.setBackground(mDrawable); resizeLayout(false); } }); /*User stoped moving map -> retrieve location and do your background task */ mGoogleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() { @Override public void onCameraIdle() { isMoving = false; textView.setVisibility(View.INVISIBLE); progress.setVisibility(View.VISIBLE); resizeLayout(true); /* this is just an example that simulates data loading you shoud substitute it with your logic */ new Handler().postDelayed(new Runnable() { public void run() { Drawable mDrawable; if (Build.VERSION.SDK_INT >= 21) mDrawable = getApplicationContext().getResources().getDrawable(R.drawable.circle_background, null); else mDrawable = getApplicationContext().getResources().getDrawable(R.drawable.circle_background); if (!isMoving) { circleFrameLayout.setBackground(mDrawable); textView.setVisibility(View.VISIBLE); progress.setVisibility(View.GONE); } } }, 1500); } }); MapsInitializer.initialize(this); moveMapCamera(); } //resing circle pin private void resizeLayout(boolean backToNormalSize){ FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) circleFrameLayout.getLayoutParams(); ViewTreeObserver vto = circleFrameLayout.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { circleFrameLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this); circleRadius = circleFrameLayout.getMeasuredWidth(); } }); if (backToNormalSize) { params.width = WRAP_CONTENT; params.height = WRAP_CONTENT; params.topMargin = 0; } else { params.topMargin = (int) (circleRadius * 0.3); params.height = circleRadius - circleRadius / 3; params.width = circleRadius - circleRadius / 3; } circleFrameLayout.setLayoutParams(params); } } 

Файл макета с выводом в центре экрана:

 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/map_container" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment xmlns:tools="http://schemas.android.com/tools" android:id="@+id/map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.mapstest.MapsActivity" /> <FrameLayout android:id="@+id/pin_view_line" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="@dimen/line_top_margin" android:background="@drawable/line_background"/> <FrameLayout android:id="@+id/pin_view_circle" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/circle_background"> <TextView android:id="@+id/textView" android:layout_margin="@dimen/inner_circle_margin" android:layout_width="@dimen/inner_circle_radius" android:layout_height="@dimen/inner_circle_radius" android:layout_gravity="top|center_horizontal" android:gravity="center" android:text="12 min" android:textSize="@dimen/text_size" android:textColor="@android:color/white"/> <ProgressBar android:id="@+id/profile_loader" android:layout_margin="@dimen/inner_circle_margin" android:layout_width="@dimen/inner_circle_radius" android:layout_height="@dimen/inner_circle_radius" android:indeterminate="true" android:layout_gravity="top|center_horizontal" android:visibility="gone" android:contentDescription="@null"/> </FrameLayout> 

Окружать фон, когда он не перемещается:

 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="@android:color/holo_green_dark"/> <stroke android:width="@dimen/stroke_width" android:color="@android:color/black"/> <size android:width="@dimen/circle_radius" android:height="@dimen/circle_radius"/> </shape> 

Обведите круг во время перемещения карты:

 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="@android:color/black"/> <size android:width="@dimen/circle_radius" android:height="@dimen/circle_radius"/> </shape> 

Линейный фоновый файл:

 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@android:color/black"/> <size android:width="@dimen/line_width" android:height="@dimen/line_height"/> </shape> 

Габаритные размеры:

 <?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="circle_radius">48dp</dimen> <dimen name="line_width">4dp</dimen> <dimen name="line_height">40dp</dimen> <dimen name="stroke_width">4dp</dimen> <dimen name="text_size">10sp</dimen> <dimen name="inner_circle_radius">40dp</dimen> <dimen name="inner_circle_margin">4dp</dimen> <dimen name="line_top_margin">20dp</dimen> </resources> 

Надеюсь, это вам поможет, но если у вас есть какие-либо вопросы, не стесняйтесь спрашивать или видите ли вы области, которые вы улучшили, отредактируйте мое решение.

Приветствия.

Поместите макет в FrameLayout и нарисуйте изображение над Картой. Когда GoogleMap готов (в моем коде используется RxJava):

 Observable.<Void>create(subscriber -> { mMap.setOnCameraIdleListener(() -> subscriber.onNext(null)); }) .debounce(1, TimeUnit.SECONDS) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(aVoid -> { LatLng newPos = mMap.getProjection().fromScreenLocation(new Point(mBinding.googleMap.getWidth() / 2, mBinding.googleMap.getHeight() / 2)); makeServerRequest(newPos); }); 

раскладка

Маркеры предназначены для размещения на определенных координатах на карте. Таким образом, они перемещаются при изменении Карты.

Поскольку ваш контакт всегда находится в середине карты, его не подходит для создания с помощью Marker . Вместо этого вы можете разместить его вне фрагмента карты в центре контейнера карты. Поэтому ваш XML должен выглядеть следующим образом:

 <RelativeLayout> <fragment android:id="@+id/map_fragment" /> <RelativeLayout android:id="@+id/pin_view" android:centerInParent="true" /> </RelativeLayout> 

Вы можете добавить детей в pin_view для реализации анимаций в соответствии с вашим требованием.

Состояние контактов

На основе предоставленного вами gif для булавки могут быть три состояния.

  • пустой
  • загрузка
  • нагруженный

Слушатели

Вы можете активировать анимацию, настроив этих слушателей:

  • setOnCameraMoveStartedListener
    • Предыдущее значение должно быть недействительным. Измените состояние булавки на Empty
    • Отменить любые текущие запросы
  • setOnCameraIdleListener
    • Получить координаты с помощью map.getCameraPosition()
    • Измените состояние булавки на «Загрузка»
    • Запуск сетевого вызова для извлечения данных. В обратном вызове
      • Измените состояние булавки на «Загружено» и отобразите значение. Обратите внимание, что обратный вызов не будет выполнен, если пользователь перемещается до завершения api, так как мы отменим запрос, как только пользователь переместится. Таким образом, этот сценарий также будет