Android BottomSheet随滑动联动的抓取手柄SVG动画实现
BottomSheet抓取手柄滑动联动形态实现方案
核心实现思路
无需引入第三方库,也不使用固定时长的AnimatedVectorDrawable,直接复用BottomSheetBehavior.BottomSheetCallback中已经可以正常获取的slideOffset参数(取值范围0~1,0代表完全折叠、1代表完全展开),通过路径实时形变计算,让手柄的形态过渡进度和用户滑动操作100%同步,完全匹配交互预期。
落地步骤
1. 前置资源对齐
先调整你已有的两个Vector资源,满足平滑形变的基础要求:
- 两个Vector的视口尺寸(
viewportWidth/viewportHeight)、描边宽度、描边颜色、线帽/线接样式完全一致 - 两个路径的节点数、绘制指令顺序完全一致,仅点位坐标存在差异。例:
折叠态扁平横条路径不要用两点结构,改成和展开态V形一致的三点结构:
M6 12 L12 12 L18 12,展开态V形路径为M6 10 L12 14 L18 10,二者都是3个绘制节点,形变时不会出现跳变。
2. 代码实现
直接在Fragment中编写逻辑,核心代码如下(Kotlin实现,Java写法逻辑完全一致):
class YourBottomSheetFragment : Fragment() { // 路径计算相关成员变量 private val collapsedHandlePath = Path() private val expandedHandlePath = Path() private val currentHandlePath = Path() private val pathEvaluator = PathEvaluatorCompat() // AndroidX兼容类,支持API14+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 初始化两个状态的路径,坐标和你的Vector资源视口对齐 val viewportSize = 24f // 折叠态:扁平横条 collapsedHandlePath.apply { reset() moveTo(6f, 12f) lineTo(12f, 12f) lineTo(18f, 12f) } // 展开态:向下V形箭头 expandedHandlePath.apply { reset() moveTo(6f, 10f) lineTo(12f, 14f) lineTo(18f, 10f) } // 初始化BottomSheet和手柄控件 val bottomSheet = view.findViewById<View>(R.id.layout_bottom_sheet) val sheetBehavior = BottomSheetBehavior.from(bottomSheet) val handleIv = view.findViewById<ImageView>(R.id.iv_sheet_handle) // 自定义Drawable实时绘制当前形态的手柄 val handleDrawable = object : Drawable() { private val drawPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = ContextCompat.getColor(requireContext(), R.color.design_handle_color) style = Paint.Style.STROKE strokeWidth = resources.getDimension(R.dimen.handle_stroke_width) strokeCap = Paint.Cap.ROUND strokeJoin = Paint.Join.ROUND } override fun draw(canvas: Canvas) { // 适配ImageView的padding和缩放 val scale = bounds.width() / viewportSize canvas.save() canvas.scale(scale, scale) canvas.drawPath(currentHandlePath, drawPaint) canvas.restore() } override fun setAlpha(alpha: Int) { drawPaint.alpha = alpha invalidateSelf() } override fun setColorFilter(colorFilter: ColorFilter?) { drawPaint.colorFilter = colorFilter invalidateSelf() } override fun getOpacity() = PixelFormat.TRANSLUCENT } handleIv.setImageDrawable(handleDrawable) // 绑定滑动回调 sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { // 业务中BottomSheet不会进入HIDDEN状态,无需额外处理隐藏逻辑 } override fun onSlide(bottomSheet: View, slideOffset: Float) { // 钳制偏移量在0~1区间,避免越界 val progress = slideOffset.coerceIn(0f, 1f) // 可选:加插值器让过渡更自然,不需要可以直接传progress // val interpolatedProgress = PathInterpolator(0.2f, 0f, 0.2f, 1f).getInterpolation(progress) pathEvaluator.evaluate(progress, collapsedHandlePath, expandedHandlePath, currentHandlePath) handleDrawable.invalidateSelf() } }) // 初始化默认折叠态 pathEvaluator.evaluate(0f, collapsedHandlePath, expandedHandlePath, currentHandlePath) handleDrawable.invalidateSelf() } }
3. 适配说明
- 该方案不存在固定动画时长问题,手指滑动多少,手柄形态就对应变化多少,完全和手势联动
- 纯路径绘制不会出现之前rotationX方案带来的透视视觉差,和设计稿的两个状态完全对齐
- 如果需要调整手柄的颜色、粗细,直接修改自定义Drawable中Paint的参数即可
内容的提问来源于stack exchange,提问作者adrem7
相关产品推荐
相关产品推荐

