/ Android

ドラッグ操作による状態変化を無効にしたBottomSheetBehaviorの実装

Hide、Collapse、Expandという三つの状態を持たせることができ、シートを画面下からにゅっと出したり、逆にするっと縮めたり隠したりといったMaterial DesignでのBottom sheetsなレイアウトを簡単に実装できるのがBottomSheetBehavior。もちろんドラッグ操作によってこの状態を変えることができ、これを目的としてBottomSheetBehaviorを用いることが普通かと思う。
ドラッグ操作による状態変化が望ましくない、ボタン操作によってのみ可能となるようにしたい場合は、Fragment Transaction Animationを用いるなどして実装するのがベタだが、BottomSheetBehaviorを用いつつこれを実現することも一応可能であるため、その方法をメモする。

LockableBottomSheetBehaviorSample-3

ドラッグ操作による状態変化を制限したBottomSheetBehaviorの実装

まずは、以下ソースのようにBottomSheetBehaviorを継承し、タッチイベント・スクロールイベント・フリックイベントを無効にできるクラスを作成する。

package link.k3n.lockable_bottom_sheet_behavior_sample

import android.content.Context
import android.support.design.widget.BottomSheetBehavior
import android.support.design.widget.CoordinatorLayout
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View

/**
 * タッチ・ドラッグ操作を無効化可能なBottomSheetBehavior
 */
class LockableBottomSheetBehavior<V: View>(context: Context, attrs: AttributeSet): BottomSheetBehavior<V>(context, attrs) {

    /**
     * タッチ・ドラッグ操作を無効化するならtrue
     */
    var locked = false

    override fun onInterceptTouchEvent(parent: CoordinatorLayout?, child: V, event: MotionEvent?): Boolean {
        return if (locked) false
        else return super.onInterceptTouchEvent(parent, child, event)
    }

    override fun onTouchEvent(parent: CoordinatorLayout?, child: V, event: MotionEvent?): Boolean {
        return if (locked) false
        else return super.onTouchEvent(parent, child, event)
    }

    override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
        return if (locked) false
        else return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
    }

    override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
        if (!locked) super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
    }

    override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {
        if (!locked) super.onStopNestedScroll(coordinatorLayout, child, target, type)
    }

    override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout, child: V, target: View, velocityX: Float, velocityY: Float): Boolean {
        return if (locked) false
        else return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
    }
}

これを、レイアウトXMLファイル中で、BottomSheetBehaviorの代わりに指定する。

