React Native - Need to hide/show header with Animation on scroll regardless of scroll position - react-native

Currently I have this code:
import React, { Component, PureComponent } from 'react';
import { View, FlatList, RefreshControl, StatusBar, Animated, ScrollView, PanResponder } from 'react-native';
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import { connect } from 'react-redux';
import i18n from 'i18n-js';
import Post from '../components/Post';
import AppHeader from '../components/AppHeader';
class Posts extends PureComponent {
constructor(props) {
super(props);
this.state = {
curY: new Animated.Value(0),
height: 0
};
}
render() {
const { postsReducer } = this.props,
{ container } = styles;
return (
<View style={container}>
<Animated.View
style={{
transform: [{
translateY: this.state.curY.interpolate({
inputRange: [0, 1],
outputRange: [0, -1]
})
}], position: 'absolute', top: 0, width: wp('100%'), marginTop: StatusBar.currentHeight
}}
onLayout={({ nativeEvent }) => this.setState({ height: nativeEvent.layout.height })}
>
<AppHeader />
</Animated.View>
<Animated.ScrollView
scrollEventThrottle={16}
refreshControl={
<RefreshControl
onRefresh={this._onRefresh}
refreshing={refreshing}
tintColor='#5E81F4'
colors={["blue", "lightblue"]}
/>
}
contentContainerStyle={{ marginTop: this.state.height }}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.curY } } }],
{ useNativeDriver: true }
)}
>
{postsReducer.map((item, index) => (
<Post
postId={item._id}
userId={item.owner}
owner={item.owner}
title={item.title}
avatar={item.picture}
userName={item.userName}
updatedAt={item.updatedAt}
image={item.photo.split(",")}
description={item.description}
age={item.age}
time={item.time}
date={item.date}
location={item.location}
city={item.city}
commentCounter={item.commentCounter}
key={index}
/>
))}
</Animated.ScrollView>
</View>
);
}
}
const styles = {
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-start',
}
};
const mapStateToProps = ({ registrationReducer, postsReducer, usersReducer, }) => (
{
registrationReducer,
postsReducer,
usersReducer
}
);
export default connect(mapStateToProps, { setPosts })(Posts);
When I start scrolling down header hides, and when I scroll up again appears. But header appears only when I get to the beginning of list.
And I need such solution: when I'm for example in the middle of the list and start scroll up - header should appear, and when started scroll down it hides again. So it should be independently from position.
It work this way in Facebook mobile app for example.

I found solution. Just need to use Animated.diffClamp.
Here is the final code. Maybe will be useful for someone:
import React, { Component, PureComponent } from 'react';
import { View, FlatList, RefreshControl, StatusBar, Animated, ScrollView, PanResponder } from 'react-native';
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import { connect } from 'react-redux';
import i18n from 'i18n-js';
import Post from '../components/Post';
import AppHeader from '../components/AppHeader';
class Posts extends PureComponent {
constructor(props) {
super(props);
this.state = {
curY: new Animated.Value(0),
height: 0
};
}
render() {
const { postsReducer } = this.props,
{ container } = styles;
const headerDistance = Animated.diffClamp(this.state.curY, 0, 60).interpolate({
inputRange: [0, 1],
outputRange: [0, -1]
});
return (
<View style={container}>
<Animated.ScrollView
scrollEventThrottle={16}
refreshControl={
<RefreshControl
onRefresh={this._onRefresh}
refreshing={refreshing}
tintColor='#5E81F4'
colors={["blue", "lightblue"]}
/>
}
contentContainerStyle={{ marginTop: this.state.height }}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.curY } } }],
{ useNativeDriver: true }
)}
>
{postsReducer.map((item, index) => (
<Post
postId={item._id}
userId={item.owner}
owner={item.owner}
title={item.title}
avatar={item.picture}
userName={item.userName}
updatedAt={item.updatedAt}
image={item.photo.split(",")}
description={item.description}
age={item.age}
time={item.time}
date={item.date}
location={item.location}
city={item.city}
commentCounter={item.commentCounter}
key={index}
/>
))}
</Animated.ScrollView>
<Animated.View
style={{
transform: [{
translateY: headerDistance
}], position: 'absolute', top: 0, width: wp('100%'), marginTop: StatusBar.currentHeight
}}
onLayout={({ nativeEvent }) => this.setState({ height: nativeEvent.layout.height })}
>
<AppHeader />
</Animated.View>
</View>
);
}
}
const styles = {
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-start',
}
};
const mapStateToProps = ({ registrationReducer, postsReducer, usersReducer, }) => (
{
registrationReducer,
postsReducer,
usersReducer
}
);
export default connect(mapStateToProps, { setPosts })(Posts);

