React-Native 瀑布流布局

参差不起的列表页对于视觉效果确实有很好的体验,今天就介绍一下React-Native实现瀑布流的效果。

实现效果参考与:https://www.jianshu.com/p/88a56de0191d

效果图:

创建MasonryList.js在需要的地方引用

import*asReactfrom'react';

import{

VirtualizedList,

View,

ScrollView,

StyleSheet,

findNodeHandle,

RefreshControl,

}from'react-native';

typeColumn= {

index:number,

totalHeight:number,

data:Array,

heights:Array,

};

const_stateFromProps= ({numColumns,data,getHeightForItem})=>{

constcolumns:Array =Array.from({

length:numColumns,

}).map((col,i)=>({

index:i,

totalHeight:0,

data:[],

heights:[],

}));

data.forEach((item,index)=>{

constheight=getHeightForItem({item,index});

constcolumn=columns.reduce(

(prev,cur)=>(cur.totalHeight

columns[0],

);

column.data.push(item);

column.heights.push(height);

column.totalHeight+=height;

});

return{columns};

};

exporttypeProps= {

data:Array,

numColumns:number,

renderItem: ({item:any,index:number,column:number})=>?React.Element<

any,

>,

getHeightForItem: ({item:any,index:number})=>number,

ListHeaderComponent?: ?React.ComponentType,

ListEmptyComponent?: ?React.ComponentType,

/**

  * Used to extract a unique key for a given item at the specified index. Key is used for caching

  * and as the react key to track item re-ordering. The default extractor checks `item.key`, then

  * falls back to using the index, like React does.

  */

keyExtractor?: (item:any,index:number)=>string,

// onEndReached will get called once per column, not ideal but should not cause

// issues with isLoading checks.

onEndReached?: ?(info: {distanceFromEnd:number})=>void,

contentContainerStyle?:any,

onScroll?: (event:Object)=>void,

onScrollBeginDrag?: (event:Object)=>void,

onScrollEndDrag?: (event:Object)=>void,

onMomentumScrollEnd?: (event:Object)=>void,

onEndReachedThreshold?: ?number,

scrollEventThrottle:number,

renderScrollComponent: (props:Object)=>React.Element,

/**

  * Set this true while waiting for new data from a refresh.

  */

refreshing?: ?boolean,

/**

  * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make

  * sure to also set the `refreshing` prop correctly.

  */

onRefresh?: ?Function,

};

typeState= {

columns:Array,

};

// This will get cloned and added a bunch of props that are supposed to be on

// ScrollView so we wan't to make sure we don't pass them over (especially

// onLayout since it exists on both).

classFakeScrollViewextendsReact.Component<{style?:any,children?:any}> {

render() {

return(

{this.props.children}

);

}

}

exportdefaultclassMasonryListextendsReact.Component {

staticdefaultProps= {

scrollEventThrottle:50,

numColumns:1,

renderScrollComponent:(props:Props)=>{

if(props.onRefresh&&props.refreshing!=null) {

return(

{...props}

refreshControl={

refreshing={props.refreshing}

onRefresh={props.onRefresh}

/>

}

/>

);

}

return;

},

};

state=_stateFromProps(this.props);

_listRefs:Array = [];

_scrollRef: ?ScrollView;

_endsReached=0;

componentWillReceiveProps(newProps:Props) {

this.setState(_stateFromProps(newProps));

}

getScrollResponder() {

if(this._scrollRef&&this._scrollRef.getScrollResponder) {

returnthis._scrollRef.getScrollResponder();

}

returnnull;

}

getScrollableNode() {

if(this._scrollRef&&this._scrollRef.getScrollableNode) {

returnthis._scrollRef.getScrollableNode();

}

returnfindNodeHandle(this._scrollRef);

}

scrollToOffset({offset,animated}:any) {

if(this._scrollRef) {

this._scrollRef.scrollTo({y:offset,animated});

}

}

_onLayout=event=>{

this._listRefs.forEach(

list=>list&&list._onLayout&&list._onLayout(event),

);

};

_onContentSizeChange= (width,height)=>{

this._listRefs.forEach(

list=>

list&&

list._onContentSizeChange&&

list._onContentSizeChange(width,height),

);

};

_onScroll=event=>{

if(this.props.onScroll) {

this.props.onScroll(event);

}

this._listRefs.forEach(

list=>list&&list._onScroll&&list._onScroll(event),

);

};

_onScrollBeginDrag=event=>{

if(this.props.onScrollBeginDrag) {

this.props.onScrollBeginDrag(event);

}

this._listRefs.forEach(

list=>list&&list._onScrollBeginDrag&&list._onScrollBeginDrag(event),

);

};

_onScrollEndDrag=event=>{

if(this.props.onScrollEndDrag) {

this.props.onScrollEndDrag(event);

}

this._listRefs.forEach(

list=>list&&list._onScrollEndDrag&&list._onScrollEndDrag(event),

);

};

_onMomentumScrollEnd=event=>{

if(this.props.onMomentumScrollEnd) {

this.props.onMomentumScrollEnd(event);

}

this._listRefs.forEach(

list=>

list&&list._onMomentumScrollEnd&&list._onMomentumScrollEnd(event),

);

};

_getItemLayout= (columnIndex,rowIndex)=>{

constcolumn=this.state.columns[columnIndex];

letoffset=0;

for(letii=0;ii

offset+=column.heights[ii];

}

return{length:column.heights[rowIndex],offset,index:rowIndex};

};

_renderScrollComponent= ()=>;

_getItemCount=data=>data.length;

_getItem= (data,index)=>data[index];

_captureScrollRef=ref=>(this._scrollRef=ref);

render() {

const{

renderItem,

ListHeaderComponent,

ListEmptyComponent,

keyExtractor,

onEndReached,

...props

} =this.props;

letheaderElement;

if(ListHeaderComponent) {

headerElement=;

}

letemptyElement;

if(ListEmptyComponent) {

emptyElement=;

}

constcontent= (

{this.state.columns.map(col=>

{...props}

ref={ref=>(this._listRefs[col.index] =ref)}

key={`$col_${col.index}`}

data={col.data}

getItemCount={this._getItemCount}

getItem={this._getItem}

getItemLayout={(data,index)=>

this._getItemLayout(col.index,index)}

renderItem={({item,index})=>

renderItem({item,index,column:col.index})}

renderScrollComponent={this._renderScrollComponent}

keyExtractor={keyExtractor}

onEndReached={onEndReached}

onEndReachedThreshold={this.props.onEndReachedThreshold}

removeClippedSubviews={false}

/>,

)}

);

constscrollComponent=React.cloneElement(

this.props.renderScrollComponent(this.props),

{

ref:this._captureScrollRef,

removeClippedSubviews:false,

onContentSizeChange:this._onContentSizeChange,

onLayout:this._onLayout,

onScroll:this._onScroll,

onScrollBeginDrag:this._onScrollBeginDrag,

onScrollEndDrag:this._onScrollEndDrag,

onMomentumScrollEnd:this._onMomentumScrollEnd,

},

headerElement,

emptyElement&&this.props.data.length===0?emptyElement:content,

);

returnscrollComponent;

}

}

conststyles=StyleSheet.create({

contentContainer:{

flexDirection:'row',

},

column:{

flex:1,

},

});

实现代码

importReact, {Component}from'react';

import{

Platform,

StyleSheet,

Text,

View,

TouchableOpacity,

Dimensions,

Image,

TextInput,

AsyncStorage,

StatusBar,

BackHandler,

ToastAndroid,

ScrollView,

ImageBackground,

SafeAreaView

}from'react-native';

import{commonStyle}from'../../../utils/commonStyle';

import*asnavutilfrom'../../../utils/navutil';

importRefreshListViewfrom'../../component/RefreshListView';

importMasonryListfrom'../../component/MasonryList';

import*ascommonfrom'../../../utils/common';

import{width,height}from'../../../utils/Device';

import*asCommonUtilsfrom'../../../utils/CommonUtils';

importImagePickerfrom'react-native-syan-image-picker'

constitemWidth= (width-16) /2;//瀑布

import{observer,inject}from'mobx-react'

@inject("wbStore")

@observer

exportdefaultclassAppextendsComponent{

constructor(props){

super(props);

this.state={

refreshing:false,

data:[],

}

}

componentDidMount(){

this.onRefreshing()

}

onRefreshing= ()=>{//下拉刷新,未优化

this.setState({

refreshing:true,

})

letformData=newFormData();

formData.append('page',1);

formData.append('count',4);

formData.append('channel_category_id',this.props.items.channel_category_id) ;

formData.append('type',0);

formData.append('is_app',1);

formData.append('oauth_token',common.rundata.session.oauth_token);

formData.append('oauth_token_secret',common.rundata.session.oauth_token_secret);

fetch(common.urls.domain+"?"+common.urls.channelList, {

method:'POST',

headers:{

'Charset':'utf-8',

'Content-Type':'multipart/form-data',

},

body:formData,

}).then((response)=>{

if(response.ok) {

returnresponse.json();

}

}).then((json)=>{

console.log(json.data.feed_list)

console.log(8888)

this.setState({

refreshing:false,

data:json.data.feed_list,

})

}).catch((error)=>{

console.error(error);

});

}

_onEndReached= ()=>{//上拉加载,未优化

letformData=newFormData();

formData.append('page',this.state.data.length/10+1);

formData.append('count',4);

formData.append('channel_category_id',this.props.items.channel_category_id) ;

formData.append('type',0);

formData.append('is_app',1);

formData.append('oauth_token',common.rundata.session.oauth_token);

formData.append('oauth_token_secret',common.rundata.session.oauth_token_secret);

fetch(common.urls.domain+"?"+common.urls.channelList, {

method:'POST',

headers:{

'Charset':'utf-8',

'Content-Type':'multipart/form-data',

},

body:formData,

}).then((response)=>{

if(response.ok) {

returnresponse.json();

}

}).then((json)=>{

console.log(json.data.feed_list)

console.log(9999)

this.setState({

refreshing:false,

data:[...this.state.data,...json.data.feed_list]

})

}).catch((error)=>{

console.error(error);

});

}

_getHeightForItem= ({item})=>{//获取图片高

returnMath.max(itemWidth,itemWidth/item.image[0].attach_origin_width*item.image[0].attach_origin_height);

}

_renderItem= ({item})=>{

constitemHeight=this._getHeightForItem({item});

return(

activeOpacity={0.7}

onPress={()=>this._onPressContent(item)}

style={{marginRight:15,marginTop:15}}>

{item.content}

{item.Fabulous}

{

navutil.push_navigator("MyPage",{uid:item.user_id})

}}style={{width:35}}>

{item.user_name}

{

item.sex=='男'?

:

}

{item.grade}

)

}

_onPressContent= (item)=>{

navutil.push_navigator('TjDetail',{id:item.feed_id})

}

render(){

return(

data={this.state.data}

numColumns={2}//横向数目

renderItem={this._renderItem}

getHeightForItem={this._getHeightForItem}

refreshing={this.state.refreshing}

onRefresh={this.onRefreshing}

onEndReachedThreshold={0.5}

onEndReached={this._onEndReached}

keyExtractor={(myItem,index)=>("index"+index)}

/>

)

}

}

conststyles=StyleSheet.create({

item:{

}

});

你可能感兴趣的:(React-Native 瀑布流布局)