PrismJS

2018年5月25日 星期五

react-native-autocomplete-input(2)Issue修改筆記

前一回react-native-autocomplete-input(1)使用筆記提到兩個Issue如下:
  Issue1.提示訊息框並沒有ScrollView的功能。
  Issue2.提示訊息框呈現半透明,且如果UI有重疊的話會干擾點擊。
新的測試後再多一個Issue:
  Issue3.Android的提示框會將下面的物件擠下去。

Gitgub上index.js原始碼如下:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
  ListView,
  Platform,
  StyleSheet,
  Text,
  TextInput,
  View,
  ViewPropTypes as RNViewPropTypes
} from 'react-native';

const ViewPropTypes = RNViewPropTypes || View.propTypes;

class Autocomplete extends Component {
  static propTypes = {
    ...TextInput.propTypes,
    /**
     * These styles will be applied to the container which
     * surrounds the autocomplete component.
     */
    containerStyle: ViewPropTypes.style,
    /**
     * Assign an array of data objects which should be
     * rendered in respect to the entered text.
     */
    data: PropTypes.array,
    /**
     * Set to `true` to hide the suggestion list.
     */
    hideResults: PropTypes.bool,
    /*
     * These styles will be applied to the container which surrounds
     * the textInput component.
     */
    inputContainerStyle: ViewPropTypes.style,
    /*
     * Set `keyboardShouldPersistTaps` to true if RN version is <= 0.39.
     */
    keyboardShouldPersistTaps: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.bool
    ]),
    /*
     * These styles will be applied to the container which surrounds
     * the result list.
     */
    listContainerStyle: ViewPropTypes.style,
    /**
     * These style will be applied to the result list.
     */
    listStyle: ListView.propTypes.style,
    /**
     * `onShowResults` will be called when list is going to
     * show/hide results.
     */
    onShowResults: PropTypes.func,
    /**
     * method for intercepting swipe on ListView. Used for ScrollView support on Android
     */
    onStartShouldSetResponderCapture: PropTypes.func,
    /**
     * `renderItem` will be called to render the data objects
     * which will be displayed in the result view below the
     * text input.
     */
    renderItem: PropTypes.func,
    /**
     * `renderSeparator` will be called to render the list separators
     * which will be displayed between the list elements in the result view
     * below the text input.
     */
    renderSeparator: PropTypes.func,
    /**
     * renders custom TextInput. All props passed to this function.
     */
    renderTextInput: PropTypes.func,
    /**
    * `rowHasChanged` will be used for data objects comparison for dataSource
    */
    rowHasChanged: PropTypes.func
  };

  static defaultProps = {
    data: [],
    defaultValue: '',
    keyboardShouldPersistTaps: 'always',
    onStartShouldSetResponderCapture: () => false,
    renderItem: rowData => <Text>{rowData}</Text>,
    renderSeparator: null,
    renderTextInput: props => <TextInput {...props} />,
    rowHasChanged: (r1, r2) => r1 !== r2
  };

  constructor(props) {
    super(props);

    const ds = new ListView.DataSource({ rowHasChanged: props.rowHasChanged });
    this.state = { dataSource: ds.cloneWithRows(props.data) };
    this.resultList = null;
  }

  componentWillReceiveProps({ data }) {
    const dataSource = this.state.dataSource.cloneWithRows(data);
    this.setState({ dataSource });
  }

  /**
   * Proxy `blur()` to autocomplete's text input.
   */
  blur() {
    const { textInput } = this;
    textInput && textInput.blur();
  }

  /**
   * Proxy `focus()` to autocomplete's text input.
   */
  focus() {
    const { textInput } = this;
    textInput && textInput.focus();
  }

  renderResultList() {
    const { dataSource } = this.state;
    const {
      listStyle,
      renderItem,
      renderSeparator,
      keyboardShouldPersistTaps,
      onEndReached,
      onEndReachedThreshold
    } = this.props;

    return (
      <ListView
        ref={(resultList) => { this.resultList = resultList; }}
        dataSource={dataSource}
        keyboardShouldPersistTaps={keyboardShouldPersistTaps}
        renderRow={renderItem}
        renderSeparator={renderSeparator}
        onEndReached={onEndReached}
        onEndReachedThreshold={onEndReachedThreshold}
        style={[styles.list, listStyle]}
      />
    );
  }

  renderTextInput() {
    const { onEndEditing, renderTextInput, style } = this.props;
    const props = {
      style: [styles.input, style],
      ref: ref => (this.textInput = ref),
      onEndEditing: e => onEndEditing && onEndEditing(e),
      ...this.props
    };

    return renderTextInput(props);
  }

  render() {
    const { dataSource } = this.state;
    const {
      containerStyle,
      hideResults,
      inputContainerStyle,
      listContainerStyle,
      onShowResults,
      onStartShouldSetResponderCapture
    } = this.props;
    const showResults = dataSource.getRowCount() > 0;

    // Notify listener if the suggestion will be shown.
    onShowResults && onShowResults(showResults);

    return (
      <View style={[styles.container, containerStyle]}>
        <View style={[styles.inputContainer, inputContainerStyle]}>
          {this.renderTextInput()}
        </View>
        {!hideResults && (
          <View
            style={listContainerStyle}
            onStartShouldSetResponderCapture={onStartShouldSetResponderCapture}
          >
            {showResults && this.renderResultList()}
          </View>
        )}
      </View>
    );
  }
}

const border = {
  borderColor: '#b9b9b9',
  borderRadius: 1,
  borderWidth: 1
};