Related

Reanimated does not work with animated code? Is there a way?

I am pretty new to React Native and reanimated so please bear with me if I cant explain this as I need to.
I have a FlatList that animates a single item using animted when clicked. This works great but the problem is I am trying to use it with code that uses reanimated and I get a lot of errors for some reason? I thought it was backwards compatible?
Because I need the animation to run only on a single item I can not use the reanimated way, it seems a lot harder to keep track of everything?
The code below the working code but if I change it to reanimated, no good.
Is there a way of converting the code to run properly when using reanimated or do I just have to start the animations again using reanimated animations from scratch?
The working code :-
import React, { useState, useEffect } from 'react';
import {
Text,
View,
StyleSheet,
FlatList,
TouchableOpacity,
Animated,
} from 'react-native';
import Constants from 'expo-constants';
const dummyData = [...Array(10)].map((_, i) => ({
title: `title ${i}`,
id: i,
}));
const App = () => {
const [Data, setData] = React.useState(dummyData);
const [activeItem, setActiveItem] = React.useState(null);
const animateValue = React.useRef(new Animated.Value(0)).current;
renderItem = ({ item, index }) => {
const animate = (index) => {
setActiveItem(index);
Animated.sequence([
Animated.spring(animateValue, {
toValue: 1,
}),
Animated.spring(animateValue, {
toValue: 0,
}),
]).start(() => console.log('animation finished'));
};
animationMap = animateValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.5],
});
return (
<TouchableOpacity onPress={(e) => animate(index)}>
{activeItem === index && (
<Animated.View
style={[styles.button, { transform: [{ scale: animationMap }] }]}>
<Text>{item.title}</Text>
</Animated.View>
)}
{activeItem !== index && (
<View style={styles.button}>
<Text>{item.title}</Text>
</View>
)}
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<FlatList
data={Data}
renderItem={renderItem}
keyExtractor={({ id }) => {
return id;
}}
extraData={activeItem}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
button: {
padding: 10,
borderWidth: 1,
alignItems: 'center',
backgroundColor: 'skyblue',
},
});
export default App;
If I change Animated from reanimated, it doesnt work?
import {
StatusBar,
Text,
View,
StyleSheet,
Image,
Dimensions,
TouchableOpacity,
Platform,
TouchableWithoutFeedback,
UIManager,
LayoutAnimation,
} from "react-native";
import { FlatList } from "react-native-gesture-handler";
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
SlideInDown,
BounceOut,
BounceIn,
FadeIn,
FadeOut,
Easing,
withSpring,
withRepeat,
withTiming,
} from "react-native-reanimated";
or
import {
StatusBar,
Text,
View,
StyleSheet,
Image,
Dimensions,
TouchableOpacity,
Platform,
TouchableWithoutFeedback,
UIManager,
LayoutAnimation,
Animated,
} from "react-native";
import { FlatList } from "react-native-gesture-handler";
import {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
SlideInDown,
BounceOut,
BounceIn,
FadeIn,
FadeOut,
Easing,
withSpring,
withRepeat,
withTiming,
} from "react-native-reanimated";
can you try using "useSharedValue" and set the value using
"withSequence" as below mentioned:
const App = () => {
const [Data, setData] = React.useState(dummyData);
const [activeItem, setActiveItem] = React.useState(null);
//const animateValue = React.useRef(new Animated.Value(0)).current;
const animateValue = useSharedValue(0);
renderItem = ({ item, index }) => {
const animate = (index) => {
setActiveItem(index);
animateValue.value = withSequence(withTiming(1), withTiming(0))
animationMap = animateValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.5],
});
return (
<TouchableOpacity onPress={(e) => animate(index)}>
{activeItem === index && (
<Animated.View
style={[styles.button, { transform: [{ scale: animationMap }] }]}>
<Text>{item.title}</Text>
</Animated.View>
)}
{activeItem !== index && (
<View style={styles.button}>
<Text>{item.title}</Text>
</View>
)}
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<FlatList
data={Data}
renderItem={renderItem}
keyExtractor={({ id }) => {
return id;
}}
extraData={activeItem}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
button: {
padding: 10,
borderWidth: 1,
alignItems: 'center',
backgroundColor: 'skyblue',
},
});
export default App;

