React-native flatlist to keep scrolling while touching view - react-native

I am trying to implement a drag and drop solution in react-native and I want to make my flatlist scroll if I drag an item in the upper 10 percent and bottom 10 percent of the view. So far the only way i can get it to happen is by calling a function that recursively dispatches a this._flatList.scrollToOffset({ offset, animated: false });. The recursive call stops when a certain condition is met but the scrolling effect is choppy. Any advice on making that smoother?
// on move pan responder
onPanResponderMove: Animated.event([null, { [props.horizontal ? 'moveX' : 'moveY']: this._moveAnim }], {
listener: (evt, gestureState) => {
const { moveX, moveY } = gestureState
const { horizontal } = this.props
this._move = horizontal ? moveX : moveY;
const { pageY } = evt.nativeEvent;
const tappedPixel = pageY;
const topIndex = Math.floor(((tappedPixel - this._distanceFromTop + this._scrollOffset) - this._containerOffset) / 85);
const bottomIndex = Math.floor(((tappedPixel + (85 - this._distanceFromTop) + this._scrollOffset) - this._containerOffset) / 85);
this.setTopAndBottom(topIndex, bottomIndex);
this.scrolling = true;
this.scrollRec();
}
}),
// recursive scroll function
scrollRec = () => {
const { activeRow } = this.state;
const { scrollPercent, data, } = this.props;
const scrollRatio = scrollPercent / 100;
const isLastItem = activeRow === data.length - 1;
const fingerPosition = Math.max(0, this._move - this._containerOffset);
const shouldScrollUp = fingerPosition < (this._containerSize * scrollRatio); // finger is in first 10 percent
const shouldScrollDown = !isLastItem && fingerPosition > (this._containerSize * (1 - scrollRatio)) // finger is in last 10
const nextSpacerIndex = this.getSpacerIndex(this._move, activeRow);
if (nextSpacerIndex >= this.props.data.length) { this.scrolling = false; return this._flatList.scrollToEnd(); }
if (nextSpacerIndex === -1) { this.scrolling = false; return; }
if (shouldScrollUp) this.scroll(-20);
else if (shouldScrollDown) this.scroll(20);
else { this.scrolling = false; return; };
setTimeout(this.scrollRec, 50);
}
/

Yeah, pass an options object to your Animated.event with your listener with useNativeDriver set to true
Animated.event([
null,
{ [props.horizontal ? "moveX" : "moveY"]: this._moveAnim },
{
listener: ...,
useNativeDriver: true
}
]);
Should make things significantly smoother.
edit: I feel I should add that there is probably a saner way to accomplish this, are you only using a FlatList because of it's "infinite" dimensions?

Related

Jerky Animations After Awhile ThreeJs

At first, my animation seems to work fine. However, after a few seconds, the animations become very jerky and laggy, I'm not sure why.
At first I thought it was due to the animation button I had which allows the user to start and stop the animation at will. However, even after I commented out the button, the animation continued to be laggy.
let camera, scene, renderer;
const loader = new GLTFLoader();
let mixer = null;
let controls;
const clock = new THREE.Clock();
let previousTime = 0;
//start and stop button
let runAnim = false;
let isPlay = true;
//animation
function animation() {
if (!isPlay) return;
const elapsedTime = clock.getElapsedTime();
const deltaTime = elapsedTime - previousTime;
previousTime = elapsedTime;
//Update mixer
if (mixer !== null) {
mixer.update(deltaTime);
}
// Update controls
controls.update();
window.requestAnimationFrame(animation);
render();
}
function render() {
renderer.render(scene, camera);
}
module.exports = function getImage() {
const mountRef = useRef(null);
useEffect(() => {
//Model
loader.load(`/gltf/1.gltf`);
mixer = new THREE.AnimationMixer(gltf.scene);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
animation();
//Camera
camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(4, 0, 5);
scene = new THREE.Scene();
// Controls
controls = new OrbitControls(camera, renderer.domElement);
controls.update();
controls.enableDamping = true;
// Animation button
const animateButton = document.getElementById('animate-button');
const stopAnimation = (e) => {
if (runAnim) {
runAnim = false;
isPlay = true;
animation();
console.log('animation starts');
} else {
runAnim = true;
isPlay = false;
console.log('animation stops');
}
};
animateButton.addEventListener('click', stopAnimation);
return () => mountRef.removeChild(renderer.domElement);
}, []);
return (
<div>
<div ref={mountRef}>
<AnimationButton />
</div>
</div>
);
};

