How to disable animations when the user scrolls (react-native-reanimated 2) - react-native

Here's a weekly mini calendar, that turns into a monthly mini calendar component.
When it turns from weekly to monthly we have some entering/exiting animations
So far so good.
Problem:
The problem is, that those animations (being entering/exiting animations) also take place while the user is scrolling.
As you can see in the gif, animations play when I scroll horizontally, which isn't what I want, I only want animations when the component changes from weekly to monthly (expands/collapses)
Code:
import Animated, {
FadeInDown,
FadeInUp,
SlideOutUp,
SlideOutDown,
} from 'react-native-reanimated';
const MiniCalendarItem = () => {
let animationEnter;
let animationExit;
if (this.props.itemRepresents === ITEM_REPRESENTS.MONTH) {
if (this.dayIsPartOfCurrentWeek(day)) {
animationEnter = FadeInUp;
} else {
animationEnter = FadeInUp.delay((weekIndex * 150)).duration(350)
}
animationExit = SlideOutDown.duration(400);
} else {
animationEnter = FadeInDown.duration(500);
animationExit = SlideOutUp.duration(400);
}
return (
<Animated.View
entering={animationEnter}
exiting={animationExit}
key={`dayData_${dayProps.id}`}
>
{...}
</Animated.View>
);
};
and here's the parent:
renderItem = () => {
return (
<MiniCalendarItem
animationsEnabled
key={itemKey}
mode={mode}
itemRepresents={visible ? ITEM_REPRESENTS.MONTH : ITEM_REPRESENTS.WEEK}
/>
)
}
}
Essentially the parent is a ScrollView (not a FlatList)
Question:
How can I stop react-native-reanimated#2 from playing any animations and when is it a good time to do that.
I added a animationsEnabled prop, but ideally I'd love to feed it with an Animated.Value(true) object. I'm just not sure how to conditionally disable animations based on that prop, from within the MiniCalendarItem.

Related

show block toolbar programmatically gutenberg

I am creating a block with InnerBlocks component.
If no content added to the InnerBlocks (and even with content in fact) it is very difficult to popup the block toolbar
I would like to add an iconbutton on top corner that will show the block floating toolbar
How can I tell the .block-editor-block-contextual-toolbar to show?
I don't see any method of the .wp-block in the inspector that would do that and the documentation of Block Controls: Block Toolbar and Settings Sidebar https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar/ is quite basic
Many thanks
You can use useSelect() to determine if there are any blocks present in the InnerBlocks component:
import { useSelect } from '#wordpress/data';
const hasInnerBlocks = useSelect((select) => (
select('core/block-editor').getBlock(clientId).innerBlocks.length > 0
), [clientId]);
Then you can use hasInnerBlocks to conditionally render whatever you'd like within the edit function:
{ !!hasInnerBlocks && (
<BlockControls group="block">
<ToolbarGroup
// Toolbar group settings here
/>
</BlockControls>
) }
Try to use same code structure among the edit and save methods. The RIchText need to be waraped inside div.
<div>
<RichText.Content
className={ `sticky-note-${ props.attributes.alignment }` }
style={ {
fontSize: props.attributes.fontSize,backgroundColor: props.attributes.color,
} }
tagName="p"
value={ props.attributes.content }/>
</div>
Example
I created this example to illustrate your situation.
import { InnerBlocks, BlockControls } from '#wordpress/block-editor';
// ...
edit: () => {
const blockProps = {
// your own props
};
return (
<div { ...blockProps }>
<BlockControls>
// your controls
</BlockControls>
<InnerBlocks />
</div>
);
}
Problem
For the BlockControls to decide whether or not it should appear, it needs to get some context from its parent which your own props don't have.
Solution:
Use the block props instead for the parent of BlockControls.
Steps:
Import useBlockProps from #wordpress/block-editor:
import { InnerBlocks, BlockControls, useBlockProps } from '#wordpress/block-editor';
Pass your own props as an argument to useBlockProps():
const blockProps = useBlockProps({
// your own props
});
Result
import { InnerBlocks, BlockControls, useBlockProps } from '#wordpress/block-editor';
// ...
edit: () => {
const blockProps = useBlockProps({
// your own props
});
return (
<div { ...blockProps }>
<BlockControls>
// your controls
</BlockControls>
<InnerBlocks />
</div>
);
}
Links
I hope that helped.
My answer is based on Wordpress's official Block Editor Handbook:
https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar/#block-toolbar
https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/
https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#block-wrapper-props