Problems with automatic image carousel when data is dynamic from an api using react native

I am implementing an image carousel which has an automatic rotation. When I implement it with static data (for example: creating a constant with an array) it works just the way I want it to.
However when I am getting the data from an api using axios, the carrosuel has a wrong behavior. The wrong behavior is as follows:
Swipe to the second image on the carousel and before moving on to the third image, go back to the first image and then go to the third image, then go to the fourth image, go back to the first image, and then go to the first image. fourth, this behavior is repeated x times.
So I think the problem is when I use axios. I attach the code of the classes that intervene in the problem that I am currently presenting.
I am using react native 0.62 with hooks and axios
HomeScreen.js
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import CategoriesScreen from "./Categories/CategoriesScreen";
import { ScrollView } from "react-native-gesture-handler";
import Carousel from "./Banner/BannerOficial";
import { axiosClient } from "../../config/axios";
export default function HomeScreen({ navigation }) {
const [banners, setBanners] = useState([]);
useEffect(() => {
getBannersAPI();
}, []);
function getBannersAPI(){
axiosClient
.get("/service/banner_available")
.then(async function (response) {
setBanners(response.data);
})
.catch(function (error) {
console.log("Error cargando los banners: ", error);
});
}
return (
<View style={{ flex: 1 }}>
<ScrollView>
<Carousel data={banners} />
<CategoriesScreen navigation={navigation} />
</ScrollView>
</View>
);
}
Carousel.js
import React, { useState, useEffect } from 'react'
import { View, Text, StyleSheet, Dimensions, FlatList, Animated } from 'react-native'
import CarouselItem from './BannerItem'
const { width, heigth } = Dimensions.get('window')
let flatList
function infiniteScroll(dataList){
const numberOfData = dataList.length
let scrollValue = 0, scrolled = 0
setInterval(function() {
scrolled ++
if(scrolled < numberOfData)
scrollValue = scrollValue + width
else{
scrollValue = 0
scrolled = 0
}
this.flatList.scrollToOffset({ animated: true, offset: scrollValue})
}, 3000)
}
const Carousel = ({ data }) => {
const scrollX = new Animated.Value(0)
let position = Animated.divide(scrollX, width)
const [dataList, setDataList] = useState(data)
useEffect(()=> {
setDataList(data)
infiniteScroll(dataList)
})
if (data && data.length) {
return (
<View>
<FlatList data={data}
ref = {(flatList) => {this.flatList = flatList}}
keyExtractor={(item, index) => 'key' + index}
horizontal
pagingEnabled
scrollEnabled
snapToAlignment="center"
scrollEventThrottle={16}
decelerationRate={"fast"}
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => {
return <CarouselItem item={item} />
}}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }]
)}
/>
<View style={styles.dotView}>
{data.map((_, i) => {
let opacity = position.interpolate({
inputRange: [i - 1, i, i + 1],
outputRange: [0.3, 1, 0.3],
extrapolate: 'clamp'
})
return (
<Animated.View
key={i}
style={{ opacity, height: 10, width: 10, backgroundColor: '#595959', margin: 8, borderRadius: 5 }}
/>
)
})}
</View>
</View>
)
}
console.log('Please provide Images')
return null
}
const styles = StyleSheet.create({
dotView: { flexDirection: 'row', justifyContent: 'center'}
})
export default Carousel
CarouselItem.js
import React from "react";
import { View, StyleSheet, Text, Image, Dimensions} from 'react-native';
const { width, height} = Dimensions.get('window')
const CarouselItem = ({item}) => {
return(
<View style={styles.cardView}>
<Image style={styles.image} source = {{ uri: item.imagePath}}/>
</View>
)
}
const styles = StyleSheet.create({
cardView:{
flex:1,
width: width -20,
height: height / 7,
backgroundColor: "white",
margin: 10,
borderRadius: 10,
shadowColor: "#000",
shadowOffset: {width: 0.5, height: 0.5},
shadowOpacity: 0.5,
shadowRadius: 3,
elevation: 5,
},
image: {
width: width-20,
height: height / 3,
borderRadius: 10
}
})
export default CarouselItem

Multiple drag and drop simultaneous in React Native

Hello i'm creating a game in react native and i'm stuck because i wan't both players can drag and drop horizontaly an element in same time on the same phone.
I have two components like that:
export class Player1 extends Component{
constructor(props){
super(props);
this.state = {
pan : new Animated.ValueXY()
};
}
componentWillMount(){
this.panResponder = PanResponder.create({
onMoveShouldSetResponderCapture : () => true,
onMoveShouldSetPanResponderCapture : () => true,
onPanResponderGrant : (e, gestureState) => {
this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
this.state.pan.setValue({x: 0, y: 0});
},
onPanResponderMove : Animated.event([null,{
dx : this.state.pan.x,
}]),
onPanResponderRelease: (e, {vx, vy}) => {
}
});
}
render(){
return (
<View style={styles.mainContainer}>
{this.renderDraggable()}
</View>
);
}
renderDraggable(){
return (
<View style={styles.draggableContainer}>
<Animated.View
style={[this.state.pan.getLayout(), styles.triangle]}
{...this.panResponder.panHandlers} >
</Animated.View>
</View>
);
}
}
And in my screen i call my components like that:
export default function HomeScreen() {
return (
<View>
<Player1></Player1>
<Player2></Player2>
</View>
);
}
Thanks for your help
I found a solution, i used react-native-gesture-handle like in the directory doubleDraggable of the example: https://kmagiera.github.io/react-native-gesture-handler/docs/example.html
My Code:
import React, { Component } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import {
PanGestureHandler,
ScrollView,
State,
} from 'react-native-gesture-handler';
export class Players extends Component {
constructor(props) {
super(props);
this._translateX = new Animated.Value(0);
this._translateY = new Animated.Value(0);
this._lastOffset = { x: 0, y: 0 };
this._onGestureEvent = Animated.event(
[
{
nativeEvent: {
translationX: this._translateX,
},
},
],
);
}
_onHandlerStateChange = event => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastOffset.x += event.nativeEvent.translationX;
this._translateX.setOffset(this._lastOffset.x);
this._translateX.setValue(0);
this._translateY.setOffset(this._lastOffset.y);
this._translateY.setValue(0);
}
};
render() {
return (
<PanGestureHandler
{...this.props}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View
style={[
styles.box,
{
transform: [
{ translateX: this._translateX },
{ translateY: this._translateY },
],
},
this.props.boxStyle,
]}
/>
</PanGestureHandler>
);
}
}
export default class Example extends Component {
render() {
return (
<View style={styles.scrollView}>
<DraggableBox />
</View>
);
}
}
const styles = StyleSheet.create({
scrollView: {
flex: 1,
},
box: {
position: 'absolute',
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 25,
borderRightWidth: 25,
borderBottomWidth: 50,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
},
});
And Screen:
<View styles={styles.container}>
<Players boxStyle={styles.player1}></Players>
<Players boxStyle={styles.player2}></Players>
</View>
I have been searching for something similar endlessly for a few days but I couldn't find these demos that react-native-gesture-handler provides. Thanks a lot for posting this here #Lillian Pacaud. Here is the link for several of their demos including the draggable component: https://snack.expo.dev/#adamgrzybowski/react-native-gesture-handler-demo
If you need any simultaneous presses/gesture/drags/etc... your best bet is to use react-native-gesture-handler because the native implementation of all touch/gesture-based components in react native don't allow for simultaneous interactions with each especially for Android.
I made a functional component that does the same thing as the accepted answer. Just pass whatever you want to be draggable as a child under the component. It can handle simultaneous drags as well like the accepted answer on both iOS and Android.
Example of using the draggable component:
import React from 'react';
import { View } from 'react-native';
import { DraggableTest } from '../components/test';
export default function Screen() {
return (
<View style={{ flex: 1 }}>
<DraggableTest>
<View
style={{ width: 150, height: 150, backgroundColor: 'lime' }}
/>
</DraggableTest>
</View>
);
}
The draggable component:
import React, { useRef } from 'react';
import { Animated, StyleSheet } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
export function DraggableTest({ children }) {
const pan = useRef(new Animated.ValueXY()).current;
const lastOffset = useRef({ x: 0, y: 0 }).current;
const onGestureEvent = Animated.event(
[{ nativeEvent: { translationX: pan.x, translationY: pan.y } }],
{ useNativeDriver: false },
);
const onHandlerStateChange = event => {
if (event.nativeEvent.oldState == State.ACTIVE) {
lastOffset.x += event.nativeEvent.translationX;
lastOffset.y += event.nativeEvent.translationY;
pan.setOffset({ x: lastOffset.x, y: lastOffset.y });
pan.setValue({ x: 0, y: 0 });
}
};
return (
<PanGestureHandler
onGestureEvent={onGestureEvent}
onHandlerStateChange={onHandlerStateChange}>
<Animated.View style={[pan.getLayout(), styles.animatedView]}>
{children}
</Animated.View>
</PanGestureHandler>
);
}
const styles = StyleSheet.create({
animatedView: {
position: 'absolute',
},
});

