Skip to main content

Marker View

Shows marker view and point annotations, including an interactive marker with sliders, switch, counter, text input, and pressable button to verify complex touch interactions inside a MarkerView.

import React from 'react';
import {
Button,
Pressable,
StyleSheet,
Switch,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import { Slider } from '@rneui/base';
import Mapbox from '@rnmapbox/maps';

import Bubble from '../common/Bubble';

const styles = StyleSheet.create({
touchableContainer: { borderColor: 'black', borderWidth: 1.0, width: 60 },
touchable: {
backgroundColor: 'blue',
width: 40,
height: 40,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
},
touchableText: {
color: 'white',
fontWeight: 'bold',
},
matchParent: { flex: 1 },
interactiveCard: {
backgroundColor: 'white',
borderRadius: 12,
padding: 12,
width: 200,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
borderWidth: 1,
borderColor: '#ddd',
},
cardTitle: {
fontWeight: 'bold',
fontSize: 13,
marginBottom: 8,
color: '#333',
},
sliderLabel: {
fontSize: 11,
color: '#666',
marginBottom: 2,
},
sliderValue: {
fontSize: 11,
fontWeight: '600',
color: '#333',
textAlign: 'right',
},
sliderRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 4,
},
switchRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginVertical: 4,
},
switchLabel: {
fontSize: 11,
color: '#666',
},
counterRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginVertical: 6,
},
counterButton: {
backgroundColor: '#4A90D9',
width: 30,
height: 30,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
},
counterButtonText: {
color: 'white',
fontWeight: 'bold',
fontSize: 16,
},
counterValue: {
fontSize: 16,
fontWeight: 'bold',
marginHorizontal: 16,
minWidth: 24,
textAlign: 'center',
},
textInput: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 6,
paddingHorizontal: 8,
paddingVertical: 4,
fontSize: 11,
marginTop: 4,
backgroundColor: '#fafafa',
},
pressableButton: {
marginTop: 8,
backgroundColor: '#4A90D9',
borderRadius: 6,
paddingVertical: 6,
alignItems: 'center',
},
pressableButtonPressed: {
backgroundColor: '#2E6DB4',
},
pressableText: {
color: 'white',
fontWeight: '600',
fontSize: 12,
},
colorPreview: {
height: 20,
borderRadius: 4,
marginTop: 4,
borderWidth: 1,
borderColor: '#ccc',
},
divider: {
height: 1,
backgroundColor: '#eee',
marginVertical: 6,
},
});

const AnnotationContent = ({ title }: { title: string }) => (
<View style={styles.touchableContainer} collapsable={false}>
<Text>{title}</Text>
<TouchableOpacity style={styles.touchable}>
<Text style={styles.touchableText}>Btn</Text>
</TouchableOpacity>
</View>
);

/**
* An interactive MarkerView with slider, switch, counter, text input, and
* pressable button — useful for verifying that complex touch interactions
* work correctly inside a MarkerView.
*/
const InteractiveMarkerContent = () => {
const [sliderValue, setSliderValue] = React.useState(0.5);
const [opacity, setOpacity] = React.useState(1.0);
const [toggleOn, setToggleOn] = React.useState(false);
const [counter, setCounter] = React.useState(0);
const [note, setNote] = React.useState('');
const [pressCount, setPressCount] = React.useState(0);

const hue = Math.round(sliderValue * 360);
const bgColor = `hsla(${hue}, 70%, 55%, ${opacity})`;

return (
<View style={styles.interactiveCard} collapsable={false}>
<Text style={styles.cardTitle}>Interactive Marker</Text>

{/* Slider: Hue */}
<View style={styles.sliderRow}>
<Text style={styles.sliderLabel}>Hue</Text>
<Text style={styles.sliderValue}>{hue}°</Text>
</View>
<Slider
value={sliderValue}
onValueChange={setSliderValue}
minimumValue={0}
maximumValue={1}
step={0.01}
thumbStyle={{ width: 18, height: 18 }}
trackStyle={{ height: 4 }}
minimumTrackTintColor="#4A90D9"
maximumTrackTintColor="#ccc"
/>

{/* Slider: Opacity */}
<View style={styles.sliderRow}>
<Text style={styles.sliderLabel}>Opacity</Text>
<Text style={styles.sliderValue}>{opacity.toFixed(2)}</Text>
</View>
<Slider
value={opacity}
onValueChange={setOpacity}
minimumValue={0}
maximumValue={1}
step={0.05}
thumbStyle={{ width: 18, height: 18 }}
trackStyle={{ height: 4 }}
minimumTrackTintColor="#E8913A"
maximumTrackTintColor="#ccc"
/>

{/* Color preview */}
<View style={[styles.colorPreview, { backgroundColor: bgColor }]} />

<View style={styles.divider} />

{/* Switch */}
<View style={styles.switchRow}>
<Text style={styles.switchLabel}>
Toggle: {toggleOn ? 'ON' : 'OFF'}
</Text>
<Switch value={toggleOn} onValueChange={setToggleOn} />
</View>

<View style={styles.divider} />

{/* Counter */}
<View style={styles.counterRow}>
<TouchableOpacity
style={styles.counterButton}
onPress={() => setCounter((c) => c - 1)}
>
<Text style={styles.counterButtonText}></Text>
</TouchableOpacity>
<Text style={styles.counterValue}>{counter}</Text>
<TouchableOpacity
style={styles.counterButton}
onPress={() => setCounter((c) => c + 1)}
>
<Text style={styles.counterButtonText}>+</Text>
</TouchableOpacity>
</View>

<View style={styles.divider} />

{/* Text input */}
<Text style={styles.sliderLabel}>Note</Text>
<TextInput
style={styles.textInput}
value={note}
onChangeText={setNote}
placeholder="Type something…"
placeholderTextColor="#aaa"
/>

{/* Pressable button */}
<Pressable
style={({ pressed }) => [
styles.pressableButton,
pressed && styles.pressableButtonPressed,
]}
onPress={() => setPressCount((c) => c + 1)}
>
<Text style={styles.pressableText}>
Pressed {pressCount} time{pressCount !== 1 ? 's' : ''}
</Text>
</Pressable>
</View>
);
};

const INITIAL_COORDINATES: [number, number][] = [
[-73.99155, 40.73581],
[-73.99155, 40.73681],
[-73.98955, 40.73581],
];

const ShowMarkerView = () => {
const [pointList, setPointList] =
React.useState<GeoJSON.Position[]>(INITIAL_COORDINATES);
const [allowOverlapWithPuck, setAllowOverlapWithPuck] =
React.useState<boolean>(false);

const onPressMap = (e: GeoJSON.Feature) => {
const geometry = e.geometry as GeoJSON.Point;
setPointList((pl) => [...pl, geometry.coordinates]);
};

return (
<>
<Button
title={
allowOverlapWithPuck
? 'allowOverlapWithPuck true'
: 'allowOverlapWithPuck false'
}
onPress={() => setAllowOverlapWithPuck((prev) => !prev)}
/>
<Mapbox.MapView onPress={onPressMap} style={styles.matchParent}>
<Mapbox.Camera
defaultSettings={{
zoomLevel: 16,
centerCoordinate: pointList[0],
}}
/>

<Mapbox.PointAnnotation coordinate={pointList[1]!} id="pt-ann">
<AnnotationContent title={'this is a point annotation'} />
</Mapbox.PointAnnotation>

<Mapbox.MarkerView
coordinate={pointList[0]!}
allowOverlapWithPuck={allowOverlapWithPuck}
>
<AnnotationContent title={'this is a marker view'} />
</Mapbox.MarkerView>

<Mapbox.MarkerView
coordinate={INITIAL_COORDINATES[2]!}
allowOverlap
allowOverlapWithPuck={allowOverlapWithPuck}
>
<InteractiveMarkerContent />
</Mapbox.MarkerView>

{pointList.slice(3).map((coordinate, index) => (
<Mapbox.PointAnnotation
coordinate={coordinate}
id={`pt-ann-${index}`}
key={`pt-ann-${index}`}
>
<AnnotationContent title={'this is a point annotation'} />
</Mapbox.PointAnnotation>
))}

<Mapbox.NativeUserLocation />
</Mapbox.MapView>

<Bubble>
<Text>Tap on map to add a point annotation</Text>
</Bubble>
</>
);
};

export default ShowMarkerView;


MarkerView.png}