Problem with script to change number of Flatlist columns depending on rotation/size

I'm working on some code to calculate numColumns for a flatlist- intention is 3 on landscape tablet, 2 on portrait tablet, and 1 on portrait phone.
Here's my code:
const [width, setWidth] = useState(Dimensions.get('window').width);
const [imageWidth, setImageWidth] = useState(100);
const [imageHeight, setImageHeight] = useState(100);
const [columns, setColumns] = useState(3);
useEffect(() => {
function handleChange() {
setWidth(Dimensions.get('window').width);
}
Dimensions.addEventListener("change", handleChange);
return () => Dimensions.removeEventListener("change", handleChange);
}, [width]);
useEffect(() => {
if (width > 1100) {
setColumns(3);
} else if (width <= 1099 && width > 600) {
setColumns(2);
} else {
setColumns(1);
}
setImageWidth((width - (64 * columns) + 15) / columns);
setImageHeight(((width - (64 * columns) + 15) / columns) * .6);
}, [width]);
imageWidth and imageHeight are passed to the render component of the flatlist to size an image.
It seems to work fine when I load it in landscape mode, but if I rotate to portrait, I get this:
Then, if I go back to landscape, it stays as 2 columns?
Any idea how I can fix this?
You're not returning the height of the device, afterwards, you need to calculate the orientation of the device (isLandscape).
Logically it flows as the following:
is the device landscape? (setColumns 3)
is the device wide and portrait? (set columns 2)
others (setColumns 1)
From there you can pass that into the second useEffect, (say, useColumnsHook). This should be able to set the height/width based on orientation of the device.
I also recommend setting the height/width based on percentages rather than exact pixels for devices (100%, 50%, 33.3%).
const [width, setWidth] = useState(Dimensions.get('window').width);
const [imageWidth, setImageWidth] = useState(100);
const [imageHeight, setImageHeight] = useState(100);
const [columns, setColumns] = useState(3);
/**
* orientation
*
* return {
* width,
* height
* }
*/
const useScreenData = () => {
const [screenData, setScreenData] = useState(Dimensions.get("screen"))
useEffect(() => {
const onChange = (result) => {
setScreenData(result.screen)
}
Dimensions.addEventListener("change", onChange)
return () => Dimensions.removeEventListener("change", onChange)
})
return {
...screenData,
}
}
const { width, height } = useScreenData()
const isLandscape = width > height
useEffect(() => {
if (isLandscape && width > 1100) {
// handle landscape
setColumns(3)
} else if (!isLandscape && (width <= 1099 && width > 600)) {
setColumns(2)
} else {
setColumns(1)
}
setImageWidth((width - (64 * columns) + 15) / columns);
setImageHeight(((width - (64 * columns) + 15) / columns) * .6);
}, [width, isLandscape]);
1:Tablet or Phone
You can use the react-native-device-info package along with the Dimensions API. Check the isTablet() method and apply different styles according on the result.
if you care Expo user then you have to user expo-constants userInterfaceIdiom method to detect isTablet Reference
import DeviceInfo from 'react-native-device-info';
let isTablet = DeviceInfo.isTablet();
2:Portrait mode or Landscape mode
then you can find portrait mode or landscape mode by
const isPortrait = () => {
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
};
3:Code:
import React, { useState, useEffect } from "react";
import { View, Text, Dimensions } from "react-native";
import DeviceInfo from "react-native-device-info";
const App = () => {
const isLandscapeFunction = () => {
const dim = Dimensions.get("screen");
return dim.width >= dim.height;
};
const [width, setWidth] = useState(Dimensions.get("window").width);
const [imageWidth, setImageWidth] = useState(100);
const [imageHeight, setImageHeight] = useState(100);
const [columns, setColumns] = useState(3);
let isTabletPhone = DeviceInfo.isTablet();;
useEffect(() => {
function handleChange(result) {
setWidth(result.screen.width);
const isLandscape = isLandscapeFunction();
if (isLandscape && isTabletPhone) {
setColumns(3);
} else if (!isLandscape && isTabletPhone) {
setColumns(2);
} else {
setColumns(1);
}
}
Dimensions.addEventListener("change", handleChange);
return () => Dimensions.removeEventListener("change", handleChange);
});
useEffect(() => {
console.log(columns);
setImageWidth((width - 64 * columns + 15) / columns);
setImageHeight(((width - 64 * columns + 15) / columns) * 0.6);
}, [columns, width]);
return (
<View style={{ justifyContent: "center", alignItems: "center", flex: 1 }}>
<Text>imageWidth {imageWidth}</Text>
<Text>imageHeight {imageHeight}</Text>
</View>
);
};
export default App;

