I'm trying to create the following behavior using a CollapsingToolbarLayout
and other view in bottom of the AppBarLayout
but the bar isn't collapsing/expanding when I scroll/pull on the PullView
, that means isn't possible to open the bar when collapsed or close using the view.
I've already tried to use NestedScrollView
in the PullView
root but without success
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/frmContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:expanded="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<FrameLayout
android:id="@+id/frmMenu"
android:layout_width="match_parent"
android:layout_height="300dp"
android:clickable="true"
android:focusable="true"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.xpto.customview.PullView
android:id="@+id/pullDownView"
android:layout_width="50dp"
android:layout_height="wrap_content"
app:layout_anchor="@+id/appBarLayout"
app:layout_anchorGravity="bottom|center_horizontal"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
I found a cool solution using the MotionLayout
.
First of all, I'm extending the MotionLayout
to change a bit the behavior, instead of dragging in the entire MotionLayout
I only want to be possible to drag on the indicator and the menu.
TLDR: Example on Github
So my MotionLayout
overrides the OnTouchEvent
and checks if we're touching in one of the direct children.
class TouchableMotionLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: MotionLayout(context, attrs, defStyleAttr), MotionLayout.TransitionListener {
private val viewRect = Rect()
private var touchStarted = false
init {
initListener()
}
private fun initListener() {
setTransitionListener(this)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
touchStarted = false
return super.onTouchEvent(event)
}
}
if (!touchStarted) {
touchStarted = verifyIfChildHasTouched(event)
}
return touchStarted && super.onTouchEvent(event)
}
/**
* Verify if touching one fo the children.
*
* @param event The motion event.
* @return True if touch its in one of the children.
*/
private fun verifyIfChildHasTouched(event: MotionEvent): Boolean {
for (index in 0 until childCount) {
val view = getChildAt(index)
view.getHitRect(viewRect)
if (viewRect.contains(event.x.toInt(), event.y.toInt())) {
return true
}
}
return false
}
override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
touchStarted = false
}
override fun allowsTransition(p0: MotionScene.Transition?) = true
override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {}
override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {}
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {}
}
In this layout I have a viewpager and then a custom MotionLayout
, it's only possible to open/close the menu swipping in the direct children of the MotionLayout
i.e. menuIndicator
and menu
.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<androidx.viewpager.widget.ViewPager
android:id="@+id/baseViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.extmkv.example.TouchableMotionLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/menu_scene">
<FrameLayout
android:id="@+id/menuIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="48dp"
android:translationZ="1dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/menu">
<FrameLayout
android:layout_width="@dimen/menu_indicator_outside_width"
android:layout_height="@dimen/menu_indicator_outside_height"
android:layout_gravity="center"
android:background="#F00"
tools:ignore="UselessParent" />
</FrameLayout>
<LinearLayout
android:id="@+id/menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center|top"
android:background="#FFF"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<LinearLayout
android:id="@+id/lnrMenuOptionsContainer"
android:layout_width="match_parent"
android:layout_height="300dp"
android:orientation="horizontal" />
</LinearLayout>
</com.extmkv.example.TouchableMotionLayout>
</FrameLayout>
And now the scene: @xml/menu_scene.xml
You can adjust the drag and changing the attrs
in the end.
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@id/menu">
<Layout
android:layout_width="0dp"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
</Constraint>
<Constraint android:id="@id/menuIndicator">
<Layout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="@id/menu" />
</Constraint>
</ConstraintSet>
<ConstraintSet
android:id="@+id/end"
motion:deriveConstraintsFrom="@id/start">
<Constraint android:id="@id/menu">
<Layout
android:layout_width="0dp"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</Constraint>
<Constraint android:id="@id/menuIndicator">
<Layout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="@id/menu"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
</Constraint>
</ConstraintSet>
<!-- All the animations values are hardcoded for now. -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="400">
<OnSwipe
motion:dragDirection="dragDown"
motion:dragScale="0.5"
motion:maxAcceleration="10"
motion:maxVelocity="10.0" />
</Transition>
</MotionScene>
And now the final result: