突出显示 React-Native FlatList 中的选定项目

Posted

技术标签:

【中文标题】突出显示 React-Native FlatList 中的选定项目【英文标题】:Highlight a selected item in React-Native FlatList 【发布时间】:2018-03-23 00:27:49 【问题描述】:

我组装了一个简单的 React-native 应用程序来从远程服务获取数据,并将其加载到 FlatList 中。当用户点击一个项目时,它应该被突出显示并且应该保留选择。我相信这样一个微不足道的操作应该不难。我不确定我错过了什么。

import React,  Component  from 'react';
import 
  StyleSheet,
  Text,
  View,
  FlatList,
  ActivityIndicator,
  Image,
  TouchableOpacity,
 from 'react-native';

export default class BasicFlatList extends Component 
  constructor(props) 
    super(props);

    this.state = 
      loading: false,
      data: [],
      page: 1,
      seed: 1,
      error: null,
      refreshing: false,
      selectedItem:'null',
    ;
  

  componentDidMount() 
    this.makeRemoteRequest();
  

  makeRemoteRequest = () => 
    const page, seed = this.state;
    const url = `https://randomuser.me/api/?seed=$seed&page=$page&results=20`;
    this.setState(loading: true);
    fetch(url)
      .then(res => res.json())
      .then(res => 
        this.setState(
          data: page === 1 ? res.results : [...this.state.data, ...res.results],
          error: res.error || null,
          loading: false,
          refreshing: false
        );
      )
      .catch(error => 
        this.setState(error, loading: false);
      );
  ;

  onPressAction = (rowItem) => 
    console.log('ListItem was selected');
    console.dir(rowItem);
    this.setState(
      selectedItem: rowItem.id.value
    );
  

  renderRow = (item) => 
    const isSelectedUser = this.state.selectedItem === item.id.value;
    console.log(`Rendered item - $item.id.value for $isSelectedUser`);
    const viewStyle = isSelectedUser ? styles.selectedButton : styles.normalButton;
    return(
        <TouchableOpacity style=viewStyle onPress=() => this.onPressAction(item) underlayColor='#dddddd'>
          <View style=styles.listItemContainer>
            <View>
              <Image source= uri: item.picture.large style=styles.photo />
            </View>
            <View style=flexDirection: 'column'>
              <View style=flexDirection: 'row', alignItems: 'flex-start',>
                isSelectedUser ?
                  <Text style=styles.selectedText>item.name.first item.name.last</Text>
                  : <Text style=styles.text>item.name.first item.name.last</Text>
                
              </View>
              <View style=flexDirection: 'row', alignItems: 'flex-start',>
                <Text style=styles.text>item.email</Text>
              </View>
            </View>
          </View>
        </TouchableOpacity>
    );
  

  render() 
    return(
      <FlatList style=styles.container
        data=this.state.data
        renderItem=( item ) => (
          this.renderRow(item)
        )
      />
    );
  


const styles = StyleSheet.create(
  container: 
    flex: 1,
    marginTop: 50,
  ,
  selectedButton: 
    backgroundColor: 'lightgray',
  ,
  normalButton: 
    backgroundColor: 'white',
  ,
  listItemContainer: 
    flex: 1,
    padding: 12,
    flexDirection: 'row',
    alignItems: 'flex-start',
  ,
  text: 
    marginLeft: 12,
    fontSize: 16,
  ,
  selectedText: 
    marginLeft: 12,
    fontSize: 20,
  ,
  photo: 
    height: 40,
    width: 40,
    borderRadius: 20,
  ,
);

当用户点击列表中的一个项目时,“onPress”方法会被调用,其中包含有关所选项目的信息。但是Flatlist中的highlight item的下一步并没有发生。 'UnderlayColor' 也无济于事。

任何帮助/建议将不胜感激。

【问题讨论】:

您还需要修改您的数据(状态),在您的onPressAction func 中将setState 替换为this.setState(selectedItem: rowItem.id.value, data: ...this.state.data);,但这不是推荐的方式。 我试过了。没有帮助 @KrishnanSriram 我也有同样的问题。您在下面选择的答案有效吗? 【参考方案1】:

