Animating Views from top-bottom to side-by-side in React Native - react-native

I am trying to create a custom header for my app and I want to animate it in a certain way. I will describe the way it is supposed to be animated.
If you have a look at the image you will see one red View that contains one green View and a blue View. I would like the Views be arranged side-by-side from their current positions while animating.
I have tried to create the code for a collapsing header and the red View that contains everything, is shrinking based on a ScrollView but I cant get the green and blue Views to come side-by-side.
Home.js
const HEADER_EXPANDED_VIEW = 200
const HEADER_COLLAPSED_VIEW = 80
export default class HomeActivity extends Component {
constructor(props) {
super(props)
this.state = {
scrollY: new Animated.Value(0)
}
}
static navigationOptions = {
title: "HomeActivity",
header: null
}
render() {
const headerHeight = this.state.scrollY.interpolate({
inputRange: [0, HEADER_EXPANDED_VIEW - HEADER_COLLAPSED_VIEW],
outputRange: [HEADER_EXPANDED_VIEW, HEADER_COLLAPSED_VIEW],
extrapolate: "clamp"
})
// console.log(headerHeight)
return (
<View style={styles.container}>
<ScrollView
contentContainerStyle={{
padding: 16,
paddingTop: HEADER_EXPANDED_VIEW,
color: "#FFFFFF"
}}
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y: this.state.scrollY
}
}
}
])}
>
<Text style={styles.title}>This is Title</Text>
<Text style={styles.content}>
.....
</Text>
</ScrollView>
<CollapsingHeader headerHeight={headerHeight} />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
scrollContainer: {
padding: 16
},
title: {
fontSize: 24,
marginVertical: 16
}
})
CollapsingHeader.js
export default class CollapsingHeader extends Component {
render() {
return (
<Animated.View
style={{
height: this.props.headerHeight,
width: Dimensions.get("screen").width,
position: "absolute",
top: 0,
left: 0,
borderWidth: 2,
borderColor: "#FF0000",
backgroundColor: "#212121"
}}
>
<View
style={{
borderWidth: 2,
borderColor: "#00FF00"
}}
>
<MenuButton />
</View>
<View
style={{
flexDirection: "row",
borderWidth: 2,
borderColor: "#0000FF"
}}
>
<View
style={{
flexGrow: 1
}}
>
<Text
style={{
fontFamily: "NunitoSans-Bold",
fontSize: 40,
color: "#FFFFFF"
}}
>
Home
</Text>
</View>
<SortButton />
<SearchButton />
</View>
</Animated.View>
)
}
}
I am relatively new to React Native, please assume I know very little about it.

If you know the headerHeight at which it's going to collapse, then you could add a dynamic flexDirection to the Animated.View style.
style={{
/* ...your Animated.View styles */
flexDirection: this.props.headerHeight < /* collapse height */ ? 'row' : 'column'
}}

Related

React native animation progress bar with image

