Dimensions.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. import EventEmitter from '../vendor/emitter/EventEmitter';
  12. import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter';
  13. import NativeDeviceInfo, {
  14. type DisplayMetrics,
  15. type DimensionsPayload,
  16. } from './NativeDeviceInfo';
  17. import invariant from 'invariant';
  18. type DimensionsValue = {
  19. window?: DisplayMetrics,
  20. screen?: DisplayMetrics,
  21. ...
  22. };
  23. const eventEmitter = new EventEmitter();
  24. let dimensionsInitialized = false;
  25. let dimensions: DimensionsValue;
  26. class Dimensions {
  27. /**
  28. * NOTE: `useWindowDimensions` is the preffered API for React components.
  29. *
  30. * Initial dimensions are set before `runApplication` is called so they should
  31. * be available before any other require's are run, but may be updated later.
  32. *
  33. * Note: Although dimensions are available immediately, they may change (e.g
  34. * due to device rotation) so any rendering logic or styles that depend on
  35. * these constants should try to call this function on every render, rather
  36. * than caching the value (for example, using inline styles rather than
  37. * setting a value in a `StyleSheet`).
  38. *
  39. * Example: `const {height, width} = Dimensions.get('window');`
  40. *
  41. * @param {string} dim Name of dimension as defined when calling `set`.
  42. * @returns {Object?} Value for the dimension.
  43. */
  44. static get(dim: string): Object {
  45. invariant(dimensions[dim], 'No dimension set for key ' + dim);
  46. return dimensions[dim];
  47. }
  48. /**
  49. * This should only be called from native code by sending the
  50. * didUpdateDimensions event.
  51. *
  52. * @param {object} dims Simple string-keyed object of dimensions to set
  53. */
  54. static set(dims: $ReadOnly<{[key: string]: any, ...}>): void {
  55. // We calculate the window dimensions in JS so that we don't encounter loss of
  56. // precision in transferring the dimensions (which could be non-integers) over
  57. // the bridge.
  58. let {screen, window} = dims;
  59. const {windowPhysicalPixels} = dims;
  60. if (windowPhysicalPixels) {
  61. window = {
  62. width: windowPhysicalPixels.width / windowPhysicalPixels.scale,
  63. height: windowPhysicalPixels.height / windowPhysicalPixels.scale,
  64. scale: windowPhysicalPixels.scale,
  65. fontScale: windowPhysicalPixels.fontScale,
  66. };
  67. }
  68. const {screenPhysicalPixels} = dims;
  69. if (screenPhysicalPixels) {
  70. screen = {
  71. width: screenPhysicalPixels.width / screenPhysicalPixels.scale,
  72. height: screenPhysicalPixels.height / screenPhysicalPixels.scale,
  73. scale: screenPhysicalPixels.scale,
  74. fontScale: screenPhysicalPixels.fontScale,
  75. };
  76. } else if (screen == null) {
  77. screen = window;
  78. }
  79. dimensions = {window, screen};
  80. if (dimensionsInitialized) {
  81. // Don't fire 'change' the first time the dimensions are set.
  82. eventEmitter.emit('change', dimensions);
  83. } else {
  84. dimensionsInitialized = true;
  85. }
  86. }
  87. /**
  88. * Add an event handler. Supported events:
  89. *
  90. * - `change`: Fires when a property within the `Dimensions` object changes. The argument
  91. * to the event handler is an object with `window` and `screen` properties whose values
  92. * are the same as the return values of `Dimensions.get('window')` and
  93. * `Dimensions.get('screen')`, respectively.
  94. */
  95. static addEventListener(type: 'change', handler: Function) {
  96. invariant(
  97. type === 'change',
  98. 'Trying to subscribe to unknown event: "%s"',
  99. type,
  100. );
  101. eventEmitter.addListener(type, handler);
  102. }
  103. /**
  104. * Remove an event handler.
  105. */
  106. static removeEventListener(type: 'change', handler: Function) {
  107. invariant(
  108. type === 'change',
  109. 'Trying to remove listener for unknown event: "%s"',
  110. type,
  111. );
  112. eventEmitter.removeListener(type, handler);
  113. }
  114. }
  115. let initialDims: ?$ReadOnly<{[key: string]: any, ...}> =
  116. global.nativeExtensions &&
  117. global.nativeExtensions.DeviceInfo &&
  118. global.nativeExtensions.DeviceInfo.Dimensions;
  119. if (!initialDims) {
  120. // Subscribe before calling getConstants to make sure we don't miss any updates in between.
  121. RCTDeviceEventEmitter.addListener(
  122. 'didUpdateDimensions',
  123. (update: DimensionsPayload) => {
  124. Dimensions.set(update);
  125. },
  126. );
  127. initialDims = NativeDeviceInfo.getConstants().Dimensions;
  128. }
  129. Dimensions.set(initialDims);
  130. module.exports = Dimensions;