ScrollView: content image is cropped when phone is in landscape mode - react-native

I'm trying to make a book reading experience using some images wrapped in
scrollViews inside a FlatList.
everything is ok in 'portrait' mode but in 'landscape' the images are cropped,
I want to be able to scroll vertically when in 'landscape' so the user can explore the whole image which becomes larger than screen height in 'landscape'
I've tried to modify the dimensions of the image depending on the orientation
but the result is not good.
Here is my code:
states
widthImage:Dimensions.get('window').width,
heightImage: Dimensions.get('window').height,
the content:
const QuranImage = [];
const scrollIsEnabled = this.state.heightImage > this.state.height;
QuranImage.push(
<ScrollView
scrollEnabled = {scrollIsEnabled}
onContentSizeChange = {this.manageScreenFlip}
nestedScrollEnabled={true}
>
<Image style={{
tintColor:'black',
width:this.state.widthImage,
height:this.state.heightImage,
}}
source={require('../Resources/page002.png')}
/>
</ScrollView>
);
QuranImage.push(
<ScrollView>
<Image style={{
tintColor:'black',
width:this.state.width,
height:this.state.height
}}
source={require('../Resources/page003.png')}/>
</ScrollView>
)
this.setState({
pdfViewer:(
<FlatList
horizontal={true}
nestedScrollEnabled={true}
pagingEnabled={true}
data={QuranImage}
keyExtractor={(item, index) => index.toString()}
renderItem={({item,index}) =>item}
/>
)
});
orientation listener fired in another place of the code:
_orientationDidChange = (orientation) => {
if (orientation === 'LANDSCAPE') {
this.setState({
height: Dimensions.get('window').height,
width: Dimensions.get('window').width,
heightImage:1000,
widthImage:1000
},() => {
this.renderPdfViewer();
console.log(Dimensions.get('window').height);
console.log(Dimensions.get('window').width);
});
} else {
console.log(orientation);
}
}
portrait with image fully displayed
landscape mode here I want to be able to scroll vertically to see the entire image

Add key prop in your flatlist as given below.
You can store the current orientation in a redux store and use it in your component as
const {orientation}= this.props
then
<FlatList
horizontal={true}
nestedScrollEnabled={true}
pagingEnabled={true}
data={QuranImage}
keyExtractor={(item, index) => index.toString()}
renderItem={({item,index}) =>item}
key= { orientation =='PORTRAIT' ||
orientation =='PORTRAITUPSIDEDOWN'?'portrait':'landscape' }
/>
modify your _orientationDidChange function as
_orientationDidChange = (orientation) => {
if (orientation === 'PORTRAIT' || orientation==
'PORTRAITUPSIDEDOWN') {
this.setState({
height: Dimensions.get('window').height,
width: Dimensions.get('window').width,
heightImage:1000,
widthImage:1000
},() => {
this.renderPdfViewer();
console.log(Dimensions.get('window').height);
console.log(Dimensions.get('window').width);
});
} else {
this.setState({
height: Dimensions.get('window').width,
width: Dimensions.get('window').height,
heightImage:1000,
widthImage:1000
},() => {
this.renderPdfViewer();
console.log(Dimensions.get('window').height);
console.log(Dimensions.get('window').width);
});
}
}

I found a solution to my issue by basically rerendering the FlatList every time i change orientation plus modifying height making it higher than screen high to enable scrolling.
here is the orientation function:
_orientationDidChange = (orientation) => {
const $this = this;
setTimeout(function() {
let width = Dimensions.get('window').width;
let height = Dimensions.get('window').height;
if (orientation == 'LANDSCAPE') {
// if LANDSCAPE make image height bigger than screen height to force vertical scroll
// 2.7 is a value chosen after visual testing
height = height * 2.7;
} else if (orientation == 'PORTRAIT' || orientation == 'PORTRAITUPSIDEDOWN') {
// if PORTRAIT make image height smaller than screen height so we can have some marges
height = height * 0.98;
}
$this.setState({renderFlat: null, itemLayout: width,width: width, height: height}, () => {
$this.renderFlat();
});
}, 50);
}
the function that render the Flatlist:
renderFlat() {
this.setState({
renderFlat:
(
<FlatList
horizontal={true}
pagingEnabled={true}
data={QuranImagePathList}
keyExtractor={(item, index) => index.toString()}
renderItem={this._renderItem}
viewabilityConfig={this.state.viewabilityConfig}
initialScrollIndex={this.state.currentPage}
onViewableItemsChanged={this.handlePageChange}
showsHorizontalScrollIndicator={false}
getItemLayout={this.getItemLayout}
removeClippedSubviews={true}
/>
)
})
}