My reanimated works more than once. How can make this animation work for once?

I'm trying to make my tabbar dissappear while scrolling down with animation. onScroll sends boolean value if last y coord is bigger than current y coord its scrolling up and otherwise scrolling down. If I continue to scroll down onScroll still sends true value to my function and animaton works more than once. How can i disable position so only toValue is gonna work and my function will not trigger again and again while returning boolean value is same from onScroll.
function runTiming(value, dest) {
const clock = new Clock();
const state = {
finished: new Value(0),
position: new Value(0),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 200,
toValue: new Value(0),
easing: Easing.inOut(Easing.cubic),
};
return block([
cond(clockRunning(clock), 0, [
set(state.finished, 0),
set(state.time, 0),
set(state.position, value),
set(state.frameTime, 0),
set(config.toValue, dest),
startClock(clock),
]),
timing(clock, state, config),
cond(state.finished, debug('stop clock', stopClock(clock))),
state.position,
]);
}
This might be due to the onScroll event being fired more than once.
I just coded this yesterday and will give you the code that is working and has been tested:
export const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
class MainComponent extends PureComponent {
constructor(props) {
super(props);
this.offset = 0;
this.isAnimatingMenu = false;
this.onScroll = this.onScroll.bind(this)
};
onScroll = throttle(event => {
const currentOffset = event.nativeEvent.contentOffset.y;
const direction = currentOffset > this.offset ? 'down' : 'up';
const diff = currentOffset - this.offset;
if (direction === 'down' && diff > 9 && !this.isAnimatingMenu) {
this.isAnimatingMenu = true
this.onHideMenu(() => this.isAnimatingMenu = false)
}
if (direction === 'up' && diff < -9 && !this.isAnimatingMenu) {
this.isAnimatingMenu = true
this.onShowMenu(() => this.isAnimatingMenu = false)
}
this.offset = currentOffset;
}, 75)
render() {
<FlatList
windowSize={1}
bounces={false}
style={styles.flex1}
scrollEventThrottle={75}
onScroll={this.onScroll}
renderItem={this.renderItem}
data={this.deriveHomeWidgetsDataFromProps()}
/>
}
}
In the functions onHideMenu and onShowMenu call your animation function to show/hide the menu. onScroll can be implemented on ScrollView or SectionList aswell. If you need more help let me know.

Expo-pixi Save stage children on App higher state and retrieve

