enter image description here
It needs to be spread out at 100 percent, so I need the card width to be in percent.
I have already tried, position and flex but It does not work.
Try this:
import { View, Image, Dimensions } from 'react-native';
const win = Dimensions.get('window');
const { width, height } = Image.resolveAssetSource(require("../assets/image.png"));
const a = 0.5;
const App = () => {
return(
<View>
<Image source={require("../assets/image.png")} style={styles.image}>
</View>
)
}
const styles = Stylesheet.create({
image: {
resizeMode: 'contain',
width: a * win.width,
height: (a * win.width)*(height/width),
}
})
Here we are using the width of the screen to determine what should be the optimum width of the image. You can vary the value of a in order to set desired width %. height is calculated to maintain the aspect ratio of the image.
Related
In my React Native app, I want to display cards using simple <View>'s that will have a set height to begin with and display two lines of text.
If the text is longer than two lines, I want to display a "more" button which will expand the height of the view to fit all the text when clicked.
My question is how to determine/calculate the height?
The approach I'm thinking is to use two different style classes and programmatically switch them but I'm not sure how to dynamically figure out the height of the <View> so that all the text would fit into it, however long it may be.
const cardStyle = this.props.moreButtonClicked ? "card-long" : "card-std";
return (
<View style={cardStyle}>
<Text>
{
this.props.cardContent.length <= 120
? this.props.cardContent
: this.props.moreButtonClicked
? this.props.cardContent
: this.props.cardContent.substring(0, 119)
}
</Text>
</View>
);
Specifically, how do I figure out the right height for my card-long style class? Or is there a better approach to handling this? Thanks.
You can determine text height before rendering the text by using this library: https://github.com/aMarCruz/react-native-text-size
Here is an example:
import rnTextSize, { TSFontSpecs } from 'react-native-text-size'
type Props = {}
type State = { width: number, height: number }
// On iOS 9+ will show 'San Francisco' and 'Roboto' on Android
const fontSpecs: TSFontSpecs = {
fontFamily = undefined,
fontSize = 24,
fontStyle = 'italic',
fontWeight = 'bold',
}
const text = 'I ❤️ rnTextSize'
class Test extends Component<Props, State> {
state = {
width: 0,
height: 0,
}
async componentDidMount() {
const width = Dimensions.get('window').width * 0.8
const size = await rnTextSize.measure({
text, // text to measure, can include symbols
width, // max-width of the "virtual" container
...fontSpecs, // RN font specification
})
this.setState({
width: size.width,
height: size.height
})
}
// The result is reversible
render() {
const { width, height } = this.state
return (
<View style={{ padding: 12 }}>
<Text style={{ width, height, ...fontSpecs }}>
{text}
</Text>
</View>
)
}
}
I am using PanGestureHandler and PinchGestureHandler to allow a large image to be panned and zoomed in/out on the screen. However, once I introduce the scaling transformation, panning behaves differently.
I have three goals with this implementation:
I am trying to scale down the image to fit a specific height when the view is first loaded. This is so the user can see as much of the image as possible. If you only apply a scale transformation to the image, translation values of 0 will not put the image in the upper left corner (as a result of the centered scale origin).
I am trying to make it so that when someone uses the pinch gesture to zoom, the translation values are adjusted as well to make it seem as if the zoom origin is at the point where the user initiated the gesture (React Native only currently supports a centered origin for scale transformations). To accomplish this, I assume I will need to adjust the translation values when a user zooms (if the scale is anything other than 1).
When panning after a zoom, I want the pan to track the user's finger (rather than moving faster/slower) by adjusting the translation values as they relate to the scale from the zoom.
Here is what I have so far:
import React, { useRef, useCallback } from 'react';
import { StyleSheet, Animated, View, LayoutChangeEvent } from 'react-native';
import {
PanGestureHandler,
PinchGestureHandler,
PinchGestureHandlerStateChangeEvent,
State,
PanGestureHandlerStateChangeEvent,
} from 'react-native-gesture-handler';
const IMAGE_DIMENSIONS = {
width: 2350,
height: 1767,
} as const;
export default function App() {
const scale = useRef(new Animated.Value(1)).current;
const translateX = useRef(new Animated.Value(0)).current;
const translateY = useRef(new Animated.Value(0)).current;
const setInitialPanZoom = useCallback((event: LayoutChangeEvent) => {
const { height: usableHeight } = event.nativeEvent.layout;
const scaleRatio = usableHeight / IMAGE_DIMENSIONS.height;
scale.setValue(scaleRatio);
// TODO: should these translation values be set based on the scale?
translateY.setValue(0);
translateX.setValue(0);
}, []);
// Zoom
const onZoomEvent = Animated.event(
[
{
nativeEvent: { scale },
},
],
{
useNativeDriver: true,
}
);
const onZoomStateChange = (event: PinchGestureHandlerStateChangeEvent) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
// Do something
}
};
// Pan
const handlePanGesture = Animated.event([{ nativeEvent: { translationX: translateX, translationY: translateY } }], {
useNativeDriver: true,
});
const onPanStateChange = (_event: PanGestureHandlerStateChangeEvent) => {
// Extract offset so that panning resumes from previous location, rather than resetting
translateX.extractOffset();
translateY.extractOffset();
};
return (
<View style={styles.container}>
<PanGestureHandler
minPointers={1}
maxPointers={1}
onGestureEvent={handlePanGesture}
onHandlerStateChange={onPanStateChange}
>
<Animated.View style={styles.imageContainer} onLayout={setInitialPanZoom}>
<PinchGestureHandler onGestureEvent={onZoomEvent} onHandlerStateChange={onZoomStateChange}>
<Animated.View style={{ transform: [{ scale }, { translateY }, { translateX }] }}>
<Animated.Image
source={require('./assets/my-image.png')}
style={{
width: IMAGE_DIMENSIONS.width,
height: IMAGE_DIMENSIONS.height,
}}
resizeMode="contain"
/>
</Animated.View>
</PinchGestureHandler>
</Animated.View>
</PanGestureHandler>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
imageContainer: {
flex: 1,
backgroundColor: 'orange',
width: '100%',
},
});
I have tried something along the lines of subtracting the difference in dimensions from the translation values:
translateX.setValue(0 - (IMAGE_DIMENSIONS.width / 2) - (IMAGE_DIMENSIONS.width * scaleRatio / 2))
The numbers don't quite work with this implementation, so I'm probably not doing this right. Also, this would only account for my first goal, and I am guessing that I will need to do something like interpolate the translation values based on the scale value to accomplish the other two.
For example I have a View with styll: width:100, height:100 for the screen with resolution ~160DPI
But if the app built on a device with DPI around 320, the View would be to small on the screen
So what is the solution for this?
How to make the styles the app automatically change depend on the size of the device?
use react-native-responsive-screen
is a great package for making a responsive layout
https://www.npmjs.com/package/react-native-responsive-screen
or you can simply calculate
import { PixelRatio } from 'react-native';
PixelRatio.roundToNearestPixel()
https://reactnative.dev/docs/pixelratio.html
import { Dimensions, PixelRatio } from 'react-native';
let Width = Dimensions.get('window').width;
let Height = Dimensions.get('window').height;
const wp = widthPercent => {
const elemWidth = typeof widthPercent === "number" ? widthPercent : parseFloat(widthPercent);
return PixelRatio.roundToNearestPixel(screenWidth * elemWidth / 100);
};
const hp = heightPercent => {
const elemHeight = typeof heightPercent === "number" ? heightPercent : parseFloat(heightPercent);
return PixelRatio.roundToNearestPixel(screenHeight * elemHeight / 100);
};
and use as
<View style={{height: hp(10), width: wp(20)}} >
<Text style={{fontSize: hp(3.4)}} />
</View>
hp Height percentage
wp Width percentage
I am trying to change numColumns of FlatList on Orientation change.(e.g. For portrait: numColumns=2 and landscape numColumns=3)
But for each Item in list it takes different width
enter image description here
I have tried using Dimensions to change width of each item dynamically
constructor(props) {
super(props);
Dimensions.addEventListener("change", this.updateStyles);
}
componentWillUnmount() {
Dimensions.removeEventListener("change", this.updateStyles);
}
updateStyles = dims => {
this.setState({
viewMode: dims.window.width > 400 ? "landscape" : "portrait"
});
};
For Styling
const styles = StyleSheet.create({
listContainer: {
flex: 1,
flexDirection: "row"
},
landscapeListItem: {
width: Dimensions.get("window").width / 3 - 20
},
portraitListItem: {
width: Dimensions.get("window").width / 2 - 10
}
});
So it looks like this:
in Landscape Mode
after changing orientation to Portrait
on Reload
Reloading screen applies width correctly. But I don't want to reload it.
It should set the width on Orientation Change.
Does anyone knows how can I resolve this issue?
Approach detech Device Oriantation and set numColumns.
<View onLayout={this._onLayout}>
{/* Subviews... */}
</View>
Handle event store orientation on in state
_onLayout(event){
const { width, height } = event.layout; //somewhat similar object
const orientation = (width > height) ? 'LANDSCAPE' : 'PORTRAIT';
this.setState({orientation})
}
Now FlatList
<FlatList
numColumns={this.state.orientation == "LANDSCAPE" ? 3 :2}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
I have found UIManager and onLayout, but all of them can get only the size after rendering.
Are there any 3rd party libraries or APIs to do this before rendering?
The Image component has something like:
var image = new Image();
image.src="??"
image.onload(()=>image.height)
But how about getting the dimensions of a Text or a View?
I think this would help you. From here you can get the Dimensionn of the view.
I don't know if this is possible. But as a workaround you could make the elements invisible by changing their colors or opacity, then calculate the dimensions, then make some changes etc. and then make it visible.
If you are still looking for a solution, I suggest using react-native-text-size. it allows you to get text dimensions before rendering it by using async functions. here is an example of how to use it to achieve what you need.
import rnTextSize, { TSFontSpecs } from 'react-native-text-size'
type Props = {}
type State = { width: number, height: number }
// On iOS 9+ will show 'San Francisco' and 'Roboto' on Android
const fontSpecs: TSFontSpecs = {
fontFamily = undefined,
fontSize = 24,
fontStyle = 'italic',
fontWeight = 'bold',
}
const text = 'I ❤️ rnTextSize'
class Test extends Component<Props, State> {
state = {
width: 0,
height: 0,
}
async componentDidMount() {
const width = Dimensions.get('window').width * 0.8
const size = await rnTextSize.measure({
text, // text to measure, can include symbols
width, // max-width of the "virtual" container
...fontSpecs, // RN font specification
})
this.setState({
width: size.width,
height: size.height
})
}
// The result is reversible
render() {
const { width, height } = this.state
return (
<View style={{ padding: 12 }}>
<Text style={{ width, height, ...fontSpecs }}>
{text}
</Text>
</View>
)
}
}