I want to make a "rounded bottom" component, without using a ImageBackground, like this:
I tried to use a combination of <LinearGradient/>, but to simplify the code in this question, I used <View/> instead.
Here is my code:
import React from 'react'
import { Dimensions, StyleSheet, View } from 'react-native'
export default class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<View style={classes.container}>
<View style={classes.block} />
<View style={classes.roundedBlock} />
</View>
)
}
}
const classes = StyleSheet.create({
container: {
flex: 1,
marginTop: 30,
},
block: {
height: 135,
backgroundColor: 'red',
},
roundedBlock: {
height: 15,
backgroundColor: 'red',
width: Dimensions.get('window').width,
borderBottomLeftRadius: Dimensions.get('window').width / 2,
borderBottomRightRadius: Dimensions.get('window').width / 2,
}
})
This code is available for tests purpose on Expo Snack
Here is the result:
As you can see, the borderRadius is limited to 7.5px, which is half of the height of the block, instead of half of the width as demanded.
Is there a way to override this limit? If no, is there a way to achieve what I want?
You can use ART from react-native to draw whatever you want to draw. Some unofficial docs https://github.com/react-native-china/react-native-ART-doc/blob/master/doc.md. Please check the Expo Snack or code below.
import React from 'react';
import { Dimensions, StyleSheet, View, ART } from 'react-native';
const {
Surface,
Shape,
Path,
RadialGradient,
Pattern,
Transform,
LinearGradient,
} = ART;
const width = Dimensions.get('window').width;
export default class App extends React.Component {
constructor(props) {
super(props);
}
getPathRect = () => {
const x = width;
const y = 0;
const radius = 1000;
return ART.Path()
.moveTo(x, y)
.lineTo(x - width, y)
.lineTo(x - width, y + width / 2)
.lineTo(x, y + width / 2)
.close();
};
getPathArc = () => {
const x = width;
const y = 0;
const radius = 1000;
return ART.Path()
.moveTo(x, y + width / 2)
.arc(-x, 0, radius, radius)
.close();
};
gradient = () => {
return new LinearGradient(
{
'.01': 'blue', // blue in 1% position
'1': 'red', // opacity white in 100% position
},
'0',
'0',
width,
'0'
);
};
render() {
return (
<View style={classes.container}>
<Surface width={width} height={width}>
<Shape
d={this.getPathRect()}
fill={this.gradient()}
// stroke="red"
strokeWidth="1"
strokeCap="butt"
strokeJoin="bevel"
/>
<Shape
d={this.getPathArc()}
fill={this.gradient()}
// stroke="red"
strokeWidth="1"
strokeCap="butt"
strokeJoin="bevel"
/>
</Surface>
</View>
);
}
}
const classes = StyleSheet.create({
container: {
flex: 1,
marginTop: 30,
},
});
Related
I am new to programming and my task is to animate the height of my image on scroll. i.e. decrease height on scroll down and increase height to original when scrolling up. However, following the React Native documentation on Animated, I replaced Text component with my Image and I'm unable to get the image showing. It shows when I don't wrap it in <Animated.View>, can anyone explain why and tell me how to fix? Thank you.
This is my current code just trying to do the sample fade animation before I try to animate height and running into issues getting the TopImage component showing:
import React, { useRef } from 'react';
import {
Animated,
View,
ScrollView,
StyleSheet,
} from 'react-native';
import { useHeaderHeight } from '#react-navigation/stack';
import TopImage from '../components/TopImage';
import GroceryList from '../components/GroceryList';
const App = () => {
const { theme } = useContext(ThemeContext);
const styles = createStyleSheet();
const fadeAnim = useRef(new Animated.Value(0)).current;
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 5000,
useNativeDriver: true,
}).start();
};
const fadeOut = () => {
Animated.timing(fadeAnim, {
toValue: 0,
duration: 3000,
useNativeDriver: true,
}).start();
};
return (
<View style={styles.container}>
<Animated.View
style={
{
// Bind opacity to animated value
opacity: fadeAnim,
},
}>
<TopImage />
</Animated.View>
<ScrollView
style={styles.scrollContainer}
onScrollBeginDrag={() => fadeIn()}>
<GroceryList />
</ScrollView>
</View>
);
};
const createStyleSheet = () => {
const headerHeight = useHeaderHeight();
return StyleSheet.create({
container: { flex: 1 },
scrollContainer: { paddingTop: headerHeight + 100 },
});
};
export default App;
and this is the code for my TopImage component:
import React from 'react';
import { Image, StyleSheet } from 'react-native';
const topImage = require('../images/top.png');
const TopImage = () => {
const styles = createStyleSheet();
return <Image source={topImage} style={styles.image} />;
};
const createStyleSheet = () => {
return StyleSheet.create({
image: {
position: 'absolute',
width: '100%',
height: '50%',
},
});
};
export default TopImage;
The reason it's not showing is because your image is 'absolute' positioned and as such, the outer container (Animated.View) has no height.
If you apply a height to the Animated.View like below. You'll see that the opacity is actually working, you just couldn't see it before because the Animated.View was 0 pixels tall.
<Animated.View
style={
{
// Bind opacity to animated value
opacity: fadeAnim,
height: 300,
},
}>
<TopImage />
</Animated.View>
Alternatively you could make the image position relative, height 0 and animate it to the correct size.
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
I aim to show a rotating cube in a react-native app. I can show the cube with this code but whenever I uncomment the rotation, it disappears. Anything with the renderer ?
I use snack and the cube is visible only in the "Web" view, not in iOS or Android.
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import { GLView } from 'expo-gl';
import { THREE } from 'expo-three';
import ExpoTHREE from 'expo-three';
export default class App extends React.Component {
constructor(props) {
super(props);
this._onGLContextCreate = this._onGLContextCreate.bind(this);
this.animate = this.animate.bind(this);
}
_onGLContextCreate = async gl => {
this.scene = new THREE.Scene();
this.camera = new THREE.Camera(
75,
gl.drawingBufferWidth / gl.drawingBufferHeight,
0.1,
1000
);
this.scene.add(this.camera);
this.renderer = new ExpoTHREE.Renderer({ gl });
this.geometry = new THREE.BoxGeometry(1, 1, 1);
this.material = new THREE.MeshBasicMaterial({ color: 0xff0f00 });
this.obj = new THREE.Mesh(this.geometry, this.material);
this.edges = new THREE.EdgesGeometry(this.geometry);
this.line = new THREE.LineSegments(
this.edges,
new THREE.LineBasicMaterial({ color: 0xffffff })
);
this.scene.add(this.line);
this.scene.add(this.obj);
this.animate();
};
animate = () => {
requestAnimationFrame(this.animate);
//this.geometry.rotation.x += 0.01;
//this.geometry.rotation.y += 0.01;
this.renderer.render(this.scene, this.camera);
};
render() {
return (
<View style={styles.container}>
<GLView
style={{ width: 300, height: 300 }}
onContextCreate={this._onGLContextCreate}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
Code was mainly adapted from https://medium.com/#zohayb1996/using-expo-and-three-js-with-react-native-4bcb353b222e
Thanks
In Three.js you don't rotate a Geometry, you rotate the Mesh. Simply update your animate function to use:
this.obj.rotation.x += 0.01;
this.obj.rotation.y += 0.01;
I want to display compass on my react-native project, i use this compas-github i already follow all step on that link but got error like this error.
I really new to this.
Please help me or suggest another way to display compass on react-native.
Big thanks
This is my code
import React, {Component} from "react";
import { Text,View, StyleSheet, Image, Animated, Easing, Dimensions} from "react-native";
import {Location, Permissions} from 'expo';
import {
Container,
Header,
Content,
} from "native-base";
export default class KompasPage extends Component{
constructor(props) {
super(props);
this.spinValue = new Animated.Value(0);
this.state = {
location: null,
//errorMessage: null,
heading: null,
truenoth: null,
timer: false,
};
}
timePass() {
this.setState({ timer: true });
}
componentWillMount() {
setTimeout(() => {
this.timePass();
}, 1000);
this._getLocationAsync();
}
componentWillUpdate()
{
this.spinValue()
}
_getLocationAsync = async () => {
//check dev loc permiss
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
this.setState({
errorMessage : 'Permission to access location was denied',
});
}
else {
Expo.Location.watchHeadingAsync((obj) => {
let heading = obj.magHeading;
this.setState({heading: heading})
})
}
};
spin() {
let start = JSON.stringify(this.spinValue);
let heading = Math.round(this.state.heading);
let rot = +start;
let rotM = rot % 360;
if (rotM < 180 && (heading > (rotM +180)))
rot -= 360;
if (rotM >= 180 && (heading <= (rotM -180)))
rot += 360;
rot += (heading - rotM)
Animated.timing(
this.spinValue,
{
toValue: rot,
duration: 300,
easing: Easing.easeInOut
}
).start()
}
render() {
const { navigate } = this.props.navigation;
let LoadingText = 'Loading...';
let display = LoadingText;
if (this.state.errorMessage)
display = this.state.errorMessage;
const spin = this.spinValue.interpolate({
inputRange: [0,360],
outputRange: ['-0deg', '-360deg']
})
display = Math.round(JSON.stringify(this.spinValue))
if(display < 0)
display += 360
if(display > 360)
display -= 360
return(
<View style={styles.container}>
<Text style={styles.text}>{display+'°'}</Text>
<View style={styles.imageContainer} >
<Animated.Image resizeMode='contain' source={require("../image/dasar_kompas.png")}
style={{
width: deviceWidth - 10, height: deviceHeight/2 - 10,
left: deviceWidth /2 - (deviceWidth - 10)/2, top: deviceHeight /2 - (deviceHeight/2 - 10)/2,
transform: [{rotate: spin}],
}} />
</View>
<View style={styles.arrowContainer} >
<Image resizeMode='contain' source={require("../image/kompasbaru.png")} style={styles.arrow} />
</View>
</View>
);
}
}
const deviceWidth = Dimensions.get('window').width
const deviceHeight = Dimensions.get('window').height
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: '#263544',
fontSize: 80,
transform: ([{translateY: -(deviceHeight/2 - (deviceHeight/2 - 10)/2) - 50 }])
},
imageContainer: {
...StyleSheet.absoluteFillObject,
},
arrowContainer: {
...StyleSheet.absoluteFillObject,
},
arrow: {
width: deviceWidth/7,
height: deviceWidth/7,
left: deviceWidth /2 - (deviceWidth/7)/2,
top: deviceHeight /2 - (deviceWidth/7)/2,
opacity: 0.9
}
});
As far as I know, the example you’re following uses some sort of locational data that requires internet for showing direction. If your device has built in compass or magnetometer, you should make use of that. That way it would be more efficient. Here are two examples, one using expo which is much easier to implement but requires, you guessed it expo. And here is another using only react native.
I have an image that I pull down from a URL. I do not know the dimensions of this image ahead of time.
How do I style / layout the image so that it is the full width of the parent view and the height is calculated so that the aspect ratio is maintained?
I have tried using onLoad in 0.34.0-rc.0 and the height / width are both 0 in event.nativeEvent.source.
The image is in a <ScrollView/>. I am not after a full screen image.
My use was very similar in that I needed to display an image with full screen width but maintaining its aspect ratio.
Based on #JasonGaare's answer to use Image.getSize(), I came up with the following implementation:
class PostItem extends React.Component {
state = {
imgWidth: 0,
imgHeight: 0,
}
componentDidMount() {
Image.getSize(this.props.imageUrl, (width, height) => {
// calculate image width and height
const screenWidth = Dimensions.get('window').width
const scaleFactor = width / screenWidth
const imageHeight = height / scaleFactor
this.setState({imgWidth: screenWidth, imgHeight: imageHeight})
})
}
render() {
const {imgWidth, imgHeight} = this.state
return (
<View>
<Image
style={{width: imgWidth, height: imgHeight}}
source={{uri: this.props.imageUrl}}
/>
<Text style={styles.title}>
{this.props.description}
</Text>
</View>
)
}
}
I am new on react-native but I came across this solution using a simple style:
imageStyle: {
height: 300,
flex: 1,
width: null}
Image full width from my 1º App:
It worked for me.
React Native has a function built in that will return the width and height of an image: Image.getSize(). Check out the documentation here
It worked for me.
import React from 'react'
import { View, Text, Image } from 'react-native'
class Test extends React.Component {
render() {
return (
<View>
<Image
source={{ uri: "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcQL6goeE1IdiDqIUXUzhzeijVV90TDQpigOkiJGhzaJRbdecSEf" }}
style={{ height: 200, left: 0, right: 0 }}
resizeMode="contain"
/>
<Text style={{ textAlign: "center" }}>Papaya</Text>
</View>
);
}
}
export default Test;
Another way you can get width of parent view after layout event.
<View
style={{ flex: 1}}
layout={(event) => { this.setState({ width: event.nativeEvent.layout.width }); }}
/>
When you get width parent view from layout event then you can assign width to Image tag.
import React from 'react';
import { View, Image } from 'react-native';
class Card extends React.Component {
constructor(props) {
super(props);
this.state = {
height: 300,
width: 0
};
}
render() {
return (
<View style={{
flex: 1,
flexDirection: 'row'
}}>
<View style={{ width: 50, backgroundColor: '#f00' }} />
<View
style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fafafa' }}
onLayout={(event) => { this.setState({ width: event.nativeEvent.layout.width }); }}
>
{
this.state.width === 0 ? null : (
<Image
source={{ uri: "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcQL6goeE1IdiDqIUXUzhzeijVV90TDQpigOkiJGhzaJRbdecSEf" }}
style={{ width: this.state.width, height: this.state.height }}
resizeMode="contain"
/>
)
}
</View>
</View>
);
}
}
export default Card;
if you have a static image you can use like this
import React, { Component } from "react";
import { View, Animated, Image, Dimensions } from "react-native";
import splashScreen from "../../../assets/imgs/splash-screen.png";
export default class MasterPage extends Component {
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0),
imgWidth: 0,
imgHeight: 0
};
}
checkLogIn = async () => {
const width = 533; // set your local image with
const height = 527; // set your local image height
// calculate image width and height
const screenWidth = Dimensions.get("window").width;
const scaleFactor = width / screenWidth;
const imageHeight = height / scaleFactor;
this.setState({ imgWidth: screenWidth, imgHeight: imageHeight });
};
async componentDidMount() {
Animated.timing(
// Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 1, // Animate to opacity: 1 (opaque)
duration: 800, // Make it take a while
useNativeDriver: true
}
).start(this.checkLogIn);
}
render() {
const { imgWidth, imgHeight, fadeAnim } = this.state;
return (
<Animated.View
style={{
opacity: fadeAnim,
backgroundColor: "#ebebeb",
flex: 1,
justifyContent: "center",
alignItems: "center"
}}
>
<View>
<Image
source={splashScreen}
style={{ width: imgWidth, height: imgHeight }}
/>
</View>
</Animated.View>
);
}
}
try this:
pass image uri and parentWidth (can be const { width } = Dimensions.get("window");) and voila... you have height auto calculated based on width and aspect ratio
import React, {Component} from 'react';
import FastImage from 'react-native-fast-image';
interface Props {
uri: string;
parentWidth: number;
}
interface State {
calcImgHeight: number;
width: number
aspectRatio: number
}
export default class CustomFastImage extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
calcImgHeight: 0,
width: props.parentWidth,
aspectRatio: 1
};
}
componentDidUpdate(prevProps: Props){
const { aspectRatio, width }= this.state
const { parentWidth} = this.props
if( prevProps.parentWidth !== this.props.parentWidth){
this.setState({
calcImgHeight: aspectRatio * parentWidth,
width: parentWidth
})
}
}
render() {
const {calcImgHeight, width} = this.state;
const {uri} = this.props;
return (
<FastImage
style={{width: width, height: calcImgHeight}}
source={{
uri,
}}
resizeMode={FastImage.resizeMode.contain}
onLoad={(evt) =>
{
this.setState({
calcImgHeight: (evt.nativeEvent.height / evt.nativeEvent.width) * width, // By this, you keep the image ratio
aspectRatio: (evt.nativeEvent.height / evt.nativeEvent.width),
})}
}
/>
);
}
}
in my case it works for me changing the aspect ratio and the flex of the image like this
flex:1,aspectRatio:1.2, height:null
incase you can't solve it yet, React Native v.0.42.0 have resizeMode
<Image style={styles.intro_img} source={require('img.png')}