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

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

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

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

animatedMarker

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

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

my 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 -->

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

Ответ 1

Пользовательский контакт 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 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>

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

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

Ответ 2

Поместите макет в 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);
            });

Ответ 3

Разметка

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

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

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

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

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

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

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

  • Empty
  • Загрузка
  • Loaded

Слушателей

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

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