Google Map + ViewPager2 swiping conflict
Asked Answered
S

4

6

Anyone dealing with ViewPager2 + Fragments + GoogleMap?

I've ran into a really interesting issue, inside my viewpager i have two tabs that each render fragments.

One of the fragments spawns a google map.

When i try maneuvering through that map i get the effect of viewpager2 trying to swipe left and right, it's really annoying, so what i did to solve it was not so nice.

I exposed the viewpager to one of the fragments through the constructor and fired a callback to the top viewpager activity.

In there i performed a simple isUserInputEnabled with logic, while the map is active disable swipe.

Once the fragment attaches this callback registers and invokes the logic.

Once the fragment detaches this callback invokes the logic to re-enable.

It's a solution but imo it's not nice. Any better ideas?

Swiping overlapping with views seems like a bug to me.

/* top activity that hosts viewpager2 and it's tabs(fragments) */
class ViewTap extends AppCompatActivity
all the good stuff about viewpager2: ViewPager2
fun registerUi(){
...TapPagerAdapter(...this)
}
override fun fireSensitivityResolver(fragment: Fragment, flag: Boolean){
 if(fragment is ViewMapLoader){
  this.mViewPager2.isUserInputEnabled = !flag
 }
}

/* callback defintion fo handling events about Tab Capale thingies */
interface TabCapableIf {
 fun fireSensitivityResolver(fragment: Fragment, flag: Boolean)
}

/* the adapter for viewpager2 */
class TapPagerAdapter(...private val vt: ViewTap) : FragmentStateAdapter(fm,lc){
override fun createFragment(position: Int): Fragment {
return when(position) {
 ....
  CROWD_FRAGMENT -> { ViewCrowd(vmf, vt) }
 }
}

/* the fragment where the recycler view shows */
class ViewCrowd(...,val vt: ViewTap) : Fragment(){
 fun subscribeUi(){
   some recycler adapter = ItemViewCrowdsAdapter(...,this)
 }
}

/* the card adapter for the recycler view , when a card is clicked transition to map view */
class ItemViewCrowdsAdapter(...,private val vc: ViewCrowd) : AdapterFragmentRecyclerView(vm) {
 override fun onBindViewHolder(holder: ItemHolder, position: Int){
  ...holder.itemView.setOnClickListener{
   ...                    fragmentTransaction.replace(R.id.layout_view_crowd_root, ViewMapLoader(...,vc.vt))
  }
 }
}

/* the map loader context that shows the map and handles adjusting the sensitivity so that viewpager2 swipe doesn't overlap with map swipe functionality. otherwise as i try swiping on the map the viewpager2 also swipes. */
class class ViewMapLoader(...,private val vt: ViewTap) : Fragment() {
 private lateinit var mTabCapableIf: TabCapableIf
 override fun onAttach(...){
  this.mTabCapableIf = this.vt
  mTabCapableIf.fireSensitivityResolver(this,true)
 }

 override fun onDetach(){
  mTabCapableIf.fireSensitivityResolver(this,false)
 }
}
Swellhead answered 4/4, 2020 at 3:55 Comment(0)
A
13

Try to override MapView to supress touch events

public class MapViewInScroll extends MapView {
public MapViewInScroll(Context context) {
    super(context);
}

public MapViewInScroll(Context context, AttributeSet attributeSet) {
    super(context, attributeSet);
}

public MapViewInScroll(Context context, AttributeSet attributeSet, int i) {
    super(context, attributeSet, i);
}

public MapViewInScroll(Context context, GoogleMapOptions googleMapOptions) {
    super(context, googleMapOptions);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    getParent().requestDisallowInterceptTouchEvent(true);
    return super.dispatchTouchEvent(ev);
}

}

and use it in your xml layout instead of original MapView

                    <YOUR_PACKAGE.SOME_PATH_TO_VIEW.MapViewInScroll
                    android:id="@+id/map"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
Anatolian answered 15/4, 2020 at 11:55 Comment(5)
Do the ViewPager2 need to be the first parent here, or do you actually need to iterate through down the view-tree to find the ViewPager2 view ?Solatium
Hi @Oleg Bozhko I have a problem with the xml layout. I'm using fragment instead of MapView to show the map, so obviously when I put my class it tecnhically work (I can intercpet touches) but is not showing any map. Any idea?Mycostatin
@Mycostatin You have to implement MapView lifecycle in your fragment and acquire it using getMapAsync(OnMapReadyCallback) as described here: developers.google.com/android/reference/com/google/android/gms/… Here is a simple example: github.com/googlemaps/android-samples/blob/main/ApiDemos/java/…Anatolian
@RoarGrønmo No. Any layout you wish.Anatolian
@OlegBozhko thanks for the answer, to be accurate I was already loading the map that way. Solved the issue putting in the fragment an ImageView transparent, applied an onTouch listener and inhibited the parent, that way worked like a charm. transparentImageView.setOnTouchListener(new OnSwipeTouchListener(mMain) { @Override public boolean onTouch(View v, MotionEvent event) { v.getParent().requestDisallowInterceptTouchEvent(true); mapView.dispatchTouchEvent(event); return super.onTouch(v, event); } });Mycostatin
A
2

If you want to detect that the user initiates the scroll near the left or right border of the screen to slide the viewpager in that case you can modify the dispatchTouchEvent() Code to (Kotlin):

private var intercept = false
private val slideBorder = 0.1 // width of the slide-border in percent of the screen width

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    when (ev.action) {
        MotionEvent.ACTION_DOWN -> {
           intercept = ev.x > width * slideBorder && ev.x < width * (1 - slideBorder)
        }
        MotionEvent.ACTION_MOVE -> {
            parent.requestDisallowInterceptTouchEvent(intercept)                                
        }                                                                                       
    }
    return super.dispatchTouchEvent(ev)
}
Arrowwood answered 21/4, 2021 at 18:59 Comment(0)
G
1

I solved that problem by adding a dumb view above the map like this:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="200dp">

    <FrameLayout
        android:id="@+id/mapContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <FrameLayout
        android:id="@+id/mapShim"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>

Then I simply listened touch events on the shim, forward them to the map container and prevent the parent to get notified using requestDisallowInterceptTouchEvent(true):

mapShim.setOnTouchListener { _, event ->
    mapContainer.dispatchTouchEvent(event)
    mapContainer.parent.requestDisallowInterceptTouchEvent(true)
    true
}
Gsuit answered 20/5, 2021 at 22:14 Comment(2)
Do the ViewPager2 need to be the first parent here, or do you actually need to iterate through down the view-tree to find the ViewPager2 view ?Solatium
The above layout is inside one of the ViewPager2 fragment pages. Intercepting touch events disables the ViewPager2 swiping.Gsuit
A
0

I also ran into the same problem. Below is my work around and it worked perfectly in my case.

  1. in on onResume() method of the fragmen, where you are using GoogleMap, add following piece of code

    (activity as MainActivity).viewPager2.isUserInputEnabled = false

MainActivity is the parent activity of the fragment (where GoogleMap is used)

  1. in onCreateView() of the fragment add following code.

    root = inflater.inflate(R.layout.your_fragment_layout, container, false)

         root.setOnTouchListener { v, event ->
             v.performClick()
             if (event.action == MotionEvent.ACTION_DOWN) {
                 (activity as MainActivity).viewPager2.isUserInputEnabled=true
             } else if (event.action == (MotionEvent.ACTION_UP)) {
                 (activity as MainActivity).viewPager2.isUserInputEnabled=false
             }
             return@setOnTouchListener true
         }
    

Hope this will help you. Thank you

Augustineaugustinian answered 7/4 at 7:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.