你可以这样做:

    对于 renderItem,使用 TouchableOpacity 之类的东西,通过 onPress 事件传递 renderItem 的索引或 id;

    将所选项目添加到状态的功能:

    handleSelection = (id) => 
       var selectedId = this.state.selectedId
    
       if(selectedId === id)
         this.setState(selectedItem: null)
       else 
         this.setState(selectedItem: id)
    
    
    handleSelectionMultiple = (id) => 
       var selectedIds = [...this.state.selectedIds] // clone state
    
       if(selectedIds.includes(id))
         selectedIds = selectedIds.filter(_id => _id !== id)
       else 
         selectedIds.push(id)
    
       this.setState(selectedIds)
    
    

    平面列表:

    <FlatList
      data=data
      extraData=
        this.state.selectedId     // for single item
        this.state.selectedIds    // for multiple items
      
      renderItem=(item) => 
         <TouchableOpacity 
    
           // for single item
           onPress=() => this.handleSelection(item.id)
           style=item.id === this.state.selectedId ? styles.selected : null 
    
           // for multiple items
           onPress=() => this.handleSelectionMultiple(item.id)
           style=this.state.selectedIds.includes(item.id) ? styles.selected : null 
         >
            <Text>item.name</Text>
         </TouchableOpacity>
      
    

    />

    为所选项目创建样式,就是这样!

【讨论】:

如何处理多个项目? @Ranjit 有点晚了,但我编辑了我对这两种情况的回答 如何将用户选择限制为两个项目? @gSaenz 您可以在 handleSelectionMultiple 函数上执行类似操作: if (selectedIds.length > 2) return 这会将我的平面列表中的所有项目都转换为“已选择”样式。知道修复是什么吗?【参考方案2】:

代替this.state.selectedItem 并设置/检查rowItem.id.value,我建议使用带有键:值对的Map 对象,如RN FlatList 文档示例中所示:https://facebook.github.io/react-native/docs/flatlist.html。也可以查看 js Map 文档:https://developer.mozilla.org/en-US/docs/Web/javascript/Reference/Global_Objects/Map。

@j.I-V 推荐的 extraData 属性将确保当 this.state.selected 在选择时发生更改时重新渲染。

您的 onPressAction 显然会与下面的示例有所不同,具体取决于您是要在任何给定时间限制选择的数量还是不允许用户切换选择等。

另外,虽然没有任何必要,但我喜欢为renderItem 组件使用另一个类或纯组件;最终看起来像下面这样:

export default class BasicFlatList extends Component 
  state = 
    otherStateStuff: ...,
    selected: (new Map(): Map<string, boolean>) //iterable object with string:boolean key:value pairs
  

  onPressAction = (key: string) => 
    this.setState((state) => 
      //create new Map object, maintaining state immutability
      const selected = new Map(state.selected);
      //remove key if selected, add key if not selected
      this.state.selected.has(key) ? selected.delete(key) : selected.set(key, !selected.get(key));
      return selected;
    );
  

  renderRow = (item) => 
    return (
        <RowItem
          ...otherProps
          item=item
          onPressItem=this.onPressAction
          selected=!!this.state.selected.get(item.key) />
    );
  

  render() 
    return(
      <FlatList style=styles.container
        data=this.state.data
        renderItem=( item ) => (
          this.renderRow(item)
        )
        extraData=this.state
      />
    );
  



class RowItem extends Component 
  render()
    //render styles and components conditionally using this.props.selected ? _ : _
    
    return (
      <TouchableOpacity onPress=this.props.onPressItem>
        ...
      </TouchableOpacity>
    )
  

【讨论】:

为什么 delete 有 2 个参数? @KailashUniyal 很好的发现!该删除方法应该只接受一个参数(一个键),所以我已经相应地编辑了我的答案。【参考方案3】:

您应该将 extraData 属性传递给您的 FlatList,以便它会根据您的选择重新呈现您的项目

这里:

<FlatList style=styles.container
    data=this.state.data
    extraData=this.state.selectedItem
    renderItem=( item ) => (
      this.renderRow(item)
    )
 />

来源:https://facebook.github.io/react-native/docs/flatlist

确保您的 renderItem 函数所依赖的所有内容都作为更新后不 === 的 prop(例如 extraData)传递,否则您的 UI 可能不会在更改时更新

【讨论】:

【参考方案4】:

第一

  constructor() 
    super();
    this.state = 
      selectedIds:[]
    ;
  

第二

handleSelectionMultiple = async (id) => 
var selectedIds = [...this.state.selectedIds] // clone state
if(selectedIds.includes(id))
  selectedIds = selectedIds.filter(_id => _id !== id)
else 
  selectedIds.push(id)
await this.setState(selectedIds)

第三

<CheckBox
     checked=this.state.selectedIds.includes(item.expense_detail_id) ? true : false
     onPress=()=>this.handleSelectionMultiple(item.expense_detail_id)
 />

最后我从 Maicon Gilton 给出的答案中得到了解决我的问题

【讨论】:

以上是关于突出显示 React-Native FlatList 中的选定项目的主要内容,如果未能解决你的问题,请参考以下文章

在 react-native 上更改 textInput 突出显示(自动完成)的背景颜色

react-native FlatList 滚动到底部的聊天应用程序

React-Native:无法访问状态值或从 renderRow 声明的任何方法

React-Native ListView拖拽交换Item

React-Native ListView拖拽交换Item

React-Native ListView拖拽交换Item