const androidStyles = {
  container: {
    flex: 1
  },
  inputContainer: {
    ...border,
    marginBottom: 0
  },
  list: {
    ...border,
    backgroundColor: 'white',
    borderTopWidth: 0,
    margin: 10,
    marginTop: 0
  }
};

const iosStyles = {
  container: {
    zIndex: 1
  },
  inputContainer: {
    ...border
  },
  input: {
    backgroundColor: 'white',
    height: 40,
    paddingLeft: 3
  },
  list: {
    ...border,
    backgroundColor: 'white',
    borderTopWidth: 0,
    left: 0,
    position: 'absolute',
    right: 0
  }
};

const styles = StyleSheet.create({
  input: {
    backgroundColor: 'white',
    height: 40,
    paddingLeft: 3
  },
  ...Platform.select({
    android: { ...androidStyles },
    ios: { ...iosStyles }
  })
});

export default Autocomplete;
以下先這個範例做個簡單的筆記:

1. Issue1: 186行可以知道,提示框是由renderResultList這個函式回傳的,136行可以知道是用ListView的方式呈現,因此本身是具有ScrollView的功能,因此猜測應該是因為View的高度關係造成沒有ScrollView的效果,144行可以知道style是由list與listStyle(props)兩個屬性負責,有兩種方式,一種是建構Autocomplete傳入listStyle的屬性設定maxHeight,或是直接修改原始碼的list(style)。

2. Issue2: 關於這個問題我一開始認知應為zIndex的設定即可,但測試後發現Android與iOS設定的不一樣,iOS是設定zIndex,Android是設定elevation,且位置不一樣,目前找一些資料還不知道為什麼zIndex在Android上會怪怪的,提示框直接沒有顯示出來,這邊筆記一下iOS該怎麼設定:
如此圖,我是將Autocomplete在包裝成一個物件來使用,而zIndex應要加上View這個標籤中,這樣就能解決半透明且干擾點擊的情況,但是Android這裡不能設定zIndex,應維持default。
那Android要設定的位置是下圖Issue3的elevation參數,且是設定在list中(或是透過listStyle傳入)。

3. Issue3: 245行可以得知,Android與iOS的style設定方式是不同的,按照Android的設定flex為1(202行)的話,會影響到其他的UI,所以可以將它改成與iOS相同,因此這個屬性就不需要分版本了,修改成以下並寫在245行中即可,其中主要的排版是使用position將他設定為absolute:
綜合以上,我將這個範例簡單改寫成以下:
Issue1: 在39行部份解決
Issue2: 在33行部份解決
export default class AutocompleteExample extends Component {
    static propTypes = {
      films: PropTypes.array,
      placeholder: PropTypes.string,
    };
    static defaultProps = {
      films: [],
      placeholder: '',
    };
    constructor(props) {
      super(props);
      this.state = {
          text: ''
      };
    }    
    findFilm(text) {
      {/*從輸入找出陣列符合*/}
      if (text === '') {
          return [];
      }    
      const { films } = this.props;
      const regex = new RegExp(`${text.trim()}`, 'i');
      return films.filter(film => film.id.search(regex) >= 0);
    }
    
    render() {
      const { text } = this.state;
      const films = this.findFilm(text);
      const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim(); 
      const { placeholder } = this.props;
      
      return (
        <View style={styles.Autocomplete}>
          <Autocomplete
            autoCapitalize="none"
            autoCorrect={false}
            keyboardShouldPersistTaps='always' 
            containerStyle={styles.autocompleteContainer}
            listStyle={{maxHeight:120,}}
            inputContainerStyle={{minWidth:115,maxWidth:200}}
            data={films.length === 1 && comp(text, films[0].id) ? [] : films}
            defaultValue={text}
            onChangeText={text => this.setState({ text: text })}
            placeholder={ placeholder }
            renderItem={
              ({id}) => (
                <TouchableOpacity 
                 style={{padding:10}} 
                 onPress={() => this.setState({ text: id })}
                >
                  {/*autocomplete 內容*/}
                  <Text style={styles.itemText}>
                    {id}
                  </Text>
                </TouchableOpacity>
              )
            }
          />
        </View>
      );
    }
}
const androidStyles = {
  Autocomplete: {
  },
};
const iosStyles = {
  Autocomplete: {
    zIndex:1
  },
};
const styles = StyleSheet.create({
  ...Platform.select({
    android: { ...androidStyles },
    ios: { ...iosStyles }
  }),  
  itemText: {
    fontSize: 24,
    backgroundColor: "#FFFFFF",     
    opacity:100,
  },
  autocompleteContainer: {
    marginLeft: 10,
    marginRight: 10
  },
});

export default AutocompleteExample;

Github上的原始碼也有做一些小調整,如下:
1.Issue2、3: 移除208、229行的list,整合合併在245行的部分。
list: {
    ...border,
    backgroundColor: 'white',
    borderTopWidth: 0,
    position: 'absolute',
    left: 0,
    right: 0,
    elevation: 2,
  },
2.Issue3: 移除201、202行androidStylescontainer的設定。
最後呼叫調整完畢的AutocompleteExample如下:
export default class App extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <View  style={styles.container}>
        <AutocompleteExample 
          placeholder = 'test'
          films= {
            [
              {id:'001'},
              {id:'002'},
              {id:'003'},
              {id:'004'},
              {id:'005'},
              {id:'006'},
              {id:'007'},
              {id:'008'},
              {id:'009'},
              {id:'010'},
              {id:'011'},
              {id:'012'},
              {id:'013'},
              {id:'014'},
            ]
          }
        />
      </View>
    );
  }
}
畫面如下:

沒有留言:

張貼留言