React Nativie 拖拽排序

需求

有一个图片上传的功能,选择完图片之后会按照选择顺序进行排序,系统会默认前5张为系统展示图片,其他的图片留存在系统内供运营商使用. 图片选择完之后可以进行拖拽,调整顺序.

先上效果图

GIF.gif

1. 响应手势事件,绑定相关的方法.

    componentWillMount() {
    this._panResponder = PanResponder.create({
        //用户开始触摸屏幕的时候,是否愿意成为响应者;
        onStartShouldSetPanResponder: () => true,
        //用户开始触摸屏幕的时候,是否愿意成为响应者;
        onMoveShouldSetPanResponder: () => true,
        // 开始按下的时候 调用的方法.
        onPanResponderGrant: (evt, gestureState) => this.onPanResponderGrant(evt),
        // 手指移动的时候调用的方法
        onPanResponderMove: (evt, gestureState) => this.onPanResponderMove(evt, gestureState),
        // 手指放开的调用的方法.
        onPanResponderRelease: (evt, gestureState) => this.onPanResponderEnd(),
        onPanResponderTerminate: (evt, gestureState) => this.onPanResponderEnd()

    });
}

  
 render() {
    const views1 = [];
    const views2 = [];

    this.names.forEach((value, index) => {
        let bg = this.color[index];
        if (index >= 5) {
            views2.push(
                <View
                     // 加了这句 View 才能响应手势 
                    {...this._panResponder.panHandlers}
                    key={value}
                    style={
                        [styles.circle, {
                            left: (index - 5) * CIRCLE_SIZE,
                            backgroundColor: bg,
                            top: CIRCLE_SIZE,
                        }]}
                    ref={ref => this.names[index] = ref}
                />)
        } else {
            views1.push(
                <View
                    // 加了这句 View 才能响应手势
                    {...this._panResponder.panHandlers}
                    key={value}
                    style={
                        [styles.circle,
                            {left: index * CIRCLE_SIZE},
                            {backgroundColor: bg},
                        ]}
                    ref={ref => this.names[index] = ref}
                />)
        }
    });
    return (
        <View
            style={styles.container}>
            {views1}
            {views2}
        </View>
    );
}

2 需要根据按下的位置来判断 要移动哪个view

  // 根据按下的坐标,判断索引
getIdByPosition(pageX, pageY) {
    let index = -1;
    if (pageX > CIRCLE_SIZE * this.names.length) {
        index = this.names.length - 1;
    } else {
        index = Math.floor((pageX) / CIRCLE_SIZE)
    }
    // 如果触摸的高度(需要减去导航栏的高度)大于圆的半径,说明是第二行
    if (pageY - NAV_BAR_HEIGHT > CIRCLE_SIZE) {
        index += 5;
    }
    return index;
}

static getLeftValueYById(id) {
    return id >= 5 ? (id - 5) * CIRCLE_SIZE : id * CIRCLE_SIZE;
}

static getTopValueById(id) {
    return id >= 5 ? CIRCLE_SIZE : 0;
}

3. 开始按下的操作

// 开始按下手势操作。加点偏移量给用户一些视觉反馈,让他们知道发生了什么事情!
onPanResponderGrant(evt) {
    const {pageX, locationX, pageY, locationY} = evt.nativeEvent;
    this.index = this.getIdByPosition(pageX, pageY);
    console.log("this.index" + this.index);
    this.preX = pageX - locationX;
    this.preY = pageY - locationY;
    // 给退拽的item加个阴影
    let item = this.names[this.index];
    item.setNativeProps({
        style: {
            shadowColor: "red",
            shadowOpacity: 0.5,
            shadowRadius: 5,
            shadowOffset: {height: 10, width: 10},
            elevation: 5,
            top: -2,
        }
    });

}

3. 移动的操作, 移动的时候 需要根据手指移动的位置,来判断哪两个小球交换.

  // 开始移动
  onPanResponderMove(evt, gestureState) {
    //  按下的小球
    let index = this.index;
    // 按下的小球需要移动到哪
    let left = this.preX + gestureState.dx;
    let top = this.preY + gestureState.dy;
    // 根据ref 来获取索引对应的view,setNativeProps来设置小球的位置
    let item = this.names[this.index];
    item.setNativeProps({
        style: {
            left: left,
            top: top,
        }
    });
    // 手指移动的某个点对应的索引,如果手指移动到某个点对应的索引 与按下那个点对应的索引不同,就交换两个小球的位置.
    let collideIndex = this.getIdByPosition(evt.nativeEvent.pageX, evt.nativeEvent.pageY);
    if (collideIndex !== this.index && collideIndex !== -1) {
        let collideItem = this.names[collideIndex];
        collideItem.setNativeProps({
            style: {
                left: ListViewPage.getLeftValueYById(this.index),
                top: ListViewPage.getTopValueById(this.index)
            }
        });

        //交换两个值
        [this.names[this.index], this.names[collideIndex]] = [this.names[collideIndex], this.names[this.index]];
        [this.order[this.index], this.order[collideIndex]] = [this.order[collideIndex], this.order[this.index]];
        this.index = collideIndex;
    }
}

4. 手指释放的操作

   onPanResponderEnd() {
    // 复位
    const shadowStyle = {
        shadowColor: "#000",
        shadowOpacity: 0,
        shadowRadius: 0,
        shadowOffset: {height: 0, width: 0,},
        elevation: 0
    };
    // 这个索引是已经交换过的索引.
    let item = this.names[this.index];
    let index = this.index;
    let leftValue = ListViewPage.getLeftValueYById(this.index);
    let topValue = ListViewPage.getTopValueById(this.index);

    item.setNativeProps({
        style: {
            ...shadowStyle,
            left: leftValue,
            top: topValue,
        }
    });
}

5.全部的代码

    import React, {Component} from 'react';
    import {
        StyleSheet,
        View,
        PanResponder,
        Animated,
        LayoutAnimation,
        UIManager,
    } from 'react-native';

    /**
     * 自定义拖拽图片排序
     **/

    const CIRCLE_SIZE = 70;
    const NAV_BAR_HEIGHT = 0;

    export default class DragSort extends Component {

constructor(props) {
    super(props);

    this.names = ['Android', 'iOS', 'js', 'jq', '00', '11', '22', '33', '44', '55'];
    this.order = ['Android', 'iOS', 'js', 'jq', '00', '11', '22', '33', '44', '55'];
    this.color = ['red', 'blue', 'black', 'pink', 'gray', '#03ef94', '#009688', '#607d8b', '#3f51b5', '#00796b'];


}

// 开始按下手势操作。加点偏移量给用户一些视觉反馈,让他们知道发生了什么事情!
onPanResponderGrant(evt) {
    const {pageX, locationX, pageY, locationY} = evt.nativeEvent;
    this.index = this.getIdByPosition(pageX, pageY);
    console.log("this.index" + this.index);
    this.preX = pageX - locationX;
    this.preY = pageY - locationY;
    // 给退拽的item加个阴影
    let item = this.names[this.index];
    item.setNativeProps({
        style: {
            shadowColor: "red",
            shadowOpacity: 0.5,
            shadowRadius: 5,
            shadowOffset: {height: 10, width: 10},
            elevation: 5,
            top: -2,
        }
    });

}

// 最近一次的移动距离为gestureState.move{X,Y}
onPanResponderMove(evt, gestureState) {
    let index = this.index;
    let left = this.preX + gestureState.dx;
    let top = this.preY + gestureState.dy;
    let item = this.names[this.index];

    item.setNativeProps({
        style: {
            left: left,
            top: top,
        }
    });

    let collideIndex = this.getIdByPosition(evt.nativeEvent.pageX, evt.nativeEvent.pageY);


    if (collideIndex !== this.index && collideIndex !== -1) {
        let collideItem = this.names[collideIndex];
        collideItem.setNativeProps({
            style: {
                left: ListViewPage.getLeftValueYById(this.index),
                top: ListViewPage.getTopValueById(this.index)
            }
        });

        //交换两个值
        [this.names[this.index], this.names[collideIndex]] = [this.names[collideIndex], this.names[this.index]];
        [this.order[this.index], this.order[collideIndex]] = [this.order[collideIndex], this.order[this.index]];
        this.index = collideIndex;
    }
}


// 用户放开了所有的触摸点,且此时视图已经成为了响应者。
// 一般来说这意味着一个手势操作已经成功完成。
onPanResponderEnd() {
    // 复位
    const shadowStyle = {
        shadowColor: "#000",
        shadowOpacity: 0,
        shadowRadius: 0,
        shadowOffset: {height: 0, width: 0,},
        elevation: 0
    };
    let item = this.names[this.index];
    let index = this.index;
    let leftValue = ListViewPage.getLeftValueYById(this.index);
    let topValue = ListViewPage.getTopValueById(this.index);

    item.setNativeProps({
        style: {
            ...shadowStyle,
            left: leftValue,
            top: topValue,
        }
    });

    console.log(this.order);

}

// 根据按下的坐标,判断索引
getIdByPosition(pageX, pageY) {
    let index = -1;
    if (pageX > CIRCLE_SIZE * this.names.length) {
        index = this.names.length - 1;
    } else {
        index = Math.floor((pageX) / CIRCLE_SIZE)
    }
    // 如果触摸的高度(需要减去导航栏的高度)大于圆的半径,说明是第二行
    if (pageY - NAV_BAR_HEIGHT > CIRCLE_SIZE) {
        index += 5;
    }
    return index;
}

static getLeftValueYById(id) {
    return id >= 5 ? (id - 5) * CIRCLE_SIZE : id * CIRCLE_SIZE;
}

static getTopValueById(id) {
    return id >= 5 ? CIRCLE_SIZE : 0;
}



componentWillMount() {
    this._panResponder = PanResponder.create({
        //用户开始触摸屏幕的时候,是否愿意成为响应者;
        onStartShouldSetPanResponder: () => true,
        //用户开始触摸屏幕的时候,是否愿意成为响应者;
        onMoveShouldSetPanResponder: () => true,
        // 开始触摸的时候 调用的方法.
        onPanResponderGrant: (evt, gestureState) => this.onPanResponderGrant(evt),
        // 手指移动的时候调用的方法
        onPanResponderMove: (evt, gestureState) => this.onPanResponderMove(evt, gestureState),
        // 手指放开的调用的方法.
        onPanResponderRelease: (evt, gestureState) => this.onPanResponderEnd(),
        onPanResponderTerminate: (evt, gestureState) => this.onPanResponderEnd()

    });
}

render() {
    const views1 = [];
    const views2 = [];

    this.names.forEach((value, index) => {
        let bg = this.color[index];
        if (index >= 5) {
            views2.push(
                <View
                    {...this._panResponder.panHandlers}
                    key={value}
                    style={
                        [styles.circle, {
                            left: (index - 5) * CIRCLE_SIZE,
                            backgroundColor: bg,
                            top: CIRCLE_SIZE,
                        }]}
                    ref={ref => this.names[index] = ref}
                />)
        } else {
            views1.push(
                <View
                    {...this._panResponder.panHandlers}
                    key={value}
                    style={
                        [styles.circle,
                            {left: index * CIRCLE_SIZE},
                            {backgroundColor: bg},
                        ]}
                    ref={ref => this.names[index] = ref}
                />)
        }
    });
    return (
        <View
            style={styles.container}>
            {views1}
            {views2}
        </View>
    );
}

}

const styles = StyleSheet.create({
container: {
flex: 1
},
circle: {
width: CIRCLE_SIZE,
height: CIRCLE_SIZE,
borderRadius: CIRCLE_SIZE / 2,
backgroundColor: 'blue',
position: 'absolute',
}
});

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,896评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,283评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,085评论 0 367
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,010评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,015评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,492评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,858评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,829评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,374评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,409评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,527评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,131评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,858评论 3 339
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,296评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,434评论 1 276
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,087评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,597评论 2 366

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,770评论 1 92
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,136评论 4 61
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,445评论 25 707
  • 昨日公司事太繁杂,人亦很累,所以晚上没有学习,也没写感赏。选择照顾好身体为要,就休息了。昨晚也未同孩子做...
    杨行知阅读 453评论 0 5
  • 翻译:启用此Mac上开发模式?一些调试及操场功能需要您输入密码。启用开发模式授权的Xcode,而无需密码为每个会话...
    我的梦想之路阅读 549评论 0 0