In my application I have a box that I want to change its height using the GestureHandler. I kind of got the idea but now I can't figure out how I can change the height from bottom to top. I will attach pictures.
My code so far -
import { StyleSheet } from "react-native";
import { PanGestureHandler } from "react-native-gesture-handler";
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withSpring,
} from "react-native-reanimated";
export default function App() {
const translateY = useSharedValue(44);
const gestureHandler = useAnimatedGestureHandler({
onStart: (event, context) => {
context.translateY = translateY.value;
},
onActive: (event, context) => {
translateY.value = event.translationY + context.translateY;
if (translateY.value > 200) {
translateY.value = 200;
}
},
onEnd: (_) => {
if (translateY.value == 200) {
translateY.value = withSpring(50);
}
},
});
const animatedStyle = useAnimatedStyle(() => {
return {
height: translateY.value,
};
});
return (
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[styles.box, animatedStyle]} />
</PanGestureHandler>
);
}
const styles = StyleSheet.create({
box: {
width: 65,
backgroundColor: "black",
alignSelf: "center",
marginTop: 350,
},
});
Final result looks like that -
The movement I want is exactly the opposite
How can i do that?
You're close - you need to translate the Y position of the box in addition to increasing the height. You can change your animated style to:
const animatedStyle = useAnimatedStyle(() => {
return {
height: translateY.value,
translate: [{ translateY: -translateY.value }]
};
});
Related
How can I do a similar oval scroll?
What can I use for this?
Based on the assumption that you want something like this, I wrote a simple example
If someday the link turns out to be broken, below I attach the code additionally
import React, { useCallback, useState, useRef } from "react";
import {
FlatList,
Text,
View,
StyleSheet,
Dimensions,
Animated
} from "react-native";
const { height } = Dimensions.get("window");
const screenMiddle = height / 2;
const itemScaleOffset = height / 3;
const DATA = new Array(20).fill(0).map((...args) => ({
id: args[1],
title: args[1]
}));
// args[1] is an index, just I hate warnings
const Item = ({ title, offsetY }) => {
const [scrollEdges, setScrollEdges] = useState({
top: 0,
middle: 0,
bottom: 0
});
const onLayout = useCallback(
({
nativeEvent: {
layout: { top, height }
}
}) =>
setScrollEdges((edges) => ({
...edges,
top: top - itemScaleOffset - screenMiddle,
middle: top + height / 2 - screenMiddle,
bottom: top + height + itemScaleOffset - screenMiddle
})),
[]
);
const scale = offsetY.interpolate({
inputRange: [scrollEdges.top, scrollEdges.middle, scrollEdges.bottom],
outputRange: [0.66, 1, 0.66],
extrapolate: "clamp"
});
return (
<Animated.View
onLayout={onLayout}
style={[
{
transform: [
{
scale
}
]
},
styles.item
]}
>
<Text style={styles.title}>{title}</Text>
</Animated.View>
);
};
const keyExtractor = ({ id }) => id.toString();
const App = () => {
const offsetY = useRef(new Animated.Value(0)).current;
const renderItem = useCallback(
({ item: { title } }) => <Item title={title} offsetY={offsetY} />,
[offsetY]
);
return (
<View style={styles.app}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={keyExtractor}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: offsetY
}
}
}
],
{
useNativeDriver: false
}
)}
/>
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1
},
item: {
backgroundColor: "#f9c2ff",
padding: 20,
marginVertical: 8,
marginHorizontal: 16
},
title: {
fontSize: 32
}
});
export default App;
I think you should use Reanimated 2 who has a very easy sintaxis and also very powerful. Maybe in combination with RNGestureHandler.
I'm using Video from expo-av to display my videos. My goal is to display the video depending on the Orientation of the device of the user. I'm using ScreenOrientation from expo-screen-orientation so i can detect the rotation using the addOrientationChangeListener function.
I tried my code below but i can't detect the change of the orientation. Any Help of how can i achieve my goal or what's wrong in my code?
import React, { Component } from 'react';
import {
StyleSheet,
View,
TouchableOpacity,
Image,
Text,
Alert,
ScrollView,
Dimensions
} from 'react-native';
import { Video } from 'expo-av';
import * as ScreenOrientation from 'expo-screen-orientation';
import NavigationHelper from '../../../../Helpers/NavigationHelper';
export default class VideoScreen extends Component {
constructor(props) {
super(props);
/* enum Orientation {
UNKNOWN = 0,
PORTRAIT_UP = 1,
PORTRAIT_DOWN = 2,
LANDSCAPE_LEFT = 3,
LANDSCAPE_RIGHT = 4
} */
this.state = {
orientation: 1,
};
}
async componentDidMount() {
await this.detectOrientation();
this.subscription = ScreenOrientation.addOrientationChangeListener(this.onOrientationChange);
/* if (ScreenOrientation.Orientation.LANDSCAPE) {
this.changeScreenLandscapeOrientation();
} */
}
async componentWillUnmount() {
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT);
ScreenOrientation.removeOrientationChangeListener(this.subscription);
// this.changeScreenPortraitOrientation();
}
onOrientationChange = async (orientation) => {
console.log('orientation changed');
if (orientation === 3 || orientation === 4) {
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
} else {
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT);
}
this.setState({ orientation });
};
detectOrientation= async () => {
let orientation = await ScreenOrientation.getOrientationAsync();
const screen = Dimensions.get('screen');
if (orientation === 0) {
orientation = screen.width > screen.height ? ScreenOrientation.Orientation.LANDSCAPE : ScreenOrientation.Orientation.PORTRAIT;
}
this.setState({ orientation });
console.log(orientation);
};
render() {
const { route } = this.props;
const { videoUri } = route.params;
if (!videoUri) {
NavigationHelper.back();
}
return (
<ScrollView style={styles.container}>
<Video
source={{ uri: videoUri }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode={Video.RESIZE_MODE_CONTAIN}
shouldPlay
isLooping
useNativeControls
style={{ width: 300, height: 300, alignSelf: 'center' }}
orientationChange={this.onOrientationChange}
/>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
flexDirection: 'column'
},
});
Look at the library method getOrientationAsync()
export declare function getOrientationAsync(): Promise<Orientation>;
The orientation definition is
export declare enum Orientation {
UNKNOWN = 0,
PORTRAIT_UP = 1,
PORTRAIT_DOWN = 2,
LANDSCAPE_LEFT = 3,
LANDSCAPE_RIGHT = 4
}
So, it already returns the integer that refers to the correct orientation. Maybe what you want to do is just remove the brackets between the orientation:
let orientation = await ScreenOrientation.getOrientationAsync();
I have already tried
const MyTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
notification: '#e1d2f5',
},
};
but this code only changes the badge`s background color.
I need to know how to change the text color.
You only can change color to 'black' or 'white' is depend on the theme is light or dark.
This is the Badge code off the bottom tab bar
import * as React from 'react';
import { Animated, StyleSheet, StyleProp, TextStyle } from 'react-native';
import color from 'color';
import { useTheme } from '#react-navigation/native';
type Props = {
/**
* Whether the badge is visible
*/
visible: boolean;
/**
* Content of the `Badge`.
*/
children?: string | number;
/**
* Size of the `Badge`.
*/
size?: number;
/**
* Style object for the tab bar container.
*/
style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
};
export default function Badge({
visible = true,
size = 18,
children,
style,
...rest
}: Props) {
const [opacity] = React.useState(() => new Animated.Value(visible ? 1 : 0));
const [rendered, setRendered] = React.useState(visible ? true : false);
const theme = useTheme();
React.useEffect(() => {
if (!rendered) {
return;
}
Animated.timing(opacity, {
toValue: visible ? 1 : 0,
duration: 150,
useNativeDriver: true,
}).start(({ finished }) => {
if (finished && !visible) {
setRendered(false);
}
});
}, [opacity, rendered, visible]);
if (visible && !rendered) {
setRendered(true);
}
if (!visible && !rendered) {
return null;
}
// #ts-expect-error: backgroundColor definitely exists
const { backgroundColor = theme.colors.notification, ...restStyle } =
StyleSheet.flatten(style) || {};
// <<<<<<<<<<<<<<<<<<<<<<<<< here <<<<<<<<<<<<<<<<<<<<<<<<<<
const textColor = color(backgroundColor).isLight() ? 'black' : 'white';
const borderRadius = size / 2;
const fontSize = Math.floor((size * 3) / 4);
return (
<Animated.Text
numberOfLines={1}
style={[
{
opacity,
transform: [
{
scale: opacity.interpolate({
inputRange: [0, 1],
outputRange: [0.5, 1],
}),
},
],
backgroundColor,
color: textColor,// <<<<<<<<<<<<<<<<<<<<<<<<< and here <<<<<<<<<<<<<<<<<<<<<<<<<<
fontSize,
lineHeight: size - 1,
height: size,
minWidth: size,
borderRadius,
},
styles.container,
restStyle,
]}
{...rest}
>
{children}
</Animated.Text>
);
}
const styles = StyleSheet.create({
container: {
alignSelf: 'flex-end',
textAlign: 'center',
paddingHorizontal: 4,
overflow: 'hidden',
},
});
Problem:
I am creating react native application with Google maps. This is how my code is structured.
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
Dimensions,
Button,
TouchableOpacity
} from "react-native";
import { MapView } from "expo";
import {
Ionicons,
Foundation,
Entypo,
MaterialCommunityIcons
} from "#expo/vector-icons";
const windowheight = (Dimensions.get("window").height * 80) / 100;
const windowwidth = (Dimensions.get("window").width * 80) / 100;
class Parking extends Component {
static navigationOptions = {
title: "Parking",
headerStyle: {
backgroundColor: "#06153b"
},
headerTintColor: "#fff",
headerTitleStyle: {
color: "#ffff"
}
};
state = {
focusedLocation: {
latitude: 6.9218374,
longitude: 79.8211859,
latitudeDelta: 0.0322,
longitudeDelta:
(Dimensions.get("window").width / Dimensions.get("window").height) *
0.0322
},
locationChosen: false,
placesList: []
};
componentDidMount() {
navigator.geolocation.getCurrentPosition(
pos => {
const coordsEvent = {
nativeEvent: {
coordinate: {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude
}
}
};
this.pickLocationHandler(coordsEvent);
},
err => {
console.log(err);
alert("Fetching the Position failed");
}
);
}
reloadLocation = () => {
navigator.geolocation.getCurrentPosition(
pos => {
const coordsEvent = {
nativeEvent: {
coordinate: {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude
}
}
};
this.pickLocationHandler(coordsEvent);
},
err => {
console.log(err);
alert("Fetching the Position failed");
}
);
};
pickLocationHandler = event => {
this.setState({ locationChosen: true });
const coords = event.nativeEvent.coordinate;
let placesList = [];
let places = [];
this.map.animateToRegion({
...this.state.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude
});
const apikey = "myKey";
fetch(
"https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=" +
coords.latitude +
"," +
coords.longitude +
"&radius=800" +
"&type=parking" +
"&key=" +
apikey
)
.then(response => response.json())
.then(responseJson => {
if (responseJson) {
placesList = responseJson.results;
placesList.map((el, index) => {
var place = {
title: el.name,
coordinates: {
latitude: el.geometry.location.lat,
longitude: el.geometry.location.lng
}
};
places.push(place);
});
this.setState({ placesList: places });
}
});
};
componentWillUnmount = () => {
this.setState(prevState => {
return {
focusedLocation: {
...prevState.focusedLocation,
latitude: 0,
longitude: 0
},
locationChosen: false,
placesList: []
};
});
};
render() {
let marker = null;
if (this.state.locationChosen) {
marker = <MapView.Marker coordinate={this.state.focusedLocation} />;
}
const places = this.state.placesList;
return (
<View style={styles.container}>
<MapView
initialRegion={this.state.focusedLocation}
showsUserLocation={true}
style={styles.map}
onPress={this.pickLocationHandler}
ref={ref => (this.map = ref)}
>
{places.map((place, index) => {
return (
<MapView.Marker
key={index}
coordinate={place.coordinates}
title={place.title}
pinColor="violet"
/>
);
})}
{marker}
</MapView>
</View>
);
}
}
export default Parking;
const styles = StyleSheet.create({
container: {
width: "100%",
alignItems: "center",
paddingBottom: 10,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 10
},
map: {
height: "100%",
width: "100%"
},
button: {
margin: 8
},
callout: {},
calloutButton: {
marginTop: windowheight,
marginLeft: windowwidth,
borderWidth: 1,
borderColor: "rgba(0,0,0,0.2)",
alignItems: "center",
justifyContent: "center",
width: 50,
height: 50,
backgroundColor: "#2b78fe",
borderRadius: 100,
shadowColor: "#e9ebee"
}
});
In the componentDidmount method, I am getting the users current location and render the map according to that. When I open the app and go this page which in this component It loads the map as I needed but when I go to another component and come again to this component it just loads the map according to the place in the state. Can someone help me to solve this problem and Is their way to load the map with new location automatically, when the user's location changes?Thank you!
Its because, you are using StackNavigator and the componentDidMount only get called in the mounting phase.
When you navigate to maps component first time componentDidMount is called, When you navigate to some other component from maps component(maps component doesn't get unmounted in case of stack navigator). When you navigate back it just focuses/updates the already mounted maps component and componentDidMount will not get called. Hopefully render and componentDidUpdate gets called.
If you dont want to change the logic then:
One solution to this problem is instead of navigate() use push(),
this.props.navigation.push(routeName) //react-navigation-v3
push() function will push the new route component into the satck rather than navigating to previous same component in the stack.
This differs from navigate() in that navigate will pop back to earlier in the stack if a route of the given name is already present there. push will always add on top, so a route can be present multiple times.
Second Solution You might wanna check this alternative approach
class MyComponent extends React.Component {
state = {
isFocused: false
};
componentDidMount() {
this.subs = [
this.props.navigation.addListener("didFocus", () => this.setState({ isFocused: true })),
this.props.navigation.addListener("willBlur", () => this.setState({ isFocused: false }))
];
}
componentWillUnmount() {
this.subs.forEach(sub => sub.remove());
}
render() {
// ...
}
}
Third Solution
When you navigate back, also set the state(current location) in componentDidUpdate Note. you need a condition before setting the state in componentDidUpdate, other wise you will end up triggring infinite loop.
Drawing inspiration from this question, I have implemented two draggable components as children in a view. The parent view is as follows:
import React, { Component } from "react";
import { Text, View, StyleSheet, Dimensions } from "react-native";
import Draggable from "./Draggable";
export default class FloorPlan extends Component {
constructor() {
super();
const { width, height } = Dimensions.get("window");
this.separatorPosition = (height * 2) / 3;
}
render() {
return (
<View style={styles.mainContainer}>
<View style={[...styles.dropZone, { height: this.separatorPosition }]}>
<Text style={styles.text}>Floor plan</Text>
</View>
<View style={styles.drawerSeparator} />
<View style={styles.row}>
<Draggable />
<Draggable />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1
},
drawerSeparator: {
backgroundColor: "grey",
height: 20
},
row: {
flexDirection: "row",
marginTop: 25
},
dropZone: {
height: 700,
backgroundColor: "#f4fffe"
},
text: {
marginTop: 25,
marginLeft: 5,
marginRight: 5,
textAlign: "center",
color: "grey",
fontSize: 20
}
});
And the draggable component is implemented as follows:
import React, { Component } from "react";
import {
StyleSheet,
View,
PanResponder,
Animated,
Text,
Dimensions
} from "react-native";
export default class Draggable extends Component {
constructor() {
super();
const { width, height } = Dimensions.get("window");
this.separatorPosition = (height * 2) / 3;
this.state = {
pan: new Animated.ValueXY(),
circleColor: "skyblue"
};
this.currentPanValue = { x: 0, y: 0 };
this.panListener = this.state.pan.addListener(
value => (this.currentPanValue = value)
);
}
componentWillMount() {
this.state.pan.removeListener(this.panListener);
}
componentWillMount() {
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => false,
onMoveShouldSetPanResponder: (evt, gestureState) => false,
onMoveShouldSetPanResponderCapture: (evt, gesture) => {
return true;
},
onPanResponderGrant: (e, gestureState) => {
this.setState({ circleColor: "red" });
},
onPanResponderMove: (event, gesture) => {
Animated.event([
null,
{
dx: this.state.pan.x,
dy: this.state.pan.y
}
])(event, gesture);
},
onPanResponderRelease: (event, gesture) => {
this.setState({ circleColor: "skyblue" });
if (gesture.moveY < this.separatorPosition) {
this.state.pan.setOffset({
x: this.currentPanValue.x,
y: this.currentPanValue.y
});
this.state.pan.setValue({ x: 0, y: 0 });
// this.state.pan.flattenOffset();
} else {
//Return icon to start position
this.state.pan.flattenOffset();
Animated.timing(this.state.pan, {
toValue: {
x: 0,
y: 0
},
useNativeDriver: true,
duration: 200
}).start();
}
}
});
}
render() {
const panStyle = {
transform: this.state.pan.getTranslateTransform()
};
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[
panStyle,
styles.circle,
{ backgroundColor: this.state.circleColor }
]}
/>
);
}
}
let CIRCLE_RADIUS = 30;
let styles = StyleSheet.create({
circle: {
backgroundColor: "skyblue",
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
borderRadius: CIRCLE_RADIUS,
marginLeft: 25
}
});
A draggable component can be dragged onto the FloorPlan and it's location will be remembered for the next pan action. However, sometimes during dragging, a glitch occurs and the icon jumps at the beginning of the pan or completetely disappears.
What could be the problem? I am developing using React Native 0.55.2 and testing using a device running Android 7.