<?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"
  tools:context="link.k3n.lockable_bottom_sheet_behavior_sample.MainActivity.MainActivity">

  <android.support.design.widget.CoordinatorLayout
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--region Main contents-->

    <RelativeLayout
      android:id="@+id/contents_root_layout"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_marginBottom="50dp">

      <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Hello World!"/>

    </RelativeLayout>

    <!--endregion-->

    <!--region BottomSheetBehavior-->

    <RelativeLayout
      android:id="@+id/bottom_sheet_behavior_root_layout"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#FFFFFFFF"
      app:layout_behavior="link.k3n.lockable_bottom_sheet_behavior_sample.LockableBottomSheetBehavior"
      app:behavior_peekHeight="50dp"
      app:behavior_hideable="false">

      <!--region BottomSheetBehavior Bar-->

      <LinearLayout
        android:id="@+id/bottom_sheet_behavior_bar_root_layout"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">

        <ImageButton
          android:id="@+id/bottom_sheet_behavior_toggle_button"
          android:layout_width="24dp"
          android:layout_height="24dp"
          android:layout_marginStart="8dp"
          android:layout_gravity="center_vertical"
          android:background="@mipmap/ic_launcher"
          android:clickable="true"
          android:focusable="true"
          tools:ignore="ContentDescription"/>

        <TextView
          android:id="@+id/bottom_sheet_behavior_status_text"
          android:layout_width="0dp"
          android:layout_height="match_parent"
          android:layout_weight="1"
          android:textColor="#4c4c4c"
          android:textSize="28sp"
          android:gravity="center"
          android:text="Collapse"/>

      </LinearLayout>

      <!--endregion-->

      <!--region BottomSheetBehavior contents-->

      <LinearLayout
        android:id="@+id/bottom_sheet_behavior_contents_root_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/bottom_sheet_behavior_bar_root_layout"
        android:layout_above="@id/bottom_sheet_behavior_edit_text_root_layout"
        android:orientation="vertical"
        android:background="@android:color/darker_gray">

        <TextView
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:gravity="center"
          android:textSize="14sp"
          android:textColor="#808080"
          android:text="Hello World!"/>

      </LinearLayout>

      <!--endregion-->

      <!--region BottomSheetBehavior bottom EditText-->

      <FrameLayout
        android:id="@+id/bottom_sheet_behavior_edit_text_root_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">

        <EditText
          android:id="@+id/bottom_sheet_behavior_edit_text"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:paddingEnd="40dp"
          android:textSize="14sp"
          android:textColor="#4c4c4c"
          android:textColorHint="#808080"
          android:maxLines="3"
          android:inputType="textMultiLine"
          android:hint="Input text here!"
          tools:ignore="LabelFor"/>

        <ImageButton
          android:id="@+id/bottom_sheet_behavior_clear_button"
          android:layout_width="25dp"
          android:layout_height="25dp"
          android:layout_gravity="center_vertical|end"
          android:layout_marginEnd="35dp"
          android:background="@mipmap/ic_launcher_round"
          android:foreground="?android:selectableItemBackgroundBorderless"
          tools:ignore="ContentDescription"/>

        <ImageButton
          android:id="@+id/bottom_sheet_behavior_send_button"
          android:layout_width="25dp"
          android:layout_height="25dp"
          android:layout_gravity="center_vertical|end"
          android:layout_marginEnd="5dp"
          android:background="@mipmap/ic_launcher_round"
          android:foreground="?android:selectableItemBackgroundBorderless"
          tools:ignore="ContentDescription"/>

      </FrameLayout>

      <!--endregion-->

    </RelativeLayout>

    <!--endregion-->

  </android.support.design.widget.CoordinatorLayout>

</layout>

あとは、BottomSheetBehavior.fromメソッドより取得したインスタンスを先ほど作成したLockableBottomSheetBehaviorにキャストして取得し、ドラッグ操作等を無効にするフラグを立てる。

適当なボタンにClickEvent Listenerを設定し、BottomSheetBehaviorの現在の状態を取得してボタンクリック時に任意の状態に変化させるといい。

package link.k3n.lockable_bottom_sheet_behavior_sample

import android.databinding.DataBindingUtil
import android.os.Bundle
import android.support.design.widget.BottomSheetBehavior
import android.support.v7.app.AppCompatActivity
import link.k3n.lockable_bottom_sheet_behavior_sample.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        // BottomSheetBehavior自体のドラッグ操作等によるExpand・Collapse操作を無効にする
        val behavior = BottomSheetBehavior.from(binding.bottomSheetBehaviorRootLayout) as LockableBottomSheetBehavior
        behavior.locked = true

        binding.bottomSheetBehaviorToggleButton.setOnClickListener {
            // ボタンクリックによって、Expand・Collapseを制御する
            when (behavior.state) {
                BottomSheetBehavior.STATE_COLLAPSED -> {
                    behavior.state = BottomSheetBehavior.STATE_EXPANDED
                    binding.bottomSheetBehaviorStatusText.text = "Expanded"
                }
                BottomSheetBehavior.STATE_EXPANDED -> {
                    behavior.state = BottomSheetBehavior.STATE_COLLAPSED
                    binding.bottomSheetBehaviorStatusText.text = "Collapsed"
                }
            }
        }

        binding.bottomSheetBehaviorClearButton.setOnClickListener {
            binding.bottomSheetBehaviorEditText.setText("")
        }
    }
}