You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移动ExtendedFloatingActionButton至左侧时视图缺失问题求助

问题:ExtendedFloatingActionButton拖动到左侧时视图缺失

我在使用ExtendedFloatingActionButton实现屏幕可移动功能时,发现将按钮拖动到屏幕左侧时,按钮视图会出现缺失情况。

  • 右侧正常显示效果:
    右侧正常显示效果

  • 左侧视图缺失效果:
    左侧视图缺失效果

XML布局文件

<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"
tools:context=".MainActivity">

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:icon="@drawable/ic_baseline_4g_mobiledata_24"
    android:text=""
    app:iconTint="@color/white"
    android:textColor="@color/white"
    app:backgroundTint="@color/black"
    app:iconGravity="end"
    android:textAllCaps="false"
    android:layout_gravity="center|end"
    android:layout_marginBottom="20dp"
    android:layout_marginEnd="20dp"/>
</FrameLayout>

MainActivity代码

class MainActivity : AppCompatActivity(), View.OnTouchListener {
lateinit var fab: ExtendedFloatingActionButton

private val CLICK_DRAG_TOLERANCE =
    10f // Often, there will be a slight, unintentional, drag when the user taps the FAB, so we need to account for this.


private var downRawX = 0f
private  var downRawY:kotlin.Float = 0f
private var dX = 0f
private  var dY:kotlin.Float = 0f

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val view: View = findViewById(R.id.fab);
    view.setOnTouchListener(this);

    fab = findViewById(R.id.fab)

    fab.shrink()

    fab.setOnClickListener {
        if (!fab.isExtended) {
            fab.extend()
            fab.text = "Boost: Off \nPing: 99 Jitter: 15"
            fab.maxLines = 2
            fab.background = applicationContext.getDrawable(R.drawable.fab_back)
        } else {
            fab.shrink()
            fab.background = applicationContext.getDrawable(R.drawable.circle_fab)

        }
    }
}

override fun onTouch(view: View?, motionEvent: MotionEvent): Boolean {

    val action = motionEvent.action
    return if (action == MotionEvent.ACTION_DOWN) {
        downRawX = motionEvent.rawX
        downRawY = motionEvent.rawY
        dX = view!!.x - downRawX
        dY = view!!.y - downRawY
        true // Consumed
    } else if (action == MotionEvent.ACTION_MOVE) {
        val viewWidth = view!!.width
        val viewHeight = view!!.height
        val viewParent = view!!.parent as View
        val parentWidth = viewParent.width
        val parentHeight = viewParent.height
        var newX = motionEvent.rawX + dX
        newX = Math.max(0f, newX) // Don't allow the FAB past the left hand side of the parent
        newX = Math.min(
            (parentWidth - viewWidth).toFloat(),
            newX)

        if (newX < 1.0) {
            fab.iconGravity = MaterialButton.ICON_GRAVITY_START
        } else {
            fab.iconGravity = MaterialButton.ICON_GRAVITY_END
        }
        Log.e("error", newX.toString())
        // Don't allow the FAB past the right hand side of the parent
        var newY = motionEvent.rawY + dY
        newY = Math.max(0f, newY) // Don't allow the FAB past the top of the parent
        newY = Math.min(
            (parentHeight - viewHeight).toFloat(),
            newY
        ) // Don't allow the FAB past the bottom of the parent
        view!!.animate()
            .x(newX)
            .y(newY)
            .setDuration(0)
            .start()
        true // Consumed
    } else if (action == MotionEvent.ACTION_UP) {
        val upRawX = motionEvent.rawX
        val upRawY = motionEvent.rawY
        val upDX = upRawX - downRawX
        val upDY = upRawY - downRawY
        if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { // A click
            performClick()
        } else { // A drag
            true // Consumed
        }
    } else {
        super.onTouchEvent(motionEvent)
    }
}

private fun performClick(): Boolean {

    if (!fab.isExtended) {
        fab.extend()
        fab.text = "Boost: Off \nPing: 99 Jitter: 15"
        fab.maxLines = 2
        fab.background = applicationContext.getDrawable(R.drawable.fab_back)
    } else {
        fab.shrink()
        fab.background = applicationContext.getDrawable(R.drawable.circle_fab)

    }
    return true
}
}

问题原因及解决方案

原因

  1. 边界计算未考虑视图内边距:ExtendedFloatingActionButton内部有默认padding,直接将newX限制为0f会让按钮贴紧父容器左边缘,导致部分内容超出可视范围被截断。
  2. 图标重力切换阈值不合理:当前用newX < 1.0作为切换条件,阈值过小,按钮未移动到合理位置就触发布局变更,引发显示异常。
  3. 手动替换背景破坏默认布局逻辑:直接修改fab.background会覆盖ExtendedFloatingActionButton的默认样式,导致布局计算出错。

解决方案

1. 调整边界计算,添加安全边距

在ACTION_MOVE中修改newX的最小值限制,给左侧保留足够边距:

// 先在res/values/dimens.xml中定义<dimen name="fab_safe_margin">16dp</dimen>
val safeMargin = resources.getDimensionPixelSize(R.dimen.fab_safe_margin).toFloat()
newX = Math.max(safeMargin, newX)

2. 优化图标重力切换阈值

将切换条件改为基于屏幕中线判断,逻辑更合理:

if (newX < parentWidth / 2f) {
    fab.iconGravity = MaterialButton.ICON_GRAVITY_START
} else {
    fab.iconGravity = MaterialButton.ICON_GRAVITY_END
}

3. 用背景色 tint 替代直接替换背景

保留控件默认布局逻辑,通过backgroundTint修改背景:

// 替换原点击事件中的background设置代码
if (!fab.isExtended) {
    fab.extend()
    fab.text = "Boost: Off \nPing: 99 Jitter: 15"
    fab.maxLines = 2
    fab.backgroundTint = ContextCompat.getColorStateList(this, R.color.fab_extended_bg)
} else {
    fab.shrink()
    fab.backgroundTint = ContextCompat.getColorStateList(this, R.color.black)
}

在res/values/colors.xml中添加对应颜色定义。

内容的提问来源于stack exchange,提问作者Abdul Rehman Jamil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 13:38:02