Skip to content

Horizontal drag scrolling bug #611

@xiangkesi

Description

@xiangkesi

This package has external dependencies of react-native-reanimated and react-native-gesture-handler which must be installed separately. Before opening an issue related to animations or gestures please verify that you have completed ALL installation steps, including the changes to MainActivity.

Describe the bug
When I set the item to have a fixed size and allow horizontal dragging with automatic scrolling, the {data, from, to} parameters in the onDragEnd callback do not match the position where the dragging stops.

To Reproduce

Platform & Dependencies
Please list any applicable dependencies in addition to those below (react-navigation etc).

  • react-native-draggable-flatlist version:4.0.5
  • Platform: ios
  • React Native or Expo version:0.77
  • Reanimated version:3.17.0
  • React Native Gesture Handler version:2.23.1

Additional context

import React from 'react';
import {DraggableFlatList, ScaleDecorator, GestureHandlerRootView} from './DraggableContainer';
import {XView} from '@ctrip/xtaro';

const DEFAULT_MAX_COUNT = 20;
const _DraggableListContainer = (props) => {
const {
dataSource = [],
renderItem,
keyExtractor,
listFooterComponent,
onPlaceholderIndexChange,
maxCount = DEFAULT_MAX_COUNT,
onDragEnd,
onDragBegin,
onRelease,
} = props;

const _renderItem = ({item, drag, isActive}) => {
    return (
        <XView
            style={{
                // backgroundColor: 'red',
                // width: 64,
                height: '100%',
                aspectRatio: 1 / 1,
                marginRight: 6,
                // paddingRight: 6,
            }}
        >
            {<ScaleDecorator>{renderItem({item, drag, isActive})}</ScaleDecorator>}
        </XView>
    );
};

const _renderPlaceholder = () => {
    return (
        <XView
            style={{
                height: '100%',
                aspectRatio: 1 / 1,
                // backgroundColor: 'blue',
                // width: 64,
                // height: 64,
                backgroundColor: 'transparent',
            }}
        ></XView>
    );
};

const _listFooterComponent = () => {
    if (listFooterComponent && dataSource.length < maxCount) {
        return listFooterComponent();
    }
    return null;
};

return (
    <GestureHandlerRootView style={{flex: 1, overflow: 'hidden', paddingRight: 16, paddingLeft: 16}}>
        {
            //
            <DraggableFlatList
                style={{width: '100%', height: '100%'}}
                // contentContainerStyle={{width: 64 * 9}}
                initialNumToRender={20}
                windowSize={41}
                // autoscrollThresholdTop={30}
                // autoscrollSpeed={300}
                onPlaceholderIndexChange={onPlaceholderIndexChange}
                onRelease={onRelease}
                // enableLayoutAnimationExperimental={false}
                // autoscrollSpeed={150}
                // autoscrollThreshold={50}
                // dragHitSlop={{left: 40, right: 40}}
                // autoscrollThresholdTop={20}
                // autoscrollThresholdBottom={20}
                // ItemSeparatorComponent={() => {
                //     return <XView style={{width: 6}}></XView>;
                // }}
                horizontal={true}
                bounces={false}
                scrollEnabled={true}
                ListFooterComponent={_listFooterComponent}
                // ListHeaderComponent={() => {
                //     return <XView style={{width: 64}}></XView>;
                // }}
                // onScrollOffsetChange={(offset) => {
                //     console.log('offset', offset);
                // }}
                showsHorizontalScrollIndicator={false}
                showsVerticalScrollIndicator={false}
                data={dataSource}
                renderItem={_renderItem}
                renderPlaceholder={_renderPlaceholder}
                dragItemOverflow={true}
                keyExtractor={keyExtractor}
                onDragEnd={onDragEnd}
                onDragBegin={onDragBegin}
            />
        }
    </GestureHandlerRootView>
);

};

const DraggableListContainer = React.memo(_DraggableListContainer);

export {DraggableListContainer};

import React, {useEffect, useState, useContext, useRef} from 'react';
import {XView, xUbt} from '@ctrip/xtaro';
import {StyleSheet} from 'react-native';
import {DraggableListContainer} from '../../../../componend/DraggableView';
import AddView from './component/AddView';
import DragItem from './component/DragItem';
import {TripShootContext} from '../../context/contextProvider';
import {sharkFormat} from '../../../../utils/shark';
import {SHARK_KEYS} from '../../../../utils/shark/constant';
import {MediaType, PublishType} from '../../controller/TripShootModel.js';
import trackingKeys from '../../trackingKeys';
import uiConstants from '../Util/UIConstans';
//http://10.32.1.3:5389/index.ios.bundle?CRNModuleName=ugcProduction&CRNType=1&initialPage=tripShootPage&isTransparentBg=YES&publishType=dynamic&isTransparentBgWithNav=YES&source=&disableAnimation=YES&sourceType=185&sourceScene=simpleArticleDetail
//http://10.32.1.3:5389/index.ios.bundle?CRNModuleName=ugcProduction&CRNType=1&initialPage=tripShootPage&publishType=dynamic&source=community&sourceType=185&isTransparentBg=YES&articleId=2012464350&sourceScene=simpleArticleDetail&pageName=/pages/tripShootPage
import DragController from './controller/DragController';
const normalWH = 64;
function getTitle(type) {
if (type === MediaType.Image) {
return sharkFormat(SHARK_KEYS.simplifyPublishPhotoUploadText);
} else if (type === MediaType.Video) {
return '上传视频';
} else {
return ${sharkFormat(SHARK_KEYS.simplifyPublishPictureText)}/${sharkFormat(SHARK_KEYS.simplifyPublishVideoText)};
}
}

function getNewData(dataList) {
if (!dataList || dataList.length === 0) {
return [];
}
const total = dataList.length;
for (let index = 0; index < dataList.length; index++) {
const element = dataList[index];
element.dragIndex = index;
element.dragTotalCount = total;
}
return dataList;
// const total = dataList.length;
// return dataList.map((item, index) => {
// return {
// ...item,
// dragIndex: index,
// dragTotalCount: total,
// };
// });
}

const DragViewV2 = ({dataList = [], type = MediaType.Default, onClickDelete, onDataChange}) => {
/**
* 控制器
/
const controller = useContext(TripShootContext)?.controller;
const useIsDraggingRef = useRef(false);
const usePlaceholderIndexRef = useRef(-1);
/
*
* 来源
/
const _source = controller?.filteredParams?.source;
/
*
* 来源类型
/
const _sourceType = controller?.filteredParams?.sourceType;
/
*
* 最大数量
/
const maxCount = controller?.publishRule?.imageMaxCount || 9;
/
*
* 视频最大大小
/
const videoMaxSize = controller?.publishRule?.videoMaxSize || 500;
/
*
* 是不是编辑
*/
const isEdit = controller?.action === PublishType.Update ? true : false;

/**
 * 拖拽控制器
 */
const [dragController] = useState(new DragController());

/**
 * 列表数据
 */
const [dataSource, setDataSource] = useState([]);

/**
 * 是不是正在拖动
 */
// const [isDragging, setIsDragging] = useState(false);

/**
 * 当前类型
 */
const [mediaType, setMediaType] = useState(type);

/**
 * 比例大小, 键盘升起或者回落的时候, 需要根据比例大小来计算高度
 */
const [scaleRatio, setScaleRatio] = useState(1);

/**
 * 数据变化
 */
useEffect(() => {
    console.log('dataList来了吗');
    const newData = dataList ? getNewData(dataList) : [];
    setMediaType(type);
    setDataSource(newData);
}, [dataList, type]);
/**
 * 高度变化
 */
useEffect(() => {
    const subscrption = controller?.registerHeightControl((info) => {
        setScaleRatio(info.keyboardShow ? 0.64 : 1);
    });
    return () => {
        subscrption?.remove();
    };
}, [controller]);

/**
 * 添加媒体
 * @returns
 */
const _clickAddMedia = () => {
    //
    if (dataSource.length >= maxCount) return;
    xUbt.logTrace(trackingKeys.click.addPhotoOrVideo, {
        sourcefrom: _source,
        sourceType: _sourceType,
    });

    dragController.goImageVideoChoose(
        {
            maxCount: maxCount,
            arrayCount: dataSource.length,
            mediaType: mediaType,
            videoMaxSize: videoMaxSize,
        },
        (data, isImage, isVideo) => {
            //组合数据
            const newData = getNewData([...dataSource, ...data]);
            //外面赋值
            _setPublishNodes(newData, isVideo);
            if (newData.length === 0) {
                //没有数据
                setMediaType(MediaType.Default);
            } else if (isImage) {
                setMediaType(MediaType.Image);
            } else if (isVideo) {
                setMediaType(MediaType.Video);
            }
            setDataSource(newData);
            onDataChange && onDataChange(newData, true);
        },
    );
};

/**
 * 删除
 * @param {*} index
 */
const _onDelete = (index) => {
    const newData = [...dataSource];
    const deleteItem = newData[index];
    newData.splice(index, 1);
    _setPublishNodes(newData, mediaType === MediaType.Video ? true : false);
    _updateListData(newData, false);

    //回调出去
    onClickDelete && onClickDelete(index, deleteItem, mediaType);
    /**
     * 埋点
     */
    if (mediaType === MediaType.Video) {
        xUbt.logDevTrace(trackingKeys.click.deleteVideo, {
            sourcefrom: _source,
            sourceType: _sourceType,
        });
    } else {
        xUbt.logDevTrace(trackingKeys.click.deletePhoto, {
            sourcefrom: _source,
            sourceType: _sourceType,
        });
    }
};

const _onDragBegin = () => {
    useIsDraggingRef.current = true;
    usePlaceholderIndexRef.current = -1;
    // setIsDragging(true);
    // placeholderIndexToRef.current = -1;
    // placeholderLastIndexRef.current = -1;
    // placeholderCurrentIndexRef.current = -1;
};

/**
 * 结束编辑
 * @param {*} param0
 */
const _onDragEnd = ({data, from, to}) => {
    useIsDraggingRef.current = false;
    const actualTo = usePlaceholderIndexRef.current !== -1 ? usePlaceholderIndexRef.current : to;
    usePlaceholderIndexRef.current = -1;

    const correctedTo = Math.max(0, Math.min(actualTo, data.length - 1));
    if (correctedTo !== to) {
        // console.log('修正to值:', to, '->', correctedTo);
        // 重新计算数据
        const findItem = data[to];
        const newData = [...data];
        newData.splice(to, 1);
        newData.splice(correctedTo, 0, findItem);
        setTimeout(() => {
            _updateListData(newData, false);
            _setPublishNodes(newData, mediaType === MediaType.Video ? true : false);
        }, 100);
    } else {
        _updateListData(data, false);
        _setPublishNodes(data, mediaType === MediaType.Video ? true : false);
    }
};

const _updateListData = (mediaList = [], needUpload = false) => {
    //重置索引
    const newDataList = getNewData(mediaList);

    if (newDataList.length === 0) {
        setMediaType(MediaType.Default);
    }
    //刷新页面
    setDataSource(newDataList);

    //更新外部数据
    onDataChange && onDataChange(newDataList, needUpload);
};
const _setPublishNodes = (originalArray = [], isVideo = false) => {
    if (!controller) return;
    setTimeout(() => {
        const dataNode = dragController.generateNewArray({
            originalArray: originalArray,
            isVideo: isVideo,
            isEdit: isEdit,
        });
        controller.tripShoot.nodes = dataNode.newArray;
        controller.tripShoot.coverImage = dataNode.coverImage;
        if (originalArray.length === 0) {
            controller.tripShoot.coverImage = {};
        }
    }, 0);
};
/**
 *
 * @param {*} param0
 * @returns
 */
const _renderItem = ({item, drag, isActive}) => {
    return (
        <DragItem
            item={item}
            scaleRatio={scaleRatio}
            drag={drag}
            isEdit={isEdit}
            isActive={isActive}
            type={mediaType}
            onDelete={_onDelete}
        />
    );
};

const _onPlaceholderIndexChange = (placeholderIndex) => {
    if (useIsDraggingRef.current) {
        usePlaceholderIndexRef.current = placeholderIndex;
        console.log('placeholderIndexChangeshishenme', placeholderIndex);
    }
    // console.log('placeholderIndexChangeshishenme', placeholderIndex);
};

const _onRelease = (index) => {
    useIsDraggingRef.current = false;
    console.log('releasereleasereleasereleasereleasereleaserelease', index);
};

/**
 * 底部组件
 * @returns
 */
const _listFooterComponent = () => {
    return (
        <AddView
            source={_source}
            sourceType={_sourceType}
            title={scaleRatio === 1 ? getTitle(mediaType) : ''}
            onClick={_clickAddMedia}
        />
    );
};
return (
    <XView
        style={[Styles.container, {height: normalWH * scaleRatio}]}
        onLayout={controller?.useMeasure(uiConstants.TAG_DRAG_VIEW)}
    >
        {
            <DraggableListContainer
                dataSource={dataSource}
                maxCount={mediaType === MediaType.Video ? 1 : maxCount}
                renderItem={_renderItem}
                keyExtractor={(item, index) => {
                    return `${item?.originalImagePath}_${index}`;
                }}
                listFooterComponent={_listFooterComponent}
                onPlaceholderIndexChange={_onPlaceholderIndexChange}
                // renderPlaceholder={_renderPlaceholder}
                onRelease={_onRelease}
                onDragBegin={_onDragBegin}
                onDragEnd={_onDragEnd}
            />
        }
    </XView>
);

};

export default DragViewV2;

const Styles = StyleSheet.create({
container: {
width: '100%',
flexDirection: 'row',
height: normalWH,
// paddingLeft: 16,
// paddingRight: 16,
},
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions