I've created SVG component for all svg's. I just want to change width height with props but I couldn't figure out. I'm using icons like this now <SVGCustomIcon name="InboxMenu" />. how can I also add width height props?
custom SVG component
const icons: SVGCustomIconMap = {
InboxMenu: (
<Svg width={18} height={20} viewBox="0 0 18 20" fill="none">
<Path
d="M11.25 17.5c0 .629-.262 1.3-.73 1.77-.47.468-1.141.73-1.77.73a2.5 2.5 0 01-1.77-.73 2.563 2.563 0 01-.73-1.77h5z"
fill="#949494"
/>
.....
),
ProfileMenu: (
<Svg width={20} height={22} viewBox="0 0 20 22" fill="none">
......
),
};
const SVGCustomIcon = ({ name }: SVGCustomIconProps) => {
return <>{icons[name] ? icons[name] : null}</>;
};
export default SVGCustomIcon;
type.ts
export type SVGCustomIcon = React.SVGProps<SVGSVGElement>;
export interface SVGCustomIconMap {
[key: string]: SVGCustomIcon;
}
export interface SVGCustomIconProps {
name: string;
}
you can try this,
export interface SVGCustomIconMap {
[key: string]: any;
}
export interface SVGCustomIconProps {
name: string;
width?: number;
height?: number;
}
export type TSize = {
width?: number;
height?: number;
};
const icons: SVGCustomIconMap = {
InboxMenu: ({ width, height }: TSize) => {
return (
<>
<Svg
width={width || 18}
height={height || 20}
viewBox="0 0 18 20"
fill="none">
{/* .... your rest code .... */}
</>
);
},
ProfileMenu: ({ width, height }: TSize) => {
return (
<>
<Svg
width={width || 20}
height={height || 22}
viewBox="0 0 20 22"
fill="none">
{/* ...... your rest code ....... */}
</>
);
},
};
const SVGCustomIcon = ({ name, width, height }: SVGCustomIconProps) => {
const SvgCustom = icons?.[name] ? icons[name] : null;
if (!SvgCustom) {
return null;
}
return <SvgCustom width={width} height={height} />;
};
export default SVGCustomIcon;
//how to call
<SVGCustomIcon name="InboxMenu" width={20} height={22} />;
I would try adding a `preserveAspectRatio="none" to your svg component. I don't work in React Native a lot but I vaguely remember this issue.
ProfileMenu: (
<Svg width={20} height={22} viewBox="0 0 20 22" fill="none" preserveAspectRatio="none">
)
To change the width and height of an SVG icon in a React Native application, you can use the style prop of the Svg component.
Here is an example of how you can set the width and height of an SVG icon to 50 pixels:
Copy code
import { Svg } from 'react-native-svg';
function MyIcon() {
return (
<Svg width={50} height={50}>
{/* Icon content goes here */}
</Svg>
);
}
You can also use the style prop to set the width and height using a style object:
Copy code
import { Svg } from 'react-native-svg';
function MyIcon() {
return (
<Svg style={{ width: 50, height: 50 }}>
{/* Icon content goes here */}
</Svg>
);
}
Keep in mind that the width and height of the Svg component will determine the size of the entire icon, including any elements inside it. You may need to adjust the size of the individual elements within the icon as well to achieve the desired appearance.
Related
How to use expo vector icon in react native skia.
I want to use this icon,
<Ionicons name="md-checkmark-circle" size={32} color="green" /> in skia Canvas or Box
Thanks.
You can't use vector icons directly but you can add the icon as an SVG image like this:
import {
Canvas,
ImageSVG,
useSVG
} from "#shopify/react-native-skia";
const ImageSVGDemo = () => {
// Alternatively, you can pass an SVG URL directly
// for instance: const svg = useSVG("https://upload.wikimedia.org/wikipedia/commons/f/fd/Ghostscript_Tiger.svg");
const svg = useSVG(require("../../assets/checkmark.svg"));
return (
<Canvas style={{ flex: 1 }}>
{ svg && (
<ImageSVG
svg={svg}
x={0}
y={0}
width={256}
height={256}
/>)
}
</Canvas>
);
};
and the checkmark.svg file will be:
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1.25 16.518l-4.5-4.319 1.396-1.435 3.078 2.937 6.105-6.218 1.421 1.409-7.5 7.626z"/></svg>
I have a container that contains multiple views like this :
export default function MyComponent() {
<View *** container *** >
<View> // some stuff </View>
<View> // some stuff </View>
<ScrollView> // some stuff </ScrollView>
</View
}
The ScrollView is about 40% of the container's height, in absolute position.
What I need to do is to be able to extend it over the whole screen with a swipe up.
I tried to use some modals npm package but I can't make it work.
A few things:
From my experience, ScrollViews and FlatLists work best when they have a flex of one and are wrapped in a parent container that limits their size.
I couldnt determine if you wanted to wrap the entire screen in a GestureDector and listen to swipes or if you only wanted the ScrollView to listen for scroll events. Because you want the ScrollView to take up the entire screen, I assume you wanted to listen to onScroll events
So here's a demo I put together:
import * as React from 'react';
import {
Text,
View,
Animated,
StyleSheet,
ScrollView,
useWindowDimensions
} from 'react-native';
import Constants from 'expo-constants';
import Box from './components/Box';
import randomColors from './components/colors'
const throttleTime = 200;
// min time between scroll events (in milliseconds)
const scrollEventThrottle = 100;
// min up/down scroll distance to trigger animatino
const scrollYThrottle = 2;
export default function App() {
const scrollViewAnim = React.useRef(new Animated.Value(0)).current;
let lastY = React.useRef(0).current;
// used to throttle scroll events
let lastScrollEvent = React.useRef(Date.now()).current;
const [{ width, height }, setViewDimensions] = React.useState({});
const [isScrollingDown, setIsScrollingDown] = React.useState(false);
const [scrollViewTop, setScrollViewTop] = React.useState(400);
// scroll view is 40% of view height
const defaultHeight = height * .4;
// call onLayout on View before scrollView
const onLastViewLayout = ({nativeEvent})=>{
// combine the y position with the layout height to
// determine where to place scroll view
setScrollViewTop(nativeEvent.layout.y + nativeEvent.layout.height)
}
const onContainerLayout = ({nativeEvent})=>{
// get width and height of parent container
// using this instead of useWindowDimensions allow
// makes the scrollView scale with parentContainer size
setViewDimensions({
width:nativeEvent.layout.width,
height:nativeEvent.layout.height
})
}
//animation style
let animatedStyle = [styles.scrollView,{
height:scrollViewAnim.interpolate({
inputRange:[0,1],
outputRange:[defaultHeight,height]
}),
width:width,
top:scrollViewAnim.interpolate({
inputRange:[0,1],
outputRange:[scrollViewTop,-10]
}),
bottom:60,
left:0,
right:0
}]
const expandScrollView = ()=>{
Animated.timing(scrollViewAnim,{
toValue:1,
duration:200,
useNativeDriver:false
}).start()
}
const shrinkScrollView = ()=>{
Animated.timing(scrollViewAnim,{
toValue:0,
duration:200,
useNativeDriver:false
}).start()
}
const onScroll=(e)=>{
// throttling by time between scroll activations
if(Date.now() - lastScrollEvent <scrollEventThrottle ){
console.log('throttling!')
return
}
lastScrollEvent = Date.now()
// destructure event object
const {nativeEvent:{contentOffset:{x,y}}} = e;
const isAtTop = y <= 0
const isPullingTop = lastY <= 0 && y <= 0
let yDiff = y - lastY
let hasMajorDiff = Math.abs(yDiff) > scrollYThrottle
// throttle if isnt pulling top and scroll dist is small
if(!hasMajorDiff && !isPullingTop ){
return
}
const hasScrolledDown = yDiff > 0
const hasScrolledUp = yDiff < 0
if(hasScrolledDown){
setIsScrollingDown(true);
expandScrollView()
}
if(isAtTop || isPullingTop){
setIsScrollingDown(false)
shrinkScrollView();
}
lastY = y
}
return (
<View style={styles.container} onLayout={onContainerLayout}>
<Box color={randomColors[0]} text="Some text"/>
<Box color={ randomColors[1]} text="Some other text "/>
<View style={styles.lastView}
onLayout={onLastViewLayout}>
<Text>ScrollView Below </Text>
</View>
<Animated.View style={animatedStyle}>
<ScrollView
onScroll={onScroll}
style={{flex:1}}
>
{randomColors.map((color,i)=>
<Box color={color} height={60} text={"Item Number "+(i+1)}/>
)}
</ScrollView>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
// justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
},
scrollView:{
// position:'absolute',
position:'absolute',
marginVertical:10,
height:'40%',
backgroundColor:'lightgray'
},
lastView:{
alignItems:'center',
paddingVertical:5,
borderBottomWidth:1,
borderTopWidth:1
}
});
The result is that on downward scrolling, the scrollview expands and takes up the entire screen, and shrinks when the user scrolls to the top.
Edit : I found that simply grabbing the y position and the height of the view directly before the scroll view made it easy to calculate where the position the ScrollView, allowing for the ScrollView to be positioned absolute all the time.
Here is a very basic example of how to use FlatList (similar to ScrollView) and allow for the scrolling behavior you are wanting:
import React from "react";
import {Text,View} from "react-native";
const App = () => {
const myData = {//everything you want rendered in flatlist}
const renderSomeStuff = () => {
return (
<View>
<Text> Some Stuff </Text>
</View>
)
};
const renderOtherStuff = () => {
return (
<View>
<Text> Other Stuff </Text>
</View>
);
};
return (
<View>
<FlatList
data={myData}
keyExtractor={(item) => `${item.id}`}
showsVerticalScrollIndicator
ListHeaderComponent={
<View>
{renderSomeStuff()}
{renderOtherStuff()}
</View>
}
renderItem={({ item }) => (
<View>
<Text>{item}</Text>
</View>
)}
ListFooterComponent={
<View></View>
}
/>
</View>
);
};
export default App;
I have some SVGs in my React native .66.1 app, and I've set up react-native-svg and react-native-svg-transformer and have set it up correctly. I can import my SVGs as components, and it all works as expected.
However, I don't know how to dynamically set the fill based on an expression in my app. For instance, I can set the gradient in my SVG, but I can't override the color on the state change.
How do you set it up to track a value, or how to override it?
My SVG:
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient gradientUnits="userSpaceOnUse" x1="12.99" y1="0" x2="12.99" y2="25.92" id="gradient" gradientTransform="matrix(0.707053, -0.707161, 1.001161, 1.001008, -9.106794, 9.237734)">
<stop offset="0" style="stop-color: rgb(161, 70, 183);"/>
<stop offset="1" style="stop-color: rgb(0, 38, 94);"/>
</linearGradient>
</defs>
<path d="M13.91.38a1.3 1.3 0 0 0-1.84 0L.33 12.14c-.4.4-.35.73-.29.89.06.16.25.44.8.44h1.32v10.45a2 2 0 0 0 1.92 2h5.95v-7.08h5.76v7.08h6.1c1.03 0 1.9-.93 1.9-2V13.47h1.36c.54 0 .73-.28.79-.44.06-.16.1-.5-.28-.9L13.9.39Z" fill-rule="evenodd" />
</svg>
My Drawer Screen:
<Drawer.Screen name="Home" options={{ drawerIcon: ({color, size, focused}) => <HomeIcon style={{maxWidth: 28}} fill={focused ? colors.primary.purpleMain : darkMode ? 'white' : 'black'} />}} component={Home} />
What I want:
I want the icon to be the gradient on focused, and either black or white depending on darkMode. Any ideas? I've tried setting the fill in App.tsx, but it doesn't track. This is what I tried: 'url(#gradient)' (gradient is the id of the linearGradient in my svg).
My perfect situation would be not to have to embed the linearGradient in every SVG, but to 'set' it to each solid SVG (just a 135deg purple gradient).
It's worth noting my dynamic color works if I just use regular colors and I omit the fill property inside my SVG. Also - if I use 'currentColor' inside my SVG, the color just remains blue, but I don't even see anywhere that the color blue is called.
i would suggest you to create font (ie: .ttf) format for your icons using https://icomoon.io/, after that replace Icon from GradientIcon adng home page respectively,
Here i am using react-native-vector-icons for icons
working example: Snack Expo
GradientIcon.tsx
import React, { FC } from 'react';
import {
View,
StyleSheet,
StyleProp,
ViewStyle,
ViewProps,
} from 'react-native';
import IonIcon, { IconProps } from 'react-native-vector-icons/Ionicons';
// in bare react native use. react-native-linear-gradient
import { LinearGradient } from 'expo-linear-gradient';
import MaskedView, {
MaskedViewProps,
} from '#react-native-community/masked-view';
/**
* GradientIonIconProps
*/
type GradientIonIconProps = IconProps & {
colors?: string[];
containerStyle?: StyleProp<ViewStyle>;
start?: {
x: number;
y: number;
};
end?: {
x: number;
y: number;
};
};
/**
* GradientIonIcon
*/
const GradientIonIcon: FC<GradientIonIconProps> = (props) => {
const {
children,
containerStyle,
start = { x: 0, y: 0 },
end = { x: 1, y: 0 },
colors = ['#3D7BF7', '#26CCFF'],
...rest
} = props;
return (
<MaskedView
style={containerStyle}
maskElement={
<IonIcon {...rest} style={{ color: '#fff' }} color="#fff" />
}>
<LinearGradient {...{ colors, start, end }}>
<IonIcon {...rest} color="transparent" />
</LinearGradient>
</MaskedView>
);
};
export default GradientIonIcon;
create home icon container which updates only when size ans focused props changes
IconContainer
const HomeIconContainer: React.FC<{ size: number; focused: boolean }> =
React.memo(
({ size, focused }) => {
console.log(focused);
const Icon = React.useMemo(
() => (focused ? GradientIonIcon : IonIcon),
[focused]
);
return (
<Icon
containerStyle={{ width: size, height: size }}
size={size}
name="ios-home"
color="purple"
/>
);
},
(prev, next) => prev.size === next.size && prev.focused === next.focused
);
then use this icon in your navigation options
DraerNAvigator
<Drawer.Screen
name="Home"
options={{
drawerIcon: ({ size, focused }) => (
<HomeIconContainer {...{ size, focused }} />
),
}}
component={Home}
/>
#Result
focused
unfocused
In my React Native 0.62.2 app, react-native-gesture-handler 1.6.1 and react-native-animated 10.10.1 are used to make image grid draggable. The problem is that all uploaded images grids are moving together instead of individually draggable. Here is the code for draggable image grid:
import { Col, Row, Grid } from 'react-native-easy-grid';
import { PanGestureHandler } from "react-native-gesture-handler";
import Animated from "react-native-reanimated";
import FastImage from 'react-native-fast-image';
export default DisplayImages = ({pics, deleteImage}) => { //<<==component to display images passed in from 'pics'
const translateX = new Animated.Value(0) //<<==draggable related code
const translateY = new Animated.Value(0)
const handleGesture = Animated.event([{nativeEvent: {translationX: translateX,translationY:translateY}}], { useNativeDriver: true });
//VV== code below displays single image grid
const displayImg = (img_source, width, ht, index, modalWidth, modalHt) => {
let aniStyle = {
transform:[
{ translateY : translateY },
{ translateX : translateX }
]
};
return (
<>
<PanGestureHandler onGestureEvent={handleGesture}>
<Animated.View style={aniStyle}>
<TouchableOpacity onPress={()=>{setModalDialog(index)}} >
<FastImage
source={{uri:img_source}}
resizeMode={FastImage.resizeMode.cover}
key={index}
style={{
width:width,
height:ht,
verticalAlign:0,
paddingTop:0,
}}
/>
</TouchableOpacity>
</Animated.View>
</PanGestureHandler>
)
}
//VV==code blow to display 2 images as an example
return (
<Grid style={{position:"absolute", paddingTop:0,paddingLeft:0}}>
<Row style={styles.row}>
{pics.map((item, index) => {
return (displayImg(item, screen_width*half, screen_width*half, index, screen_width, item.height*(screen_width/item.width)))
})}
</Row>
</Grid>
);
}
Here is the 2 image grids were dragged towards the left together but not only one grid moved
1 image was dragged but 2 images were moving together
The property of the gesture needs to be defined for each of the grid. It can be done by moving the declaration of the property into the definition of method displayImg:
const displayImg = (img_source, width, ht, index, modalWidth, modalHt) => {
const translateX = new Animated.Value(0) //<<==draggable related code
const translateY = new Animated.Value(0)
const handleGesture = Animated.event([{nativeEvent: {translationX: translateX,translationY:translateY}}], { useNativeDriver: true });
let aniStyle = {
transform:[
{ translateY : translateY },
{ translateX : translateX }
]
};
return (
<>
<PanGestureHandler onGestureEvent={handleGesture}>
<Animated.View style={aniStyle}>
...
After that, each grid can be dragged on its own.
I am building a chat app, using an inverted Flatlist. I add new items to the top of the list when onEndReached is called and everything works fine.
The problem is that if add items to the bottom, it instantly scrolls to the bottom of the list. That means that the user has to scroll back up to read the messages that were just added (which is terrible).
I tried to call scrollToOffset in onContentSizeChange, but this has a one-second delay where the scroll jumps back and forth.
How can I have the list behave the same way when I add items to the top AND to the bottom, by keeping the same messages on screen instead of showing the new ones?
here is demo: https://snack.expo.io/#nomi9995/flatlisttest
Solution 1:
use maintainVisibleContentPosition props for preventing auto scroll in IOS but unfortunately, it's not working on android. but here is PR for android Pull Request. before merge this PR you can patch by own from this PR
<FlatList
ref={(ref) => { this.chatFlatList = ref; }}
style={styles.flatList}
data={this.state.items}
renderItem={this._renderItem}
maintainVisibleContentPosition={{
minIndexForVisible: 0,
}}
/>
Solution 2:
I found another workaround by keep latest y offset with onScroll and also save content height before and after adding new items with onContentSizeChange and calculate the difference of content height, and set new y offset to the latest y offset + content height difference!
Here I am adding a new item on top and bottom in an inverted Flatlist.
I hope you can compare your requirements with the provided sample code :)
Full Code:
import React, {Component} from 'react';
import {
SafeAreaView,
View,
FlatList,
StyleSheet,
Text,
Button,
Platform,
UIManager,
LayoutAnimation,
} from 'react-native';
if (Platform.OS === 'android') {
if (UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
const getRandomColor = () => {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
const DATA = [
getRandomColor(),
getRandomColor(),
getRandomColor(),
getRandomColor(),
getRandomColor(),
];
export default class App extends Component {
scrollValue = 0;
append = true;
state = {
data: DATA,
};
addItem = (top) => {
const {data} = this.state;
let newData;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
if (top) {
newData = [...data, getRandomColor()];
this.setState({data: newData});
} else {
newData = [getRandomColor(), ...data];
this.setState({data: newData});
}
};
shouldComponentUpdate() {
return this.scrollValue === 0 || this.append;
}
onScrollBeginDrag = () => {
this.append = true;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({});
};
render() {
const {data} = this.state;
return (
<SafeAreaView style={styles.container}>
<Button title="ADD ON TOP" onPress={() => this.addItem(true)} />
<FlatList
data={data}
onScrollBeginDrag={this.onScrollBeginDrag}
renderItem={({item}) => <Item item={item} />}
keyExtractor={(item) => item}
inverted
onScroll={(e) => {
this.append = false;
this.scrollValue = e.nativeEvent.contentOffset.y;
}}
/>
<Button title="ADD ON BOTTOM" onPress={() => this.addItem(false)} />
</SafeAreaView>
);
}
}
function Item({item}) {
return (
<View style={[styles.item, {backgroundColor: item}]}>
<Text style={styles.title}>{item}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
height: 100,
},
title: {
fontSize: 32,
},
});
This is one year late, but this works fine:
<FlatList
inverted
initialScrollIndex={1}
{...}
/>
Since inverted renders flatlist but with inverted: 1, thus you need to pass 1 to initialScrollIndex so that it scrolls to bottom in normal list and to top in the inverted one
Have you tried using keyExtractor?
It may help react avoid re-render, so try use unique keys for each item.
you can read more about it here: https://reactnative.dev/docs/flatlist#keyextractor