Flatlist visible position

I'm trying to render an curved vertical list like this iOS component: https://github.com/makotokw/CocoaWZYCircularTableView
That component (written in Obj-c) iterates the visible cells when laying them out, and sets the frame (i.e. indent) using asin.
I know in React Native I can set the leftMargin style in the renderItem callback, but I can't figure out how to get the on-screen index of the item - all I have is the index into the source data. And also, at that point, I don't think I have access to the absolute position.
Any ideas?
The function you are looking for is
onViewableItemsChanged.
You can use it with viewabilityConfig which provides us with
minimumViewTime,viewAreaCoveragePercentThreshold,waitForInteraction
which can be set accordingly
const VIEWABILITY_CONFIG = {
minimumViewTime: 3000,
viewAreaCoveragePercentThreshold: 100,
waitForInteraction: true,
};
_onViewableItemsChanged = (info: {
changed: Array<{
key: string,
isViewable: boolean,
item: any,
index: ?number,
section?: any,
}>
}
){
//here you can have the index which is visible to you
}
<FlatList
renderItem={this.renderItem}
data={this.state.data}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={VIEWABILITY_CONFIG}
/>
Thanks for both answers.
What I have ended up doing is deriving the visible items using the scroll offset of the list. This is simple because the list items all have the same height.
I do this in the onScroll handler, and at that point I calculate the horizontal offset for each item (and I use leftMargin / rightMargin to render this). It's not perfect, but it does give me an elliptical list.
_handleScroll = (event) => {
const topItemIndex = Math.floor(event.nativeEvent.contentOffset.y / LIST_ITEM_HEIGHT);
const topItemSpare = LIST_ITEM_HEIGHT-(event.nativeEvent.contentOffset.y % LIST_ITEM_HEIGHT);
const positionFromEllipseTop = (forIndex-topItemIndex)*LIST_ITEM_HEIGHT+topItemSpare;
const positionFromOrigin = Math.floor(Math.abs(yRadius - positionFromEllipseTop));
const angle = Math.asin(positionFromOrigin / yRadius);
if (orientation === 'Left') {
marginLeft = 0;
marginRight = ((xRadius * Math.cos(angle)))-LIST_ITEM_HEIGHT;
alignSelf = 'flex-end';
}
else if (orientation === 'Right') {
marginLeft = (xRadius * Math.cos(angle))-LIST_ITEM_HEIGHT;
marginRight = 0;
alignSelf = 'flex-start';
}
}
React-native's FlatList component has a prop called onLayout. You can get the position of the component on screen with this prop.
onLayout
Invoked on mount and layout changes with:
{nativeEvent: { layout: {x, y, width, height}}}
This event is fired immediately once the layout has been calculated,
but the new layout may not yet be reflected on the screen at the time
the event is received, especially if a layout animation is in
progress.

React DnD change div style only when dragging

I am implementing the drag and drop mechanic using react-dnd library, but I find it hard to style my drop targets. I want to show the user which drop target is available to drop on, but using the isOver and canDrop will only style the item that is currently being hovered on.
If I use the !isOver value, all the divs are being styled, without even dragging any of the elements.
How can I style the drop targets only when the dragging of an element happens?
This is my code so far, for a #DropTarget:
import React from 'react';
import {DropTarget} from 'react-dnd';
import {ItemTypes} from './Constants';
const target = {
drop(props, monitor, component){
// console.log("Dropped on", props.id);
},
canDrop(props, monitor, component){
var cardColumn = monitor.getItem().column;
var targetColumn = props.column;
return false; // still testing styling when only an element is being dragged on the page
}
};
#DropTarget(ItemTypes.CARD, target, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver({shallow: true}),
canDrop: monitor.canDrop(),
}))
class CardList extends React.Component{
constructor(props){
super(props);
this.addClass = this.addClass.bind(this);
}
addClass(){
const {isOver, canDrop} = this.props;
if(isOver && canDrop){
return "willDrop"; // green background for .card-list
}
if(isOver && !canDrop){
return "noDrop"; // red background for .card-list
}
if(!isOver && !canDrop){
return ""; // will style all the backgrounds in a color, but not when dragging
}
}
render(){
const {connectDropTarget} = this.props;
return connectDropTarget(
<div class={"card-list col-xl-12 col-lg-12 col-md-12 col-sm-12 col-xs-12 " + this.addClass()} id={this.props.id}>
{this.props.children}
</div>
);
}
}
export default CardList;
Is there a way to get the isDragging value when an element is being dragged on the page, since this is the only possibility to obtain what I want.
Thanks!
Both isOver and canDrop implicitly do the isDragging check, per http://react-dnd.github.io/react-dnd/docs-drop-target-monitor.html - note that they only return true if a drag operation is in progress. Therefore, if you want to style drop targets such that only when something that can be dragged is being dragged, then I think you need another case in your addClass() function to handle that, like this:
addClass(){
const {isOver, canDrop} = this.props;
if(isOver && canDrop){
return "willDrop"; // green background for .card-list
}
if(isOver && !canDrop){
return "noDrop"; // red background for .card-list
}
if(!isOver && canDrop){
return ""; // THIS BLOCK WILL EXECUTE IF SOMETHING IS BEING DRAGGED THAT *COULD* BE DROPPED HERE
}
}
And I don't think you want the !isOver && !canDrop block - this will execute even when nothing is being dragged at all.

ReactNative: How to measure height of Text Input after programatic change

React Native has documentation for AutoExpandingTextInput: https://facebook.github.io/react-native/docs/textinput.html
The Problem: When the content of the AutoExpandingTextInput is changed programmatically the height never changes.
For example:
componentWillReceiveProps(props) {
this.setState({
richText: this._addHighlights(props.richText)
});
}
//
<AutoExpandingTextInput ref={component => this._text = component}>
{this.state.richText}
</AutoExpandingTextInput>
Say, for example. the user hits a button that adds a link to the text that wraps to the next line; in this case, the AutoExpandingTextInput never expands, because the height only is measured & changed on the onChange event of the TextInput.
I need some work around to get the content height when no onChange is triggered --- or less ideally, a way to programmatically trigger an onChange to the TextInput.
Are there any solutions????
No need to use the AutoExpandingTextInput plugin any more. The functionality you need is supported (sort of) in react-native now and will resize with a programatic update. Try something like this:
_heightChange(event) {
let height = event.nativeEvent.contentSize.height;
if (height < _minHeight) {
height = _minHeight;
} else if (height > _maxHeight) {
height = _maxHeight;
}
if (height !== this.state.height) {
this.setState({height: height});
}
}
render() {
return (
<TextInput
{...this.props}
multiline={true}
onContentSizeChange={this._heightChange.bind(this)}
/>
)
}

Is it possible to change transitions in react native navigator?

I have 3 different react native components and I am using the Navigator to navigate between them. In my first view I define the navigator:
View 1
<Navigator
ref="nav"
renderScene={#renderScene}
initialRoute={#renderContent(I18n.t("Incidents"))}
configureScene={ ->
transition = Navigator.SceneConfigs.HorizontalSwipeJump
transition.gestures = null
transition
}
/>
As you can see the transition is HorizontalSwipeJump.
View 2
#props.navigator.push
component: IncidentScreen
incidentId: incident.id
sceneConfig: -> Navigator.SceneConfigs.FloatFromBottomAndroid
As you can see, I am trying move into view #3 using FloatFromBottomAndroid, however, it's not working.
By looking at the source code for RN I can see that the navigator.push method get's the animation from the props:
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
this.props.configureScene(route),
]);
So what can I do?
Thanks a lot.
You have to go digging into the react-native source here for the list of SceneConfigs, but here's the current list as of writing:
PushFromRight
FloatFromRight
FloatFromLeft
FloatFromBottom
FloatFromBottomAndroid
FadeAndroid
HorizontalSwipeJump
HorizontalSwipeJumpFromRight
VerticalUpSwipeJump
VerticalDownSwipeJump
Example usage:
<Navigator
configureScene={(route) => {
if (someCondition) {
return Navigator.SceneConfigs.HorizontalSwipeJump;
} else {
return Navigator.SceneConfigs.PushFromRight;
}
}}
/>
Ok, I figure it out. I was missing this part in View 1:
configureScene={ (route) ->
if route.sceneConfig
route.sceneConfig
else
transition = Navigator.SceneConfigs.HorizontalSwipeJump
transition.gestures = null
transition
}
If anyone is still looking at this, you can push without animation by just reseting the routes to what you want them to end up being. This is assuming that you don't do anything special with your routes like save the forward routes or anything.
if( !shouldAnimate )
{
var routeStack = this.refs.mainNav.state.routeStack;
routeStack.push(newRoute);
this.refs.mainNav.immediatelyResetRouteStack(routeStack);
}
else
{
this.refs.mainNav.push(feature);
}
Where mainNav is the ref of my Navigator. Hope this helps.