Flatlist data not showing up on screen

Trying to make a simple to-do list. My AddTodo component works fine and I don't believe it is causing the issue but my Flatlist does not show the data. I have no idea why as there are no errors. The issue appears with or without the scroll view.
I've tried messing around with the width and height of the items and the list itself but nothing seems to do the trick.
my mainTodo file:
import React, { Component } from 'react';
import { Text, View, StyleSheet, FlatList, ScrollView } from 'react-native';
import AddTodo from './AddTodo';
import TodoItem from './TodoItem';
class MainTodo extends Component {
constructor() {
super();
this.state = {
textInput: '',
todos: [
{ id: 0, title: 'walk rocky', completed: false },
{ id: 1, title: 'pickup dinner', completed: false }
]
};
}
addNewTodo() {
let todos = this.state.todos;
todos.unshift({
id: todos.length + 1,
todo: this.state.textInput,
completed: false
});
this.setState({
todos,
textInput: ''
});
}
render() {
return (
<View style={{ flex: 1 }}>
<AddTodo
textChange={textInput => this.setState({ textInput })}
addNewTodo={() => this.addNewTodo()}
textInput={this.state.textInput}
/>
<ScrollView>
<FlatList
style={{ flex: 1 }}
data={this.state.todos}
extraData={this.state}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<TodoItem todoItem={item} />
);
}}
/>
</ScrollView>
</View>
);
}
}
export default MainTodo;
my TodoItem file:
import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity style={styles.todoItem}>
<Text style={(todoItem.completed) ? { color: '#aaaaaa' } : { color: '#313131' }}>
{todoItem.title}
</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
todoItem: {
width: 40,
height: 40,
borderBottomColor: '#DDD',
borderBottomWidth: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingLeft: 15
}
});
export default TodoItem;
Under my addtodo component nothing shows up, it's just a blank screen.
In the maintodo file you are rendering the AddTodo component but i didn't see your AddTodo component. So you can update your code accordingly.
In the TodoItem remove the style applied to TouchableOpacity so that your code looks like
import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity style={styles.todoItem}>
<Text style={(todoItem.completed) ? { color: '#aaaaaa' } : { color: '#313131' }}>
{todoItem.title}
</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
});
export default TodoItem;
And in the MainTodo
update your addNewTodo function as
addNewTodo = () => {
const todo = {
id: this.state.todos.length,
title: this.state.textInput,
completed: false
}
this.setState({todos: [...this.state.todos, todo ], textInput: ""})
}
create the TextInput and Button with parent View as flexDirection: "row" and so when TextInput is changed it's value is set in the textInput and when Button is pressed it will create new object and add it to the todos and set the value of TextInput to empty.
and final code can be as
import React, { Component } from 'react';
import { Text, View, StyleSheet, FlatList, ScrollView, TextInput, Button } from 'react-native';
import TodoItem from './TodoItem';
class MainTodo extends Component {
constructor() {
super();
this.state = {
textInput: '',
todos: [
{ id: 0, title: 'walk rocky', completed: false },
{ id: 1, title: 'pickup dinner', completed: false }
]
};
}
addNewTodo = () => {
const todo = {
id: this.state.todos.length,
title: this.state.textInput,
completed: false
}
this.setState({todos: [...this.state.todos, todo ], textInput: ""})
}
render() {
return (
<View style={{ flex: 1, marginTop: 30, paddingHorizontal: 20 }}>
<View style={{flexDirection: "row", alignItems: "center", justifyContent: "space-between"}}>
<TextInput style={{borderWidth: 1, borderColor: "black"}} onChangeText={textInput => this.setState({textInput})} placeholder="Enter todo text" value={this.state.textInput} />
<Button onPress={this.addNewTodo} title="Add todo" />
</View>
<FlatList
contentContainerStyle={{flexGrow: 1}}
data={this.state.todos}
extraData={this.state.todos}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<TodoItem todoItem={item} />
);
}}
/>
</View>
);
}
}
export default MainTodo;
use the code
mainTodo file:
import React, { Component } from 'react';
import { Text, View, StyleSheet, FlatList, ScrollView } from 'react-native';
import AddTodo from './AddTodo';
import TodoItem from './TodoItem';
class MainTodo extends Component {
constructor() {
super();
this.state = {
textInput: '',
todos: [
{ id: 0, title: 'walk rocky', completed: false },
{ id: 1, title: 'pickup dinner', completed: false }
]
};
}
addNewTodo() {
let todos = this.state.todos;
todos.unshift({
id: todos.length + 1,
todo: this.state.textInput,
completed: false
});
this.setState({
todos,
textInput: ''
});
}
render() {
return (
<View style={{ flex: 1 }}>
<AddTodo
textChange={textInput => this.setState({ textInput })}
addNewTodo={() => this.addNewTodo()}
textInput={this.state.textInput}
/>
<ScrollView>
<FlatList
style={{ flex: 1 }}
data={this.state.todos}
extraData={this.state}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<TodoItem todoItem={item} />
);
}}
/>
</ScrollView>
</View>
);
}
}
export default MainTodo;
TodoItem file:
import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity style={styles.todoItem}>
<Text style={(todoItem.completed) ? { color: '#aaaaaa' } : { color: '#313131' }}>
{todoItem.title}
</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
todoItem: {
width: 40,
height: 40,
borderBottomColor: '#DDD',
borderBottomWidth: 1,
backgroundColor:'red',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingLeft: 15
}
});
export default TodoItem;

React Native `alignItems: 'flex-end'` hides content in TabBarIOS component

This question is similar to this one however I have different requirements. I have a <TabBarIOS> component that renders a <Camera> from react-native-camera. I need to place a button to take a picture at the bottom of the <Camera> component but above the <TabBarIOS> component.
index.ios.js
import React, { Component } from 'react';
import {
AppRegistry,
TabBarIOS,
ScrollView,
StyleSheet,
Text,
View
} from 'react-native';
import CameraTab from './views/CameraTab.ios.js';
import FilesTab from './views/FilesTab.ios.js';
import Icon from 'react-native-vector-icons/MaterialIcons';
export default class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'cameraTab'
};
};
_renderContent() {
switch (this.state.selectedTab) {
case "filesTab":
return <FilesTab style={styles.tabContent}></FilesTab>;
case "cameraTab":
return <CameraTab style={styles.tabContent}></CameraTab>;
case "settingsTab":
return <View style={styles.tabContent}></View>;
default:
return <View style={styles.tabContent}></View>;
}
};
render() {
return (
<TabBarIOS
tintColor="#3498db"
barTintColor="#ecf0f1">
<Icon.TabBarItemIOS
title="Files"
iconName="folder"
selected={this.state.selectedTab === "filesTab"}
onPress={() => {
this.setState({
selectedTab: "filesTab",
});
}}>
{this._renderContent()}
</Icon.TabBarItemIOS>
<Icon.TabBarItemIOS
title="Camera"
iconName="photo-camera"
badge={this.state.notifCount > 0 ? this.state.notifCount : undefined}
selected={this.state.selectedTab === "cameraTab"}
onPress={() => {
this.setState({
selectedTab: "cameraTab",
notifCount: this.state.notifCount + 1,
});
}}>
{this._renderContent()}
</Icon.TabBarItemIOS>
<Icon.TabBarItemIOS
title="Settings"
iconName="settings"
selected={this.state.selectedTab === 'settingsTab'}
onPress={() => {
this.setState({
selectedTab: "settingsTab",
presses: this.state.presses + 1
});
}}>
{this._renderContent()}
</Icon.TabBarItemIOS>
</TabBarIOS>
);
}
}
var styles = StyleSheet.create({
tabContent: {},
});
AppRegistry.registerComponent('myapp', () => myApp);
CameraTab.ios.js
import React, { Component } from 'react';
import {
Dimensions,
StyleSheet,
Text,
View
} from 'react-native';
import Camera from 'react-native-camera';
export default class CameraTab extends Component {
constructor(props) {
super(props);
this.state = {};
};
render() {
return (
<Camera
ref={(cam) => {
this.camera = cam;
}}
style={styles.preview}
aspect={Camera.constants.Aspect.fill}>
</Camera>
);
}
takePicture() {
this.camera.capture()
.then((data) => console.log(data))
.catch(err => console.error(err));
}
}
var styles = StyleSheet.create({
preview: {
flex: 1,
flexDirection: 'row',
alignItems: 'flex-end',
height: Dimensions.get('window').height,
width: Dimensions.get('window').width
},
capture: {
backgroundColor: '#fff',
borderRadius: 5,
color: '#000',
height: 20
}
});
module.exports = CameraTab;
I've tried various things but the capture button is always hidden when alignItems: 'flex-end' is in the container component's style.
It should look something like this:
Edit: I've discovered this issue that describes a workaround (placing the button component outside of the camera component). According to RN's docs on Height and Width it seems that this solution will work for all screen dimensions. However this doesn't work for me because I want a Subview with icons inside the camera.
OK, finally fixed it. I think the problem had to do with the height and width in the preview style. Working code:
import React, { Component } from 'react';
import {
Dimensions,
StyleSheet,
Text,
TouchableHighlight,
View
} from 'react-native';
import Camera from 'react-native-camera';
import Icon from 'react-native-vector-icons/Ionicons';
export default class CameraTab extends Component {
constructor(props) {
super(props);
this.state = {};
};
render() {
return (
<View style={styles.container}>
<Camera
ref={(cam) => {
this._camera = cam;
}}
style={styles.preview}
aspect={Camera.constants.Aspect.fill}
captureTarget={Camera.constants.CaptureTarget.disk}>
<TouchableHighlight
style={styles.cameraButton}
onPress={this._takePicture.bind(this)}>
<Icon name="ios-qr-scanner" size={55} color="#95a5a6" />
</TouchableHighlight>
</Camera>
</View>
);
}
_takePicture() {
this._camera.capture()
.then((data) => {
console.log(data)
})
.catch((err) => {
console.error(err)
});
}
}
var styles = StyleSheet.create({
cameraButton: {
flex: 0,
flexDirection: 'row',
marginBottom: 60,
},
container: {
flex: 1,
},
preview: {
flex: 1,
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'space-around'
},
});
module.exports = CameraTab;