已开启
HarmonyOS 端 hitSlop.bottom 使用视图宽度计算底部边界,导致非正方形视图底部扩展触摸区域无法命中 #76
pyang5166创建于  7月19日
pyang5166
pyang5166
7月19日 创建

问题描述
计算 hitSlop.bottom 命中区域时,底部边界使用了视图宽度 width,而不是视图高度 height。
例如目标视图尺寸是 width=200, height=400,并设置:

<PanGestureHandler hitSlop={{bottom: 100}}>

此时底部可触摸范围应从 y=0..400 扩展到 y=0..500。但当前 Harmony 实现会把底部边界算成 width + bottom = 200 + 100 = 300,导致 y=300..500 这段区域无法正确命中手势。这个问题在非正方形视图上很明显,正方形视图会掩盖该错误。

复现代码

import React, {useState} from 'react';
import {SafeAreaView, StatusBar, StyleSheet, Text, View} from 'react-native';
import {
  GestureHandlerRootView,
  PanGestureHandler,
  State,
} from 'react-native-gesture-handler';

type PanEvent = {
  nativeEvent: {
    absoluteX?: number;
    absoluteY?: number;
    state?: number;
    translationY?: number;
  };
};

function format(value?: number): string {
  if (typeof value !== 'number' || !Number.isFinite(value)) {
    return '-';
  }
  return value.toFixed(1);
}

export default function App(): JSX.Element {
  const [status, setStatus] = useState('touch the red bottom hitSlop area');
  const [lastTouch, setLastTouch] = useState('none');
  const [events, setEvents] = useState(0);

  const onGestureEvent = ({nativeEvent}: PanEvent) => {
    setEvents(prev => prev + 1);
    setStatus('handler received gesture event');
    setLastTouch(
      `x=${format(nativeEvent.absoluteX)}, y=${format(
        nativeEvent.absoluteY,
      )}, dy=${format(nativeEvent.translationY)}`,
    );
    console.log('[rngh-hitSlop-bottom-repro]', nativeEvent);
  };

  const onStateChange = ({nativeEvent}: PanEvent) => {
    if (nativeEvent.state === State.ACTIVE) {
      setStatus('ACTIVE: accepted by hitSlop');
    } else if (nativeEvent.state === State.FAILED) {
      setStatus('FAILED: rejected');
    } else if (nativeEvent.state === State.END) {
      setStatus('END: gesture completed');
    } else if (nativeEvent.state === State.CANCELLED) {
      setStatus('CANCELLED');
    }
  };

  return (
    <GestureHandlerRootView style={styles.root}>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.safeArea}>
        <View style={styles.page}>
          <Text style={styles.title}>hitSlop bottom boundary repro</Text>
          <Text style={styles.description}>
            Target is 200 x 400 with hitSlop bottom 100. The red area from
            y=400 to y=500 should trigger, but the buggy boundary stops near
            y=300 when bottom is calculated from width.
          </Text>

          <View style={styles.statusPanel}>
            <Text style={styles.status}>{status}</Text>
            <Text style={styles.detail}>events: {events}</Text>
            <Text style={styles.detail}>last: {lastTouch}</Text>
          </View>

          <View style={styles.stage}>
            <PanGestureHandler
              hitSlop={{bottom: 100}}
              minDist={1}
              onGestureEvent={onGestureEvent}
              onHandlerStateChange={onStateChange}>
              <View collapsable={false} style={styles.target}>
                <Text style={styles.targetText}>200 x 400 target</Text>
                <Text style={styles.targetHint}>view bottom: y=400</Text>
              </View>
            </PanGestureHandler>

            <View pointerEvents="none" style={styles.expectedArea}>
              <Text style={styles.expectedText}>
                expected bottom hitSlop: y=400..500
              </Text>
            </View>

            <View pointerEvents="none" style={styles.bugBoundary}>
              <Text style={styles.bugText}>
                buggy boundary: width + bottom = 300
              </Text>
            </View>
          </View>
        </View>
      </SafeAreaView>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  root: {
    flex: 1,
    backgroundColor: '#f5f7fa',
  },
  safeArea: {
    flex: 1,
  },
  page: {
    flex: 1,
    alignItems: 'center',
    paddingHorizontal: 18,
    paddingTop: 24,
  },
  title: {
    color: '#111827',
    fontSize: 20,
    fontWeight: '700',
    textAlign: 'center',
  },
  description: {
    color: '#334155',
    fontSize: 13,
    lineHeight: 19,
    marginTop: 10,
    maxWidth: 360,
    textAlign: 'center',
  },
  statusPanel: {
    width: '100%',
    maxWidth: 360,
    minHeight: 86,
    borderColor: '#cbd5e1',
    borderRadius: 8,
    borderWidth: 1,
    backgroundColor: '#ffffff',
    marginTop: 14,
    paddingHorizontal: 12,
    paddingVertical: 10,
  },
  status: {
    color: '#0f172a',
    fontSize: 15,
    fontWeight: '700',
  },
  detail: {
    color: '#475569',
    fontSize: 12,
    marginTop: 6,
  },
  stage: {
    width: 240,
    height: 520,
    alignItems: 'center',
    backgroundColor: '#ffffff',
    borderColor: '#cbd5e1',
    borderWidth: 1,
    marginTop: 16,
    position: 'relative',
  },
  target: {
    width: 200,
    height: 400,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#2563eb',
  },
  targetText: {
    color: '#ffffff',
    fontSize: 18,
    fontWeight: '800',
    textAlign: 'center',
  },
  targetHint: {
    color: '#dbeafe',
    fontSize: 12,
    marginTop: 8,
  },
  expectedArea: {
    position: 'absolute',
    left: 20,
    top: 400,
    width: 200,
    height: 100,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#ef4444',
    opacity: 0.72,
  },
  expectedText: {
    color: '#ffffff',
    fontSize: 13,
    fontWeight: '700',
    textAlign: 'center',
  },
  bugBoundary: {
    position: 'absolute',
    left: 10,
    top: 300,
    width: 220,
    borderTopColor: '#111827',
    borderTopWidth: 2,
  },
  bugText: {
    color: '#111827',
    fontSize: 12,
    fontWeight: '700',
    marginTop: 3,
    textAlign: 'center',
  },
});

预期结果
对于 200 x 400 的目标视图,整块区域都可以触发手势。

实际结果
对于 200 x 400 的目标视图和 bottom: 100,实际边界约为 y=300,导致 y=300..500 之间本应可命中的底部扩展区域无法触发手势。

备注
复现.mp4

likedislike
pyang5166pyang5166
7月19日 修改了issue 的描述
dingchengjie
dingchengjie成员
8月3日 评论:

你好开发者,已收到问题,内部会进行确认分析

likedislike
dingchengjie
dingchengjie成员
12 天前 评论:

你好开发者,已经确认是问题,近期会修复发版本

likedislike