Today i want to make a progress bar with a image on the left. Unfortunately I can't position the image in the right place. For now it looks like that
I want to looks something like that -
My code so far -
<View
style={{
flexDirection: "row",
alignSelf: "center",
marginBottom: "20%",
marginTop: "10%",
}}
>
<View
style={{
width: "90%",
height: 30,
padding: 2.5,
backgroundColor: "#00000020",
borderRadius: 30,
}}
>
{/* <Animated.View
style={[
{
width: "100%",
height: 25,
borderRadius: 15,
backgroundColor: "#f5de41",
},
{
width: progressAnim,
},
]}
></Animated.View> */}
<Image
source={require("./assets/lion.png")}
style={{
height: 44,
height: 44,
position: "absolute",
}}
resizeMode="contain"
/>
</View>
</View>
I tried to add left: '-62%' in style of iamge but it not works. I am not sure how to move the lion to the left?
One approach would be to remove the absolute position and use flexbox to align the the image to end of the row:
const ProgressBar = ({imgSource,imgStyle,imgSize,style,progress,color})=>{
let loadingAnim = useRef(new Animated.Value(progress)).current;
const [{width,height},setViewDimensions] = useState({});
// get parent view size
const onLayout=({nativeEvent})=>{
setViewDimensions({
width:nativeEvent.layout.width,
height:nativeEvent.layout.height
})
}
const animatedWidth =loadingAnim.interpolate({
inputRange:[0,1],
outputRange:[0,width-imgSize],
extrapolate:'clamp'
})
const containerAnimation = {
margin:0,
padding:0,
width:Animated.add(animatedWidth,imgSize),
backgroundColor:color,
height:'100%',
justifyContent:'center',
overflow:'hidden'
}
useEffect(()=>{
Animated.timing(loadingAnim,{
toValue:progress,
duration:100
}).start()
},[progress])
return(
<View style={[styles.loadingContainer,{height:imgSize*1.5||null},style]} onLayout={onLayout}>
<Animated.View style={[styles.loadingContainer,containerAnimation]}>
<Animated.Image
source={imgSource}
style={[{height:imgSize,width:imgSize,alignSelf:'flex-end'},imgStyle,{}]}
/>
</Animated.View>
</View>
)
}
I found this approach to be slightly un-smooth. I think this is because the width of image's parent view was being animated, and not the actual position of the image.
Another approach would be to animate the image:
const ProgressBar = ({imgSource,imgStyle,imgSize,style,progress,color})=>{
let widthAnim = useRef(new Animated.Value(progress)).current;
const [{width,height},setViewDimensions] = useState({});
// get parent view width to determine progress view size
const onLayout=({nativeEvent})=>{
setViewDimensions({
width:nativeEvent.layout.width,
height:nativeEvent.layout.height
})
}
const animatedWidth = widthAnim.interpolate({
inputRange:[0,1],
outputRange:[0,width-imgSize],
extrapolate:'clamp'
})
const containerAnimation = {
// min width will be imgSize
width:Animated.add(animatedWidth,imgSize),
backgroundColor:color,
}
const imgAnimation = {
left:animatedWidth
}
// animate progress changess
useEffect(()=>{
Animated.timing(widthAnim,{
toValue:progress,
duration:100
}).start()
},[progress])
return(
<View>
<View style={[styles.loadingContainer,{height:imgSize*1.25||null},style]} onLayout={onLayout}>
<Animated.View style={[styles.progressBar,containerAnimation]}/>
</View>
<Animated.Image
source={imgSource}
style={[styles.image,{height:imgSize,width:imgSize},imgStyle,imgAnimation]}
resizeMode='contain'
/>
</View>
)
}

React Navigation (native): Overflow visible not working on custom header

