EvOlaNdLuPiZ
EvOlaNdLuPiZ

Reputation: 600

Google Map + ViewPager2 swiping conflict

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)
 }
}

Upvotes: 6

Views: 1820

Answers (4)

ibrahim
ibrahim

Reputation: 91

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

Upvotes: 0

Tim Autin
Tim Autin

Reputation: 6165

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
}

Upvotes: 1

hannes
hannes

Reputation: 83

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)
}

Upvotes: 2

Oleg Bozhko
Oleg Bozhko

Reputation: 196

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" />

Upvotes: 13

Related Questions