Related

Check if component is in View

I have components like below
<Animated.ScrollView
onScroll = {Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
{ useNativeDriver: true, listener: (event) => handleScroll(event) }
)}
scrollEventThrottle = {16}
alwaysBounceHorizontal = {false}
alwaysBounceVertical = {false}
bounces = {false}>
<View style = {{height:100}}><Text>One</Text></View>
<View style = {{height:100}}><Text>Two</Text></View>
<View style = {{height:100}}><Text>Three</Text></View>
<Animated.ScrollView>
I am able to get get Scroll Y position using
const scrollY = new Animated.Value(0);
Now how do I get the positions of view so I can get that values to compute and add animations to it.
For example, when I scroll down and if View - Three becomes completely visible inside the viewport, I need to change some styles to it. And remove the styles if its going away from the viewport... How do I do it?
I think there is a good way.
Because your list is variable, use FlatList to render your elements, and use onViewableItemsChanged to detect wich have changed.
In this example i animate the opacity of views
Init
// fetch api => response
let animatedValues = []
response.forEach(element => {
animatedValues.push(new Animated.Value(0))
})
Handle
const handleViewsChange = (event) => {
let animations = []
event.changed.forEach(view => {
animations.push(
Animated.timing(animatedValues[view.index], {
toValue: view.isViewable ? 1 : 0,
duration: 300,
useNativeDriver: true
})
)
})
Animated.parallel(animations).start()
}
Your list
<Flatlist
data={response}
renderItem={(item, index) => {
return(
<Animated.View key={index} style={{height:100, opacity: animatedValues[index]}}><Text>{item.thing}</Text></Animated.View>
)
}}
keyExtractor={(item, index) => index.toString()}
onViewableItemsChanged={event => handleViewsChange(event)}
/>
You will surely have to adapt for your use

FlatList onEndReached event not working in ScrollView on React-Native

I am going to show the items by using the FlatList and the FlatList component is the child of the ScrollView because there is some animation.
The OnEndReached event is working without parent ScrollView but in ScrollView, not working for me.
I am sure why this happened.
I should use the ScrollView for the animation and also working the FlatList event.
Is there any solution?
Here is my FlatList code and structure.
<Animated.ScrollView style={{ flex: 1 }}
scrollEventThrottle={1}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
{ useNativeDriver: true }
)}>
<ProductSearchResults
products={this.state.products}
itemAction={this.navigateToProduct}
onLoadMore={ async () => {await this.findProducts();}}
more={this.state.more} />
</Animated.ScrollView>
This is ProductSearchResults component code with FlatList.
render() {
const { products, setScrolling } = this.props;
return (
<FlatList
contentContainerStyle={styles.container}
data={products}
initialNumToRender={1}
renderItem={this.renderItem}
keyExtractor={(item, index) => index.toString() }
removeClippedSubviews={false}
numColumns={2}
showsVerticalScrollIndicator={false}
legacyImplementation={false}
onEndReached={({ distanceFromEnd }) => {
console.log('onEndReached:', distanceFromEnd); **// not working**
if (this.props.more && !this.onEndReachedCalledDuringMomentum)
this.props.onLoadMore();
}}
onEndReachedThreshold={Platform.OS === 'ios' ? 0 : Dimensions.get('window').height / 2}
onMomentumScrollBegin={() => { this.onEndReachedCalledDuringMomentum = false; }}
scrollsToTop={false}
ListFooterComponent={
this.props.more && <InnerLoading />
}
onScrollBeginDrag={() => setScrolling(true)}
/>
);
}
This is not a onEndReached event issue.
Maybe this event is working for you and will be happened when the FlatList is mounted on your project.
There is an issue that your structure is not correct.
For this event works correctly, you should change the structure without ScrollView and it is possible for your code.
You can remove the ScrollView Component.
<ProductSearchResults
products={this.state.products}
itemAction={this.navigateToProduct}
onLoadMore={ async () => {await this.findProducts();}}
more={this.state.more}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }])}
/>
<FlatList
.....
onScroll={this.props.onScroll}
.....
/>
Like the above code, you can add the scroll event on FlatList for the parent animation.
Wish to help you.
Replace this line:
onEndReachedThreshold={Platform.OS === 'ios' ? 0 : Dimensions.get('window').height / 2}
with this:
onEndReachedThreshold={0.5}
and it will work.

Translate a View inside an Scrollview in React Native