I'm implementing a custom header for #react-navigation/native-stack. Using React Navigation v6.
One of the elements within the custom header has a native shadow on iOS added (via the style prop). The shadow is a tad bigger than the header and unfortunately, I can't get it to display beyond the boundaries of the header. Of course, I've tried using overflow: visible on basically every component in the tree, but no success. The shadow is clipped off:
Here's my custom header:
function CustomHeader(props: NativeStackHeaderProps) {
const { options, route, navigation } = props;
const insets = useSafeAreaInsets();
const headerHeight = Helpers.getHeaderHeight(insets);
return (
<View style={{
height: headerHeight,
paddingTop: insets.top,
width: '100%',
backgroundColor: Colors.white,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingLeft: 20,
paddingRight: 20,
overflow: 'visible',
}}
>
<View style={{
flex: 1, display: 'flex', alignItems: 'flex-start',
}}
>
{ options.headerLeft ? options.headerLeft({ canGoBack: false }) : (
<TouchableOpacity
onPress={() => route.name === 'Home'
? null
: navigation.reset({ index: 0, routes: [{ name: 'Home' }] })}
>
<Image
style={{ width: Sizing.logo, height: Sizing.logo }}
source={Logo}
/>
</TouchableOpacity>
)}
</View>
<Text style={{
textAlign: 'center', color: Colors.purple,
}}
>
{(options.title || route.name).toUpperCase()}
</Text>
<View style={{
flex: 1, display: 'flex', alignItems: 'flex-end', overflow: 'visible',
}}
>
{ options.headerRight ? options.headerRight({ canGoBack: false }) : null}
</View>
</View>
);
}
The button on the right with the shadow is passed in through the headerRight option and contains this shadow style:
nativeShadow: {
shadowColor: Colors.gray,
shadowOffset: { width: 0, height: 8 },
shadowRadius: Colors.shadows.gray.distance,
shadowOpacity: 0.5,
},
Any idea what I could try next? I don't want to increase the headers' height since this would break the layout elsewhere.
I had a similar problem with the menu. I think i was taking another way and maybe you should try it.
Be sure that all the parent elements has at least the same height than the navmenu's height.
import React, { Component } from 'react';
import { TouchableOpacity, StyleSheet, Image, View, Text } from 'react-native';
const styles = StyleSheet.create({
menuParent: {
marginLeft: 30,
justifyContent: "flex-start",
alignItems: "flex-end",
flex: 1,
height: 250
},
btnContainer: {
width: 40,
height: 40,
elevation: 50,
zIndex: 50,
marginTop: 5
},
image: {
width: 40,
height: 40,
},
menuContainer: {
position: "absolute",
top: 5,
left: -5,
right: -5,
padding: 20,
borderRadius: 10,
elevation: 10,
zIndex: 10,
flex: 1,
height: 230,
backgroundColor: "#fff",
}
});
export default class DefaultHeaderMenu extends Component {
state = {
menuVisible: false
};
constructor(props) {
super(props);
}
render() {
return (
<View style={ styles.menuParent }>
{this.state.menuVisible ? <View style={ styles.menuContainer }></View> : null }
<TouchableOpacity
style={ styles.btnContainer }
onPress={ () => this.setState({ menuVisible: !this.state.menuVisible }) }
>
<Image
source={ require('#/assets/img/menu.png') }
style={ styles.image }
/>
</TouchableOpacity>
</View>
);
}
}
This is the component i used and the passed it to nav as headerRight element.
In my case, the problem was that height: 250 was missing in menuParent.
I hope it helps you and good luck
I had a problem like this once and fixed it by using 'zIndex'. Try setting it as the* max index in your project. Hope it helps

react native styling component

I am trying to style a component but cant figure out how to do a couple of things.
1) I want to apply a background color to 100% of my component
return (
<TouchableWithoutFeedback
style={styles.touchable}
onPress={() =>
this.props.navigation.navigate('Quotes', { name: this.props.name })}
>
<View style={styles.viewStyle}>
<Image
source={this.props.image}
key={this.props.image}
style={styles.imageStyle}
/>
<Text style={styles.textStyle}> {this.props.name} </Text>
</View>
</TouchableWithoutFeedback>
);
}
}
const styles = {
imageStyle: {
height: 100,
width: 100
},
touchable: {
width: '100%',
backgroundColor: 'black'
},
viewStyle: {
height:100,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
backgroundColor: 'black'
},
textStyle: {
marginLeft: 20,
width: 120,
fontWeight: 'bold',
fontSize: 15
}
}
I tried to apply the background color to touchable tag a view tag but none of this work.
Moreover Id like to position my image at the top left of the component using justifyContent: flex-start. But my elements inside the view tag keeps staying centered.
How can I achieve these goals ?
The parent component is :
render() {
const {listStyle} = styles
return (
<ScrollView style={listStyle}>
{this.renderCharacters(this.props.matches)}
</ScrollView>
);
}
}
const styles = {
listStyle: {
width: '100%',
height: '100%'
}
}

React Native Flatlist overlapping footer?

