Expand/Collapse 拉取视图的工具栏
Expand/Collapse Toolbar pulling on a view
我正在尝试使用 CollapsingToolbarLayout
和 AppBarLayout
底部的其他视图创建以下行为,但是当我 scroll/pull 在 PullView
上,这意味着在使用视图折叠或关闭时无法打开栏。
我已经尝试在 PullView
根中使用 NestedScrollView
但没有成功
<?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>
我使用 MotionLayout
.
找到了一个很棒的解决方案
首先,我扩展了 MotionLayout
来稍微改变一下行为,而不是拖入整个 MotionLayout
我只希望能够在指示器和菜单。
TLDR: Example on Github
所以我的 MotionLayout
覆盖 OnTouchEvent
并检查我们是否正在接触其中一个直接子级。
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) {}
}
在这个布局中,我有一个 viewpager,然后是一个自定义 MotionLayout
,它只能 open/close 在 MotionLayout
的直接子项中滑动菜单,即 menuIndicator
和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>
现在的场景:@xml/menu_scene.xml
您可以调整拖动并在最后更改attrs
。
<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>
现在的最终结果是:
我正在尝试使用 CollapsingToolbarLayout
和 AppBarLayout
底部的其他视图创建以下行为,但是当我 scroll/pull 在 PullView
上,这意味着在使用视图折叠或关闭时无法打开栏。
我已经尝试在 PullView
根中使用 NestedScrollView
但没有成功
<?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>
我使用 MotionLayout
.
首先,我扩展了 MotionLayout
来稍微改变一下行为,而不是拖入整个 MotionLayout
我只希望能够在指示器和菜单。
TLDR: Example on Github
所以我的 MotionLayout
覆盖 OnTouchEvent
并检查我们是否正在接触其中一个直接子级。
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) {}
}
在这个布局中,我有一个 viewpager,然后是一个自定义 MotionLayout
,它只能 open/close 在 MotionLayout
的直接子项中滑动菜单,即 menuIndicator
和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>
现在的场景:@xml/menu_scene.xml
您可以调整拖动并在最后更改attrs
。
<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>
现在的最终结果是: