React Nativie 拖拽排序

需求

有一個圖片上傳的功能,選擇完圖片之后會按照選擇順序進行排序,系統(tǒng)會默認前5張為系統(tǒng)展示圖片,其他的圖片留存在系統(tǒng)內(nèi)供運營商使用. 圖片選擇完之后可以進行拖拽,調(diào)整順序.

先上效果圖

GIF.gif

1. 響應(yīng)手勢事件,綁定相關(guān)的方法.

    componentWillMount() {
    this._panResponder = PanResponder.create({
        //用戶開始觸摸屏幕的時候官还,是否愿意成為響應(yīng)者;
        onStartShouldSetPanResponder: () => true,
        //用戶開始觸摸屏幕的時候毒坛,是否愿意成為響應(yīng)者望伦;
        onMoveShouldSetPanResponder: () => true,
        // 開始按下的時候 調(diào)用的方法.
        onPanResponderGrant: (evt, gestureState) => this.onPanResponderGrant(evt),
        // 手指移動的時候調(diào)用的方法
        onPanResponderMove: (evt, gestureState) => this.onPanResponderMove(evt, gestureState),
        // 手指放開的調(diào)用的方法.
        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 才能響應(yīng)手勢 
                    {...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 才能響應(yīng)手勢
                    {...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 需要根據(jù)按下的位置來判斷 要移動哪個view

  // 根據(jù)按下的坐標(biāo),判斷索引
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)
    }
    // 如果觸摸的高度(需要減去導(dǎo)航欄的高度)大于圓的半徑,說明是第二行
    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. 開始按下的操作

// 開始按下手勢操作。加點偏移量給用戶一些視覺反饋煎殷,讓他們知道發(fā)生了什么事情屯伞!
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. 移動的操作, 移動的時候 需要根據(jù)手指移動的位置,來判斷哪兩個小球交換.

  // 開始移動
  onPanResponderMove(evt, gestureState) {
    //  按下的小球
    let index = this.index;
    // 按下的小球需要移動到哪
    let left = this.preX + gestureState.dx;
    let top = this.preY + gestureState.dy;
    // 根據(jù)ref 來獲取索引對應(yīng)的view,setNativeProps來設(shè)置小球的位置
    let item = this.names[this.index];
    item.setNativeProps({
        style: {
            left: left,
            top: top,
        }
    });
    // 手指移動的某個點對應(yīng)的索引,如果手指移動到某個點對應(yīng)的索引 與按下那個點對應(yīng)的索引不同,就交換兩個小球的位置.
    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() {
    // 復(fù)位
    const shadowStyle = {
        shadowColor: "#000",
        shadowOpacity: 0,
        shadowRadius: 0,
        shadowOffset: {height: 0, width: 0,},
        elevation: 0
    };
    // 這個索引是已經(jīng)交換過的索引.
    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'];


}

// 開始按下手勢操作。加點偏移量給用戶一些視覺反饋豪直,讓他們知道發(fā)生了什么事情劣摇!
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;
    }
}


// 用戶放開了所有的觸摸點,且此時視圖已經(jīng)成為了響應(yīng)者弓乙。
// 一般來說這意味著一個手勢操作已經(jīng)成功完成末融。
onPanResponderEnd() {
    // 復(fù)位
    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);

}

// 根據(jù)按下的坐標(biāo),判斷索引
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)
    }
    // 如果觸摸的高度(需要減去導(dǎo)航欄的高度)大于圓的半徑,說明是第二行
    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({
        //用戶開始觸摸屏幕的時候,是否愿意成為響應(yīng)者暇韧;
        onStartShouldSetPanResponder: () => true,
        //用戶開始觸摸屏幕的時候勾习,是否愿意成為響應(yīng)者;
        onMoveShouldSetPanResponder: () => true,
        // 開始觸摸的時候 調(diào)用的方法.
        onPanResponderGrant: (evt, gestureState) => this.onPanResponderGrant(evt),
        // 手指移動的時候調(diào)用的方法
        onPanResponderMove: (evt, gestureState) => this.onPanResponderMove(evt, gestureState),
        // 手指放開的調(diào)用的方法.
        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',
}
});

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末懈玻,一起剝皮案震驚了整個濱河市巧婶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涂乌,老刑警劉巖艺栈,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異湾盒,居然都是意外死亡湿右,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門罚勾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毅人,“玉大人漾唉,你說我怎么就攤上這事⊙咚” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵分衫,是天一觀的道長场刑。 經(jīng)常有香客問我,道長蚪战,這世上最難降的妖魔是什么牵现? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮邀桑,結(jié)果婚禮上瞎疼,老公的妹妹穿的比我還像新娘。我一直安慰自己壁畸,他們只是感情好贼急,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捏萍,像睡著了一般太抓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上令杈,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天走敌,我揣著相機與錄音,去河邊找鬼逗噩。 笑死掉丽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的异雁。 我是一名探鬼主播捶障,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼片迅!你這毒婦竟也來了残邀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柑蛇,失蹤者是張志新(化名)和其女友劉穎芥挣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耻台,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡空免,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盆耽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹋砚。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡扼菠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坝咐,到底是詐尸還是另有隱情循榆,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布墨坚,位于F島的核電站秧饮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泽篮。R本人自食惡果不足惜盗尸,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帽撑。 院中可真熱鬧泼各,春花似錦、人聲如沸亏拉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽专筷。三九已至弱贼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間磷蛹,已是汗流浹背吮旅。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留味咳,地道東北人庇勃。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像槽驶,于是被迫代替她去往敵國和親责嚷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案掂铐? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J的外補...
    _Yfling閱讀 13,754評論 1 92
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫罕拂、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,109評論 4 62
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,190評論 25 707
  • 昨日公司事太繁雜全陨,人亦很累爆班,所以晚上沒有學(xué)習(xí),也沒寫感賞辱姨。選擇照顧好身體為要柿菩,就休息了。昨晚也未同孩子做...
    楊行知閱讀 450評論 0 5
  • 翻譯:啟用此Mac上開發(fā)模式雨涛?一些調(diào)試及操場功能需要您輸入密碼枢舶。啟用開發(fā)模式授權(quán)的Xcode懦胞,而無需密碼為每個會話...
    我的夢想之路閱讀 547評論 0 0