I'm getting started with React Native and writing an app. The problem I'm having a problem with the layout/styling. This is a simplified version to show my difficulty.
The Flatlist doesn't know where it's bottom is, it is overlapping the Footer component. I've messed around with it but can't get it to work properly.
import React, { Component } from 'react'
import { FlatList, StyleSheet, Text, View } from 'react-native'
class Header extends Component {
render() {
return (
<View style={styles.headerContainer}>
<Text style={styles.headerText}> </Text>
<Text style={styles.headerText}>
This is the page header!
</Text>
</View>
)
}
}
class Footer extends Component {
render() {
return (
<View style={styles.footerContainer}>
<Text style={styles.footerText}>
This is the page footer!
</Text>
</View>
)
}
}
let myData = [];
for (let i=1; i<=30; i++) {
myData.push({ key: ('My item ' + i) })
}
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<Header />
<View style={styles.listWrapper}>
<FlatList
contentContainerStyle={styles.listContainer}
data={myData}
renderItem={({item}) => <Text style={styles.listItem} >{item.key}</Text>}
contentInset={{top: 0, left: 0, bottom: 50, right: 0}}
/>
</View>
<Footer />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'pink'
},
headerContainer: {
backgroundColor: '#333',
height: 60,
},
headerText: {
textAlign: 'center',
fontSize: 20,
color: '#999'
},
listWrapper: {
flex: 1
},
listContainer: {
backgroundColor: 'lightblue',
},
listItem: {
color: 'red',
fontSize: 28
},
footerContainer: {
backgroundColor: '#333',
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
flex: 1,
height: 20
},
footerText: {
textAlign: 'center',
fontSize: 14,
color: '#999'
}
})
The only solution I have is to add the prop:
ListFooterComponent={<View style={{ height: 20 }} />}
to the Flatlist, giving it the same height as the Footer, so it would take up that space. That works, but it seems inelegant. Is there a better way to do it, like with the CSS?
Thanx.

Place a view relative to other view in React Native

I try to create something like this:
As you see, the text should be placed relative to the marker representing the current day (centered above). When you're at the first few and the last few days, it should stay inside the frame rather than being centered above the tall marker.
How do I achieve this?
This is my current code:
Component
export default class DateLine extends Component {
render() {
const dots = (<View style={Styles.dotsContainer} >
{[...Array(DaysInCurrentMonth())].map((x, i) =>
<View key={i.toString()} style={[Styles.dot, (CurrentDayInMonth() === (i + 1) ? Styles.tallDot : null), (CurrentDayInMonth() < (i + 1) ? Styles.grayDot : null)]} />
)}
</View>);
return (
<View style={Styles.container}>
<Text style={Styles.text}>{GetDateWithMonthAsText(Date()).toUpperCase()}</Text>
{dots}
</View>
);
}
}
Style:
export default StyleSheet.create({
container: {
alignSelf: 'stretch',
marginHorizontal: 15
},
dotsContainer: {
height: 10,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-end'
},
dot: {
backgroundColor: Colors.darkBlueGrey,
height: 5,
width: 5,
borderRadius: 2.5
},
tallDot: {
height: 10
},
grayDot: {
backgroundColor: Colors.pinkishGreyTwo
},
text: {
fontWeight: '600'
}
});
Not a perfect but fast solution that you can easily improve:
export default class DateLine extends Component {
_renderDay (x, i) {
let currentDay = CurrentDayInMonth();
if(currentDay > (i + 1)) {
return <PassedDay />;
}
if(currentDay === (i + 1)) {
return <ActiveDate dateTitle={ GetDateWithMonthAsText(Date()).toUpperCase() } />;
}
return <InactiveDay />;
}
render() {
return (
<View style={ Styles.container }>
<View style={ Styles.dotsContainer } >
{[...Array(DaysInCurrentMonth())].map(this._renderDay)}
</View>
</View>
);
}
}
export function PassedDay() {
return (<View>
<View style={{backgroundColor: '#1f1749', width: 5, height: 5, borderRadius: 5, marginHorizontal: 2.5,}} />
</View>);
}
export function ActiveDate(dateTitle) {
return (<View style={{position: 'relative'}}>
<View style={{position: 'absolute', minWidth: 70, bottom: 15, left: 0, right: 0,}}>
<Text>
{dateTitle}
</Text>
</View>
<View style={{backgroundColor: '#1f1749', width: 5, height: 10, borderRadius: 5, marginHorizontal: 2.5,}} />
</View>);
}
export function InactiveDay() {
return (<View>
<View style={{backgroundColor: 'gray', width: 5, height: 5, borderRadius: 5, marginHorizontal: 2.5,}} />
</View>);
}
The point is: you can manipulate your 'DOM' via react-native's positions in same manner as html.
Hope, it will help.