|
| 1 | +--- |
| 2 | +title: Fit |
| 3 | +tags: [Camera, Camera#zoomTo] |
| 4 | +custom_props: |
| 5 | + example_rel_path: Camera/Fit.js |
| 6 | +custom_edit_url: https://github.com/rnmapbox/maps/tree/master/example/src/examples/Camera/Fit.js |
| 7 | +--- |
| 8 | + |
| 9 | +Change camera via imperative methods |
| 10 | + |
| 11 | +```jsx |
| 12 | +import React from 'react'; |
| 13 | +import { View, Text, ScrollView, TouchableOpacity } from 'react-native'; |
| 14 | +import { isEqual } from 'lodash'; |
| 15 | +import Mapbox from '@rnmapbox/maps'; |
| 16 | + |
| 17 | +import sheet from '../../styles/sheet'; |
| 18 | + |
| 19 | +const buildPadding = ([top, right, bottom, left] = [0, 0, 0, 0]) => { |
| 20 | + return { |
| 21 | + paddingLeft: left, |
| 22 | + paddingRight: right, |
| 23 | + paddingTop: top, |
| 24 | + paddingBottom: bottom, |
| 25 | + }; |
| 26 | +}; |
| 27 | + |
| 28 | +const houseBounds = { |
| 29 | + ne: [-74.135379, 40.795909], |
| 30 | + sw: [-74.135449, 40.795578], |
| 31 | +}; |
| 32 | + |
| 33 | +const townBounds = { |
| 34 | + ne: [-74.12641, 40.797968], |
| 35 | + sw: [-74.143727, 40.772177], |
| 36 | +}; |
| 37 | + |
| 38 | +const houseCenter = [ |
| 39 | + (houseBounds.ne[0] + houseBounds.sw[0]) / 2, |
| 40 | + (houseBounds.ne[1] + houseBounds.sw[1]) / 2, |
| 41 | +]; |
| 42 | +const townCenter = [ |
| 43 | + (townBounds.ne[0] + townBounds.sw[0]) / 2, |
| 44 | + (townBounds.ne[1] + townBounds.sw[1]) / 2, |
| 45 | +]; |
| 46 | + |
| 47 | +const paddingZero = buildPadding(); |
| 48 | +const paddingTop = buildPadding([200, 40, 40, 40]); |
| 49 | +const paddingBottom = buildPadding([40, 40, 200, 40]); |
| 50 | + |
| 51 | +class Fit extends React.Component { |
| 52 | + constructor(props) { |
| 53 | + super(props); |
| 54 | + |
| 55 | + this.state = { |
| 56 | + locationType: 'houseCenter', // houseCenter | houseBounds | townCenter | townBounds |
| 57 | + zoomLevel: 16, // number |
| 58 | + followUserLocation: false, |
| 59 | + padding: paddingZero, |
| 60 | + animationDuration: 500, |
| 61 | + |
| 62 | + // For updating the UI in this example. |
| 63 | + cachedFlyTo: undefined, // house | town |
| 64 | + cachedZoomLevel: undefined, // number |
| 65 | + }; |
| 66 | + |
| 67 | + this.camera = null; |
| 68 | + } |
| 69 | + |
| 70 | + componentDidUpdate(prevProps, prevState) { |
| 71 | + const changed = (stateKey) => { |
| 72 | + // Checking if final state is `undefined` prevents another round of zeroing out in |
| 73 | + // second `componentDidUpdate` call. |
| 74 | + return ( |
| 75 | + !isEqual(prevState[stateKey], this.state[stateKey]) && |
| 76 | + this.state[stateKey] !== undefined |
| 77 | + ); |
| 78 | + }; |
| 79 | + |
| 80 | + if (changed('followUserLocation') && this.state.followUserLocation) { |
| 81 | + this.setState({ |
| 82 | + locationType: undefined, |
| 83 | + zoomLevel: undefined, |
| 84 | + cachedFlyTo: undefined, |
| 85 | + cachedZoomLevel: undefined, |
| 86 | + }); |
| 87 | + return; |
| 88 | + } |
| 89 | + |
| 90 | + if (changed('locationType') || changed('zoomLevel') || changed('padding')) { |
| 91 | + this.setState({ |
| 92 | + cachedFlyTo: undefined, |
| 93 | + cachedZoomLevel: undefined, |
| 94 | + }); |
| 95 | + } else if (changed('cachedFlyTo') || changed('cachedZoomLevel')) { |
| 96 | + this.setState({ |
| 97 | + locationType: undefined, |
| 98 | + zoomLevel: undefined, |
| 99 | + padding: paddingZero, |
| 100 | + }); |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + renderSection = (title, buttons, fade = false) => { |
| 105 | + return ( |
| 106 | + <View style={{ paddingBottom: 5, opacity: fade ? 0.5 : 1 }}> |
| 107 | + <Text>{title}</Text> |
| 108 | + <ScrollView |
| 109 | + horizontal={true} |
| 110 | + style={{ |
| 111 | + flex: 0, |
| 112 | + flexDirection: 'row', |
| 113 | + width: '100%', |
| 114 | + paddingVertical: 10, |
| 115 | + }} |
| 116 | + > |
| 117 | + {buttons.map((button) => ( |
| 118 | + <TouchableOpacity |
| 119 | + key={button.title} |
| 120 | + style={{ |
| 121 | + flex: 0, |
| 122 | + padding: 5, |
| 123 | + marginRight: 5, |
| 124 | + backgroundColor: button.selected ? 'coral' : '#d8d8d8', |
| 125 | + borderRadius: 5, |
| 126 | + }} |
| 127 | + onPress={button.onPress} |
| 128 | + > |
| 129 | + <Text>{button.title}</Text> |
| 130 | + </TouchableOpacity> |
| 131 | + ))} |
| 132 | + </ScrollView> |
| 133 | + </View> |
| 134 | + ); |
| 135 | + }; |
| 136 | + |
| 137 | + cameraProps = () => { |
| 138 | + const { |
| 139 | + locationType, |
| 140 | + zoomLevel, |
| 141 | + followUserLocation, |
| 142 | + padding, |
| 143 | + animationDuration, |
| 144 | + } = this.state; |
| 145 | + |
| 146 | + let p = { |
| 147 | + bounds: undefined, |
| 148 | + centerCoordinate: undefined, |
| 149 | + zoomLevel: undefined, |
| 150 | + followUserLocation, |
| 151 | + padding, |
| 152 | + animationDuration, |
| 153 | + }; |
| 154 | + |
| 155 | + if (locationType === 'houseCenter') { |
| 156 | + p.centerCoordinate = houseCenter; |
| 157 | + } else if (locationType === 'houseBounds') { |
| 158 | + p.bounds = houseBounds; |
| 159 | + } else if (locationType === 'townCenter') { |
| 160 | + p.centerCoordinate = townCenter; |
| 161 | + } else if (locationType === 'townBounds') { |
| 162 | + p.bounds = townBounds; |
| 163 | + } |
| 164 | + |
| 165 | + if (zoomLevel !== undefined) { |
| 166 | + p.zoomLevel = zoomLevel; |
| 167 | + } |
| 168 | + |
| 169 | + return p; |
| 170 | + }; |
| 171 | + |
| 172 | + render() { |
| 173 | + const { |
| 174 | + locationType, |
| 175 | + zoomLevel, |
| 176 | + followUserLocation, |
| 177 | + padding, |
| 178 | + cachedFlyTo, |
| 179 | + cachedZoomLevel, |
| 180 | + } = this.state; |
| 181 | + |
| 182 | + const centerIsSet = locationType?.toLowerCase().includes('center'); |
| 183 | + |
| 184 | + const locationTypeButtons = [ |
| 185 | + ['House (center)', 'houseCenter'], |
| 186 | + ['House (bounds)', 'houseBounds'], |
| 187 | + ['Town (center)', 'townCenter'], |
| 188 | + ['Town (bounds)', 'townBounds'], |
| 189 | + ['undef', undefined], |
| 190 | + ].map((o) => { |
| 191 | + return { |
| 192 | + title: `${o[0]}`, |
| 193 | + selected: locationType === o[1], |
| 194 | + onPress: () => this.setState({ locationType: o[1] }), |
| 195 | + }; |
| 196 | + }); |
| 197 | + |
| 198 | + const zoomConfigButtons = [14, 15, 16, 17, 18, 19, 20, undefined].map( |
| 199 | + (n) => { |
| 200 | + return { |
| 201 | + title: n ? `${n}` : 'undef', |
| 202 | + selected: zoomLevel === n, |
| 203 | + onPress: () => this.setState({ zoomLevel: n }), |
| 204 | + }; |
| 205 | + }, |
| 206 | + ); |
| 207 | + |
| 208 | + const zoomToButtons = [14, 15, 16, 17, 18, 19, 20].map((n) => { |
| 209 | + return { |
| 210 | + title: `${n}`, |
| 211 | + selected: cachedZoomLevel === n, |
| 212 | + onPress: () => { |
| 213 | + this.camera.zoomTo(n, 1000); |
| 214 | + this.setState({ cachedZoomLevel: n }); |
| 215 | + }, |
| 216 | + }; |
| 217 | + }); |
| 218 | + |
| 219 | + return ( |
| 220 | + <> |
| 221 | + <Mapbox.MapView |
| 222 | + styleURL={Mapbox.StyleURL.Satellite} |
| 223 | + style={sheet.matchParent} |
| 224 | + > |
| 225 | + <Mapbox.Camera |
| 226 | + ref={(ref) => (this.camera = ref)} |
| 227 | + {...this.cameraProps()} |
| 228 | + /> |
| 229 | + <View style={{ flex: 1, ...padding }}> |
| 230 | + <View style={{ flex: 1, borderColor: 'white', borderWidth: 4 }} /> |
| 231 | + </View> |
| 232 | + </Mapbox.MapView> |
| 233 | + |
| 234 | + <ScrollView |
| 235 | + style={{ |
| 236 | + flex: 0, |
| 237 | + width: '100%', |
| 238 | + maxHeight: 350, |
| 239 | + backgroundColor: 'white', |
| 240 | + }} |
| 241 | + contentContainerStyle={{ |
| 242 | + padding: 10, |
| 243 | + paddingBottom: 20, |
| 244 | + }} |
| 245 | + > |
| 246 | + {this.renderSection('Location type', locationTypeButtons)} |
| 247 | + |
| 248 | + {this.renderSection( |
| 249 | + 'Zoom' + |
| 250 | + (centerIsSet ? '' : ' (only used if center coordinate is set)'), |
| 251 | + zoomConfigButtons, |
| 252 | + !centerIsSet, |
| 253 | + )} |
| 254 | + |
| 255 | + {this.renderSection('Follow user location', [ |
| 256 | + { |
| 257 | + title: followUserLocation ? 'Enabled' : 'Disabled', |
| 258 | + selected: followUserLocation, |
| 259 | + onPress: () => |
| 260 | + this.setState({ followUserLocation: !followUserLocation }), |
| 261 | + }, |
| 262 | + ])} |
| 263 | + |
| 264 | + {this.renderSection('Fly to (imperative)', [ |
| 265 | + { |
| 266 | + title: 'House', |
| 267 | + selected: cachedFlyTo === 'house', |
| 268 | + onPress: () => { |
| 269 | + this.camera.flyTo(houseCenter); |
| 270 | + this.setState({ cachedFlyTo: 'house' }); |
| 271 | + }, |
| 272 | + }, |
| 273 | + { |
| 274 | + title: 'Town', |
| 275 | + selected: cachedFlyTo === 'town', |
| 276 | + onPress: () => { |
| 277 | + this.camera.flyTo(townCenter); |
| 278 | + this.setState({ cachedFlyTo: 'town' }); |
| 279 | + }, |
| 280 | + }, |
| 281 | + ])} |
| 282 | + |
| 283 | + {this.renderSection('Zoom to (imperative)', zoomToButtons)} |
| 284 | + |
| 285 | + {this.renderSection('Padding', [ |
| 286 | + { |
| 287 | + title: 'None', |
| 288 | + selected: isEqual(padding, paddingZero), |
| 289 | + onPress: () => this.setState({ padding: paddingZero }), |
| 290 | + }, |
| 291 | + { |
| 292 | + title: 'Top', |
| 293 | + selected: isEqual(padding, paddingTop), |
| 294 | + onPress: () => this.setState({ padding: paddingTop }), |
| 295 | + }, |
| 296 | + { |
| 297 | + title: 'Bottom', |
| 298 | + selected: isEqual(padding, paddingBottom), |
| 299 | + onPress: () => this.setState({ padding: paddingBottom }), |
| 300 | + }, |
| 301 | + ])} |
| 302 | + </ScrollView> |
| 303 | + </> |
| 304 | + ); |
| 305 | + } |
| 306 | +} |
| 307 | + |
| 308 | +export default Fit; |
| 309 | + |
| 310 | +``` |
| 311 | +
|
| 312 | +} |
| 313 | +
|
0 commit comments