RecyclerView/NestedScrollView内嵌HIChartView导致滑动卡顿问题
将HighCharts库提供的HIChartView放置在NestedScrollView或RecyclerView内部时,手指触摸HIChartView区域向上滑动页面,容器滑动会出现严重卡顿;该问题仅在触摸HIChartView区域滑动时触发,触摸图表外区域滑动时容器滑动十分流畅。
当图表上存在可见tooltip时用户触发滑动,卡顿现象会更加明显,严重影响交互体验。
已知HIChartView继承自RelativeLayout,已尝试两种集成方式均无法解决问题:
- 方式1:将
HIChartView嵌套在NestedScrollView中,对应布局代码如下:
<androidx.core.widget.NestedScrollView android:id="@+id/nsv_view_trend" android:layout_width="match_parent" android:layout_height="0dp" android:background="@color/background_interface_secondary" app:layout_constraintBottom_toTopOf="@id/cv_footer" app:layout_constraintTop_toBottomOf="@id/cl_header"> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.highsoft.highcharts.core.HIChartView android:id="@+id/trend_graph" android:layout_width="match_parent" android:layout_height="300dp" android:layout_marginTop="@dimen/space_20dp" app:layout_constraintTop_toBottomOf="@id/chart_label" /> <FooterView android:id="@+id/privacy_policy_footer_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/space_16dp" android:layout_marginTop="@dimen/space_24dp" android:layout_marginBottom="@dimen/space_16dp" android:visibility="gone" tools:visibility="visible" /> </androidx.appcompat.widget.LinearLayoutCompat> </androidx.core.widget.NestedScrollView>
- 方式2:将
HIChartView作为RecyclerView的列表子项嵌入。
卡顿核心原因是HIChartView内部基于WebView实现,自带手势处理逻辑会默认消费触摸事件,打断父容器的嵌套滑动机制;tooltip显示时内部JS会实时重绘元素,进一步占用主线程资源导致卡顿加重,可通过以下方案组合修复:
1. 关闭图表内置的纵向手势消费
初始化图表配置时,禁用图表自身的纵向平移、触摸跟随逻辑,将滑动事件交还给父容器处理:
val options = HIOptions() val chart = HIChart() // 关闭图表内置平移能力 chart.panning = HIPanning().apply { enabled = false } // 清空缩放配置,避免触摸事件被内部拦截 chart.zoomType = "" options.chart = chart // 关闭tooltip触摸跟随重绘 val tooltip = HITooltip().apply { followTouchMove = false } options.tooltip = tooltip trend_graph.options = options
Java代码对应调用set方法实现相同配置即可。
2. 自定义控件修正事件分发逻辑
重写HIChartView的触摸事件分发逻辑,检测到纵向滑动时主动将事件交还给父容器,避免事件被内部WebView消费:
class NestedCompatibleHIChartView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : HIChartView(context, attrs, defStyleAttr) { private var startX = 0f private var startY = 0f private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { when (ev.action) { MotionEvent.ACTION_DOWN -> { startX = ev.x startY = ev.y parent.requestDisallowInterceptTouchEvent(true) } MotionEvent.ACTION_MOVE -> { val dx = abs(ev.x - startX) val dy = abs(ev.y - startY) // 判定为纵向滑动时,交由父容器处理 if (dy > dx && dy > touchSlop) { parent.requestDisallowInterceptTouchEvent(false) return false } } } return super.onInterceptTouchEvent(ev) } }
将布局文件中原生HIChartView替换为上述自定义控件即可生效。
3. 滑动过程临时暂停图表渲染
针对tooltip显示时的卡顿问题,给父容器添加滑动监听,滑动过程中临时暂停图表渲染,滑动停止后再恢复,避免主线程被渲染逻辑占用:
// NestedScrollView场景示例 private val resumeRenderRunnable = Runnable { trend_graph.resumeRendering() } nsv_view_trend.viewTreeObserver.addOnScrollChangedListener { trend_graph.pauseRendering() handler.removeCallbacks(resumeRenderRunnable) // 停止滑动100ms后恢复渲染 handler.postDelayed(resumeRenderRunnable, 100) }
RecyclerView场景可通过addOnScrollListener实现相同逻辑。
补充:旧版本HighCharts库未给WebView开启硬件加速也会导致滑动卡顿,可在初始化时调用
trend_graph.setLayerType(View.LAYER_TYPE_HARDWARE, null),能进一步提升滑动流畅度。
内容的提问来源于stack exchange,提问作者K Pradeep Kumar Reddy

