在Android中通过向上/向下拉动来扩展/折叠底部框架的简单方法



我有这样的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/app_bar_main"
android:orientation="vertical">
<FrameLayout
android:id="@+id/folderContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/playerControlsContainer"
android:layout_width="match_parent"
android:layout_height="@dimen/control_view_height_min"
android:visibility="visible">
<fragment
android:id="@+id/playerControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:name="eu.zderadicka.audioserve.fragments.ControllerFragment"
/>
</FrameLayout>
</LinearLayout>

我想实现这一点:

  • 通过向上拖动(滚动(底部框架将其高度从control_view_height_min(80dp(扩展到control_view_height_max(200dp(
    • 当fram展开时,通过向下拉(滚动(将其再次拉回到control_view_height_min
    • 一边拉一边改变高度
    • 停止拖动动画后的剩余部分

我一直在看"Android向上滑动面板",但对于这种情况来说,它看起来有点太复杂了,而且它总是将底部框架拉到全屏。

所以我的问题是,在标准SDK中,最简单的方法是什么?使用什么以及如何使用?一些代码示例是否符合我的要求。

谢谢你的提示。

好的,所以我必须自己找到它。创建了FrameLayout的子类,该子类可以通过拖动进行展开/折叠。比"向上滑动面板"简单得多,更重要的是它在我的设置中起作用。如果有人感兴趣(来源于kotlin(:

package eu.zderadicka.audioserve.ui
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.widget.FrameLayout
import eu.zderadicka.audioserve.R
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
private const val LOG_TAG = "ExpandableFrameLayout"
class ExpandableFrameLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleRes: Int = 0)
: FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
var minHeight: Int = 0
var maxHeight: Int = 0
val gestureDetector: GestureDetector
var animator: ValueAnimator? = null
private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
var midHeight: Int = 0
fun animate(toValue: Int) {
animator?.cancel()
animator = ValueAnimator.ofInt(height, toValue).apply {
this.addUpdateListener {
layoutParams.height = it.animatedValue as Int
requestLayout()
}
start()
}
}
inner class MyGestureListener: GestureDetector.SimpleOnGestureListener() {
var isDragging = false
var startHeight = 0
override fun onDown(e: MotionEvent?): Boolean {
startHeight = layoutParams.height
isDragging = false
return true
}
//        override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
//            Log.d(LOG_TAG, "Fling with velocity $velocityY")
//            isDragging = true
//            if (velocityY < 0 && height < maxHeight) {
//                animate(maxHeight)
//
//            } else if (velocityY > 0 && height > minHeight) {
//                animate(minHeight)
//            }
//
//            return true
//        }
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
val dist = e1.rawY - e2.rawY
Log.d(LOG_TAG, "Scroll in Y $dist")
isDragging = true
if (dist > touchSlop && height < maxHeight) {
layoutParams.height = min(startHeight + dist.roundToInt(), maxHeight)
requestLayout()
} else if (dist < -touchSlop && height > minHeight) {
layoutParams.height = max(startHeight + dist.roundToInt(), minHeight)
requestLayout()
}
return true
}
fun dragging() = isDragging
}
val gestureListener = MyGestureListener()
init {
val customAttrs = context.theme.obtainStyledAttributes(attrs, R.styleable.ExpandableFrameLayout, 0, 0)
minHeight = customAttrs.getLayoutDimension(R.styleable.ExpandableFrameLayout_minExpandableHeight, 0)
maxHeight = customAttrs.getLayoutDimension(R.styleable.ExpandableFrameLayout_maxExpandableHeight, 1024)
midHeight = minHeight+ (maxHeight - minHeight) / 2
gestureDetector = GestureDetector(context, this.gestureListener)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
Log.d(LOG_TAG, "Player touch $event")
if (event.actionMasked == MotionEvent.ACTION_UP) {
if (layoutParams.height >= midHeight && layoutParams.height < maxHeight)
animate(maxHeight)
else if (layoutParams.height < midHeight && layoutParams.height > minHeight)
animate(minHeight)
}
return gestureDetector.onTouchEvent(event)
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
//listen also to all child events in upper part
if (event.y <= minHeight) {
onTouchEvent(event)
// need to block up event, which were dragging
if (event.actionMasked == MotionEvent.ACTION_UP) {
return gestureListener.isDragging
}
}
return super.onInterceptTouchEvent(event)
}
// this should be called from containing frame/activity in onPause
fun onPause() {
if (layoutParams.height >= minHeight) {
layoutParams?.height = minHeight
}
}

}

Gotchas:

  • 在更改高度或其他布局属性时调用requestLayout
  • 使用rawY滚动-(y与视图有关,因此滚动运动不稳定(
  • 由于子视图(在我的情况下是按钮(正在消耗运动事件,我不得不从onIntercentTouchEvent加块发送它们,然后再发送不需要的ACTION_UP事件
  • 最后,使用onScroll更容易,效果更好——它提供了更好的体验(但对于较短的底部面板(——与onFling的组合提供了更复杂的动作

我希望有人告诉我这里的那些事情,这样可以节省我几个小时。然而,看起来SO现在更多的是投票否决,而不是提供有用的提示。

相关内容

最新更新