Search code examples
androidkotlingoogle-maps-android-api-2draggable

Is there a way to make a view drag handle


I am working on a project where I use GoogleMap (v2) and Recyclerview (and a bunch of other views). When the app is in portrait mode, (showing map on top, and a recyclerview below), I want it to be possible to "drag" the intersection between the recyclerview and the map, so the map can be higher/lower (and the recyclerview height adjusts accordingly).

In principle, my layout is like this:

<?xml version="1.0" encoding="utf-8"?>
<layout
    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">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="@color/fragmentBackgroundColor">



        <com.google.android.material.card.MaterialCardView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:layout_margin="4dp">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

<!-- other views in same level here , not essentially for the Q-->

                
                <com.google.android.gms.maps.MapView
                    android:id="@+id/fragment_home_map_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    app:liteMode="false"
                    app:mapType="hybrid" />

            </androidx.constraintlayout.widget.ConstraintLayout>



        </com.google.android.material.card.MaterialCardView>

        <include
            android:id="@+id/fragment_home_info_card_car_position"
            layout="@layout/info_card_car_position"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="4dp"
            tools:visibility="visible"
            android:visibility="visible" />

        <include
            android:id="@+id/fragment_home_filter_chips"
            layout="@layout/selection_chips"
            app:layout_constraintTop_toBottomOf="@id/fragment_home_info_card_car_position"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="4dp" />

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_marginStart="4dp"
            android:layout_marginEnd="4dp"
            android:background="?android:attr/listDivider"/>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:orientation="vertical"
            android:layout_weight="1">

            <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
                android:id="@+id/fragment_home_swiper_refresh_layout"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@id/fragment_home_divider_2"
                android:layout_width="match_parent"
                android:layout_height="0dp">

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/fragment_home_recyclerview"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:fadeScrollbars="false"
                    android:scrollbars="vertical"
                    android:scrollbarSize="4dp"
                    android:scrollbarStyle="outsideOverlay"
                    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                    tools:listitem="@layout/list_item_order_list"/>



            </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

            <View
                app:layout_constraintBottom_toTopOf="@id/fragment_home_bottom_navigation"
                android:layout_marginBottom="4dp"
                android:id="@+id/fragment_home_divider_2"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="?android:attr/listDivider"/>

            <com.google.android.material.bottomnavigation.BottomNavigationView
                android:id="@+id/fragment_home_bottom_navigation"
                style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:menu="@menu/bottom_navigation_view_menu"
                app:layout_constraintBottom_toBottomOf="parent"
                />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.appcompat.widget.LinearLayoutCompat>

</layout>

shows where I want drag handle

In the middle between the map and the recyclerview, there are two included views which could be used as drag handler (touch and drag them up/down).

I hope for (and haven't found any yet) some kind if gesture handler I could wrap around those two to make them draggable up/down and resize both map and recyclerview, and leave the other as they are (and let them adjust if needed).

Or do you have any other good suggestions.


Solution

  • I have found an answer through this one:

    Android make a resizable split screen inside a contraintlayout

    In short term:

    1. Divide your layout with a Guideline,and a View as drag handle, embrace it with ConstraintLayout
    2. Make a "drawer handle" with a simple View (add a background color to it)
    3. Reference the guideline to the views over and under the guideline.
    4. In code, listen to onTouchEvent for your draghandle View
    5. Change the percentage of the Guideline respectively.

    layout.xml

    <layout
        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">
    
      <data>
    
      </data>
    
      <androidx.constraintlayout.widget.ConstraintLayout
          android:id="fragment_home_content_constraint"
          android:layout_widt="match_parent"
          amdroid:layout_height="match_parent">
    
    <!-- I.E. A Card on top-->
    
          <com.google.android.material.card.MaterialCardView
              android:id="@+id/top_card"
              app:layout_constraintTop_toTopOf="parent"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintBottom_toTopOf="@id/fragment_home_drag_handle"
    ...       >
    
    <!-- The drag handle -->
          <View
              android:id="@+id/fragment_home_drag_handle"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintTop_toBottomOf="@id/fragment_home_map_guideline"
              android:background="?android:attr/listDivider"
    ...       >
    
    <!-- The guideline -->
          <androidx.constraintlayout.widget.Guideline
                android:id="@+id/fragment_home_map_guideline"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                app:layout_constraintGuide_percent="0.5"
                />
    <!-- I.E. A card on bottom -->
    
          <com.google.android.material.card.MaterialCardView
              android:id="@+id/bottom_card"
              app:layout_constraintTop_toBottomOf="@id/fragment_home_drag_handle"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintBottom_toBottomOf="parent"
    ...       >
    
    
                   
    
      </androidx.constraintlayout.widget.ConstraintLayout>
    
    
    
    </layout>
    
    
    

    in code, where you can listen to changes to views. i.e. with bindings.

    
    binding.fragmentHomeDragHandle?.setOnTouchListener { v, event ->
    
                when(event.actionMasked){
                    MotionEvent.ACTION_DOWN -> return@setOnTouchListener true
                    MotionEvent.ACTION_MOVE -> {
    
                    val height = binding.fragmentHomeContentConstraint?.height?:0
    
                    val percentage = (binding.fragmentHomeMapGuideline?.layoutParams as ConstraintLayout.LayoutParams?)?.guidePercent
                        
    
                        val delta = event.y / height.toFloat()
    
                        var newPercent = percentage?.plus(delta)
    
                        if((newPercent?:0.0F)<0.04F) newPercent = 0.04F //You can go to 0, but I don't want to loose my draghandle... 4% should do
    
                        if((newPercent?:0.0F)>0.96F) newPercent = 0.96F //Likewise above, 96% shoudl do
    
                        newPercent?.let{
                            binding.fragmentHomeMapGuideline?.setGuidelinePercent(it)
                        }
    
                        Log.i(TAG, "onViewCreated: setOnTouchListener ACTION_MOVE: percentage = $percentage event.y=${event.y}, delta = $delta, newPercent = $newPercent, event.rawY = ${event.rawY}, height = $height")
                        return@setOnTouchListener true
                    }
                    MotionEvent.ACTION_UP -> {
                        v.performClick()
                        return@setOnTouchListener true
                    }
                    else-> return@setOnTouchListener false
                }
    
    
            }
    
    
    

    I hope this helps.

    (I don't like to answer my own as solved, but it did here)