I'm trying another solution to my problem:
The thing is: im rendering a Sketch component with a background image and sketching over it
onReady = async () => {
const { layoutWidth, layoutHeight, points } = this.state;
this.sketch.graphics = new PIXI.Graphics();
const linesStored = this.props.screenProps.getSketchLines();
if (this.sketch.stage) {
if (layoutWidth && layoutHeight) {
const background = await PIXI.Sprite.fromExpoAsync(this.props.image);
background.width = layoutWidth * scaleR;
background.height = layoutHeight * scaleR;
this.sketch.stage.addChild(background);
this.sketch.renderer._update();
}
if (linesStored) {
for(let i = 0; i < linesStored.length; i++) {
this.sketch.stage.addChild(linesStored[i])
this.sketch.renderer._update();
}
}
}
};
this lineStored variable is returning a data i've saved onChange:
onChangeAsync = async (param) => {
const { uri } = await this.sketch.takeSnapshotAsync();
this.setState({
image: { uri },
showSketch: false,
});
if (this.sketch.stage.children.length > 0) {
this.props.screenProps.storeSketchOnState(this.sketch.stage.children);
}
this.props.callBackOnChange({
image: uri,
changeAction: this.state.onChangeAction,
startSketch: this.startSketch,
undoSketch: this.undoSketch,
});
};
storeSketchOnState saves this.sketch.stage.children; so when i change screen and back to the screen my Sketch component in being rendered, i can retrieve the sketch.stage.children from App.js state and apply to Sketch component to persist the sketching i was doing before
i'm trying to apply the retrieved data like this
if (linesStored) {
for(let i = 0; i < linesStored.length; i++) {
this.sketch.stage.addChild(linesStored[i])
this.sketch.renderer._update();
}
}
```
but it is not working =(

React Native Swipe Gestures not working in android

I'am facing a problem with swipe gestures not responding in android as I'am using react-native swipe gestures. It works fine in IOS. Below is the source code for the same.
constructor(props, context) {
super(props, context);
this.swipeConfig = Object.assign(swipeConfig, props.config);
}
componentWillReceiveProps(props) {
this.swipeConfig = Object.assign(swipeConfig, props.config);
}
componentWillMount() {
const responderEnd = this._handlePanResponderEnd.bind(this);
const shouldSetResponder = this._handleShouldSetPanResponder.bind(this);
this._panResponder = PanResponder.create({ //stop JS beautify collapse
onStartShouldSetPanResponder: shouldSetResponder,
onMoveShouldSetPanResponder: shouldSetResponder,
onPanResponderRelease: responderEnd,
onPanResponderTerminate: responderEnd
});
}
_handleShouldSetPanResponder(evt, gestureState) {
return evt.nativeEvent.touches.length === 1 &&
!this._gestureIsClick(gestureState);
}
_gestureIsClick(gestureState) {
return Math.abs(gestureState.dx) < 5 && Math.abs(gestureState.dy) < 5;
}
_handlePanResponderEnd(evt, gestureState) {
const swipeDirection = this._getSwipeDirection(gestureState);
this._triggerSwipeHandlers(swipeDirection, gestureState);
}
_triggerSwipeHandlers(swipeDirection, gestureState) {
const {onSwipe, onSwipeUp, onSwipeDown, onSwipeLeft, onSwipeRight} =
this.props;
const {SWIPE_LEFT, SWIPE_RIGHT, SWIPE_UP, SWIPE_DOWN} = swipeDirections;
onSwipe && onSwipe(swipeDirection, gestureState);
switch (swipeDirection) {
case SWIPE_LEFT:
onSwipeLeft && onSwipeLeft(gestureState);
break;
case SWIPE_RIGHT:
onSwipeRight && onSwipeRight(gestureState);
break;
case SWIPE_UP:
onSwipeUp && onSwipeUp(gestureState);
break;
case SWIPE_DOWN:
onSwipeDown && onSwipeDown(gestureState);
break;
}
}
_getSwipeDirection(gestureState) {
const {SWIPE_LEFT, SWIPE_RIGHT, SWIPE_UP, SWIPE_DOWN} = swipeDirections;
const {dx, dy} = gestureState;
if (this._isValidHorizontalSwipe(gestureState)) {
return (dx > 0)
? SWIPE_RIGHT
: SWIPE_LEFT;
} else if (this._isValidVerticalSwipe(gestureState)) {
return (dy > 0)
? SWIPE_DOWN
: SWIPE_UP;
}
return null;
}
_isValidHorizontalSwipe(gestureState) {
const {vx, dy} = gestureState;
const {velocityThreshold, directionalOffsetThreshold} =
this.swipeConfig;
return isValidSwipe(vx, velocityThreshold, dy,
directionalOffsetThreshold);
}
_isValidVerticalSwipe(gestureState) {
const {vy, dx} = gestureState;
const {velocityThreshold, directionalOffsetThreshold} =
this.swipeConfig;
return isValidSwipe(vy, velocityThreshold, dx,
directionalOffsetThreshold);
}
render() {
return (<View {...this.props} {...this._panResponder.panHandlers}/>);
}
};
Any help is appreciated. Thank You!