I'm trying to build this sticky header navbar in my RN app. Basically, an horizontal scrollview of categories that highlight the current category based on Y scrolling.
Thanks to the video of great William Candillon (https://www.youtube.com/watch?v=xutPT1oZL2M&t=1369s) I'm pretty close, but I have a main problem.
I'm using interpolation to translate the X position of category View while scrolling. And then I have a Scrollview wrapping this Animated View. The problem is that Scrollview is not functional as is does not have the reference of the position of the Animated View. As you can see in the gif below (blue -> Animated.View / red -> Scrollview)
I like the interpolation approach as it's declarative and runs on native thread, so I tried to avoid as much as possible create listener attached to scrollTo() function.
What approach would you consider?
export default ({ y, scrollView, tabs }) => {
const index = new Value(0);
const [measurements, setMeasurements] = useState(
new Array(tabs.length).fill(0)
);
const indexTransition = withTransition(index);
const width = interpolate(indexTransition, {
inputRange: tabs.map((_, i) => i),
outputRange: measurements
});
const translateX = interpolate(indexTransition, {
inputRange: tabs.map((_tab, i) => i),
outputRange: measurements.map((_, i) => {
return (
-1 *
measurements
.filter((_measurement, j) => j < i)
.reduce((acc, m) => acc + m, 0) -
8 * i
);
})
});
const style = {
borderRadius: 24,
backgroundColor: 'black',
width,
flex: 1
};
const maskElement = <Animated.View {...{ style }} />;
useCode(
() =>
block(
tabs.map((tab, i) =>
cond(
i === tabs.length - 1
? greaterOrEq(y, tab.anchor)
: and(
greaterOrEq(y, tab.anchor),
lessOrEq(y, tabs[i + 1].anchor)
),
set(index, i)
)
)
),
[index, tabs, y]
);
return (
<Animated.View style={[styles.container, {}]}>
<Animated.ScrollView
scrollEventThrottle={16}
horizontal
style={{ backgroundColor: 'red', flex: 1 }}
>
<Animated.View
style={{
transform: [{ translateX }],
backgroundColor: 'blue'
}}
>
<Tabs
onPress={i => {
if (scrollView) {
scrollView.getNode().scrollTo({ y: tabs[i].anchor + 1 });
}
}}
onMeasurement={(i, m) => {
measurements[i] = m;
setMeasurements([...measurements]);
}}
{...{ tabs, translateX }}
/>
</Animated.View>
</Animated.ScrollView>
</Animated.View>
);
};
For anyone facing this issue, I solved it by adding the following on the animated scrollview to auto scroll the to the active tab
// Tabs.tsx
const scrollH = useRef<Animated.ScrollView>(null);
let lastScrollX = new Animated.Value<number>(0);
//Here's the magic code to scroll to active tab
//translateX is the animated node value from the position of the active tab
useCode(
() => block(
[cond(
or(greaterThan(translateX, lastScrollX), lessThan(translateX, lastScrollX)),
call([translateX], (tranX) => {
if (scrollH.current && tranX[0] !== undefined) {
scrollH.current.scrollTo({ x: tranX[0], animated: false });
}
})),
set(lastScrollX, translateX)
])
, [translateX]);
// Render the Animated.ScrollView
return (
<Animated.ScrollView
horizontal
ref={scrollH}
showsHorizontalScrollIndicator={false}
>{tabs.map((tab, index) => (
<Tab ..../> ..... </Animated.ScrollView>

React-Native Flat List columns number depending on screen orientation

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>}
/>

How to modify Scrollview height dynamically in react native

I have a scroll view with a full-length screen. With one flag I need to show button at the bottom.
Am using Animated.View for showing button at Bottom.
When button visible am unable to change scroll view height.
If I try to manage with marginBottom it is showing unwanted white before getting a button with animation.
Here I need to do either change scroll view height dynamically or transparent unwanted white background.
Below is code snippet:
const modalY = new Animated.Value(Dimensions.get('window').height);
openModal = () => {
Animated.timing(modalY, {
duration: 500,
toValue: Dimensions.get('window').height - 60
}).start();
}
closeModal = () => {
Animated.timing(modalY, {
duration: 300,
toValue: Dimensions.get('window').height
}).start();
}
showButton = () => {
const animationView = <Animated.View style={[ {width: Dimensions.get('window').width,position: 'absolute'}, { transform: [{translateY: modalY}] }]}>
<TouchableHighlight with title height 60/>
</Animated.View>;
return (
animationView
);
};
const marBottom = buttonTitle ? 60 : 0;
here buttonTitle is flag
<View>
<ScrollView contentContainerStyle={{ paddingTop: top }} style={{marginBottom:marBottom}}>
<View style={ [styles.itemscontainer]}>
{ this.myItems()}
</View>
</ScrollView>
{ this.showButton() }
{(buttonTitle) ? this.openModal() : this.closeModal()}
</View>