React Native:不同的样式应用于方向变化
Posted
技术标签:
【中文标题】React Native:不同的样式应用于方向变化【英文标题】:React Native: Different styles applied on orientation change 【发布时间】:2018-05-20 21:12:02 【问题描述】:我正在开发一个 React Native 应用程序,将其作为原生应用程序部署在 ios 和 android(以及 Windows,如果可能的话)上。
问题是我们希望布局是不同的取决于屏幕尺寸及其方向。
我创建了一些返回 styles 对象 的函数,并在每个组件渲染的函数上调用,因此我可以在应用程序启动时应用不同的样式,但如果方向(或屏幕大小) 应用程序初始化后发生更改,它们不会重新计算或重新应用。
我已将侦听器添加到顶部渲染,因此它会在方向更改时更新其状态(并强制为应用程序的其余部分进行渲染),但子组件不会重新渲染(因为事实上,它们没有改变)。
所以,我的问题是:我怎样才能让样式根据屏幕尺寸和方向完全不同,就像 CSS 媒体一样查询(即时呈现)?
我已经尝试过react-native-responsive 模块,但运气不佳。
谢谢!
【问题讨论】:
***.com/a/64559463/9444013 试试这个 【参考方案1】:可以在[这里][1]找到解决方案。
应用程序的方向从纵向到横向,反之亦然,这听起来很容易,但当方向更改时必须更改视图时,在 react native 中可能会很棘手。换句话说,可以通过考虑这两个步骤来为两个方向定义不同的视图。
从 React Native 导入维度
import Dimensions from 'react-native';
识别当前方向并相应地渲染视图
/**
* Returns true if the screen is in portrait mode
*/
const isPortrait = () =>
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
;
/**
* Returns true of the screen is in landscape mode
*/
const isLandscape = () =>
const dim = Dimensions.get('screen');
return dim.width >= dim.height;
;
了解方向何时改变以相应地改变视图
// Event Listener for orientation changes
Dimensions.addEventListener('change', () =>
this.setState(
orientation: Platform.isPortrait() ? 'portrait' : 'landscape'
);
);
组装所有部件
import React from 'react';
import
StyleSheet,
Text,
Dimensions,
View
from 'react-native';
export default class App extends React.Component
constructor()
super();
/**
* Returns true if the screen is in portrait mode
*/
const isPortrait = () =>
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
;
this.state =
orientation: isPortrait() ? 'portrait' : 'landscape'
;
// Event Listener for orientation changes
Dimensions.addEventListener('change', () =>
this.setState(
orientation: isPortrait() ? 'portrait' : 'landscape'
);
);
render()
if (this.state.orientation === 'portrait')
return (
//Render View to be displayed in portrait mode
);
else
return (
//Render View to be displayed in landscape mode
);
由于为查看方向变化而定义的事件使用此命令“this.setState()”,因此该方法会自动再次调用“render()”,因此我们不必担心再次渲染它,一切都已经处理好了。
【讨论】:
请将代码作为文本发布在答案中,而不是图像。您可以使用 Ctrl + K 或编辑器顶部的
按钮将代码格式化为代码块。
谢谢@FeiXiang。我已经做出了改变。
如果设备的宽度大于高度,则此解决方案将无法使用。
这也适用于网络吗?它适用于世博会吗?他们似乎使用不同的 API 来获取尺寸:docs.expo.io/versions/latest/sdk/screen-orientation/…【参考方案2】:
这是@Mridul Tripathi 作为可重复使用的钩子的答案:
// useOrientation.tsx
import useEffect, useState from 'react';
import Dimensions from 'react-native';
/**
* Returns true if the screen is in portrait mode
*/
const isPortrait = () =>
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
;
/**
* A React Hook which updates when the orientation changes
* @returns whether the user is in 'PORTRAIT' or 'LANDSCAPE'
*/
export function useOrientation(): 'PORTRAIT' | 'LANDSCAPE'
// State to hold the connection status
const [orientation, setOrientation] = useState<'PORTRAIT' | 'LANDSCAPE'>(
isPortrait() ? 'PORTRAIT' : 'LANDSCAPE',
);
useEffect(() =>
const callback = () => setOrientation(isPortrait() ? 'PORTRAIT' : 'LANDSCAPE');
Dimensions.addEventListener('change', callback);
return () =>
Dimensions.removeEventListener('change', callback);
;
, []);
return orientation;
然后您可以使用以下方式使用它:
import useOrientation from './useOrientation';
export const MyScreen = () =>
const orientation = useOrientation();
return (
<View style=color: orientation === 'PORTRAIT' ? 'red' : 'blue' />
);
【讨论】:
您可能希望将[]
添加为useEffect
的第二个参数,否则效果挂钩将无限期运行。
如果我想在没有组件的情况下使用方向怎么办?有什么解决办法吗
NB: const dim = Dimensions.get('screen');应该是 const dim = Dimensions.get('window');
@AzeezatRaheem 不一定。 window
是可见应用程序窗口的大小。 screen
是设备屏幕的大小。 src【参考方案3】:
您可以使用onLayout
属性:
export default class Test extends Component
constructor(props)
super(props);
this.state =
screen: Dimensions.get('window'),
;
getOrientation()
if (this.state.screen.width > this.state.screen.height)
return 'LANDSCAPE';
else
return 'PORTRAIT';
getStyle()
if (this.getOrientation() === 'LANDSCAPE')
return landscapeStyles;
else
return portraitStyles;
onLayout()
this.setState(screen: Dimensions.get('window'));
render()
return (
<View style=this.getStyle().container onLayout = this.onLayout.bind(this)>
</View>
);
const portraitStyles = StyleSheet.create(
...
);
const landscapeStyles = StyleSheet.create(
...
);
【讨论】:
【参考方案4】:终于,我做到了。不知道它可能带来的性能问题,但它们应该不是问题,因为它只需要调整大小或改变方向。
我创建了一个全局控制器,其中有一个函数接收组件(容器、视图)并向其添加事件侦听器:
const getScreenInfo = () =>
const dim = Dimensions.get('window');
return dim;
const bindScreenDimensionsUpdate = (component) =>
Dimensions.addEventListener('change', () =>
try
component.setState(
orientation: isPortrait() ? 'portrait' : 'landscape',
screenWidth: getScreenInfo().width,
screenHeight: getScreenInfo().height
);
catch(e)
// Fail silently
);
这样,当方向或窗口大小发生变化时,我会强制重新渲染组件。
然后,在每个组件构造函数上:
import ScreenMetrics from './globalFunctionContainer';
export default class UserList extends Component
constructor(props)
super(props);
this.state = ;
ScreenMetrics.bindScreenDimensionsUpdate(this);
这样,每次调整窗口大小或改变方向时都会重新渲染。
您应该注意,但是,这必须应用于我们想要监听方向变化的每个组件,因为如果父容器更新但state
(或props
) 的孩子不会更新,它们不会被重新渲染,所以如果我们有一棵大的孩子树在听它,这可能会导致性能下降。
希望它对某人有所帮助!
【讨论】:
你可能有内存泄漏,事件监听器没有被移除?也可能会影响性能,因为当它们随着时间的推移未被删除时,可能会调用许多回调。 是的,正如我在回答中已经解释的那样,这可能会导致性能下降,具体取决于附加到侦听器的项目数量。但是,如果您删除了侦听器,它不会在您下次更改方向时更新 UI,因此删除它们也没有用。 在组件卸载时移除监听器很有用。如果你不这样做,你最终会耗尽内存。【参考方案5】:我制作了一个超轻的组件来解决这个问题。 https://www.npmjs.com/package/rn-orientation-view
组件在方向改变时重新渲染它的内容。 例如,您可以传递 LandscapeStyles 和 PortraitStyles 以不同方式显示这些方向。 适用于 iOS 和 Android。 它很容易使用。看看吧。
【讨论】:
【参考方案6】:我遇到了同样的问题。方向改变后布局没有改变。 然后我明白了一个简单的想法 - 布局应该取决于应该在渲染函数内部计算的屏幕宽度,即
getScreen = () =>
return Dimensions.get('screen');
render ()
return (
<View style= width: this.getScreen().width >
// your code
</View>
);
在这种情况下,宽度将在渲染时计算。
【讨论】:
【参考方案7】:React Native 还有useWindowDimensions
钩子,可以返回设备的宽度和高度。
有了这个,您可以通过比较宽度和高度轻松检查设备是“纵向”还是“横向”。
查看更多here
【讨论】:
【参考方案8】:** 我将此逻辑用于横向和纵向逻辑。** ** 如果我首先在横向启动我的应用程序,我将获得设备的真实高度。并相应地管理标题的高度。**
const [deviceOrientation, setDeviceOrientation] = useState( Dimensions.get('window').width < Dimensions.get('window').height ? 'portrait' : 'landscape' ); const [deviceHeight, setDeviceHeight] = useState( Dimensions.get('window').width < Dimensions.get('window').height ? Dimensions.get('window').height : Dimensions.get('window').width ); useEffect(() => const setDeviceHeightAsOrientation = () => if (Dimensions.get('window').width < Dimensions.get('window').height) setDeviceHeight(Dimensions.get('window').height); else setDeviceHeight(Dimensions.get('window').width); ; Dimensions.addEventListener('change', setDeviceHeightAsOrientation); return () => //cleanup work Dimensions.removeEventListener('change', setDeviceHeightAsOrientation); ; ); useEffect(() => const deviceOrientation = () => if (Dimensions.get('window').width < Dimensions.get('window').height) setDeviceOrientation('portrait'); else setDeviceOrientation('landscape'); ; Dimensions.addEventListener('change', deviceOrientation); return () => //cleanup work Dimensions.removeEventListener('change', deviceOrientation); ; ); console.log(deviceHeight); if (deviceOrientation === 'landscape') return ( <View style=[styles.header, height: 60, paddingTop: 10 ]> <TitleText>props.title</TitleText> </View> ); else return ( <View style=[ styles.header, height: deviceHeight >= 812 ? 90 : 60, paddingTop: deviceHeight >= 812 ? 36 : 10 ]> <TitleText>props.title</TitleText> </View> );
【讨论】:
【参考方案9】:到目前为止,我在这个库上取得了最大的成功:https://github.com/axilis/react-native-responsive-layout 它可以满足您的要求以及更多功能。简单的组件实现几乎没有像上面一些更复杂的答案那样的逻辑。我的项目是通过 RNW 使用电话、平板电脑、和网络 - 并且实施完美无缺。此外,在调整浏览器大小时,它是真正的响应式,而不仅仅是在初始渲染时 - 处理手机方向的变化完美无缺。
示例代码(将任何组件作为块的子级):
<Grid>
<Section> /* Light blue */
<Block xsSize="1/1" smSize="1/2" />
<Block xsSize="1/1" smSize="1/2" />
<Block xsSize="1/1" smSize="1/2" />
</Section>
<Section> /* Dark blue */
<Block size="1/1" smSize="1/2" />
<Block size="1/1" smSize="1/2" />
<Block size="1/1" smSize="1/2" />
<Block size="1/1" smSize="1/2" />
<Block size="1/1" smSize="1/2" />
</Section>
</Grid>
给出这个:
【讨论】:
【参考方案10】:我为我的 expo SDK36 项目编写了一个 HoC 解决方案,它支持方向更改并基于 ScreenOrientation.Orientation
值传递 props.orientation
。
import React, Component from 'react';
import ScreenOrientation from 'expo';
export default function withOrientation(Component)
class DetectOrientation extends React.Component
constructor(props)
super(props);
this.state =
orientation: '',
;
this.listener = this.listener.bind(this);
UNSAFE_componentWillMount()
this.subscription = ScreenOrientation.addOrientationChangeListener(this.listener);
componentWillUnmount()
ScreenOrientation.removeOrientationChangeListener(this.subscription);
listener(changeEvent)
const orientationInfo = changeEvent;
this.setState(
orientation: orientationInfo.orientation.split('_')[0],
);
async componentDidMount()
await this.detectOrientation();
async detectOrientation()
const orientation = await ScreenOrientation.getOrientationAsync();
this.setState(
orientation: orientation.split('_')[0],
);
render()
return (
<Component
...this.props
...this.state
onLayout=this.detectOrientation
/>
);
return (props) => <DetectOrientation ...props />;
【讨论】:
【参考方案11】:为了实现更高性能的集成,我将以下内容用作我的每个反应导航屏幕的超类:
export default class BaseScreen extends Component
constructor(props)
super(props)
const height, width = Dimensions.get('screen')
// use this to avoid setState errors on unmount
this._isMounted = false
this.state =
screen:
orientation: width < height,
height: height,
width: width
componentDidMount()
this._isMounted = true
Dimensions.addEventListener('change', () => this.updateScreen())
componentWillUnmount()
this._isMounted = false
Dimensions.removeEventListener('change', () => this.updateScreen())
updateScreen = () =>
const height, width = Dimensions.get('screen')
if (this._isMounted)
this.setState(
screen:
orientation: width < height,
width: width, height: height
)
将任何根组件设置为从该组件扩展,然后将屏幕状态从继承的根组件传递给您的叶/哑组件。
此外,为了避免增加性能开销,请更改样式对象而不是添加更多组件:
const TextObject = ( title ) => (
<View style=[styles.main, screen.orientation ? styles.column : styles.row]>
<Text style=[styles.text, screen.width > 600 ? fontSize: 14 : null ]>title</Text>
</View>
)
const styles = StyleSheet.create(
column:
flexDirection: 'column'
,
row:
flexDirection: 'row'
,
main:
justifyContent: 'flex-start'
,
text:
fontSize: 10
我希望这对将来的任何人都有帮助,并且您会发现它在开销方面非常理想。
【讨论】:
【参考方案12】:我正在使用styled-components
,这就是我在方向更改时重新渲染 UI 的方式。
import React, useState from 'react';
import View from 'react-native';
import ThemeProvider from 'styled-components';
import appTheme from 'constants/appTheme';
const App = () =>
// Re-Layout on orientation change
const [theme, setTheme] = useState(appTheme.getTheme());
const onLayout = () =>
setTheme(appTheme.getTheme());
return (
<ThemeProvider theme=theme>
<View onLayout=onLayout/>
/* Components */
</ThemeProvider>
);
export default App;
即使您不使用styled-components
,您也可以创建一个状态并在onLayout
上更新它以重新渲染 UI。
【讨论】:
【参考方案13】:这是我的解决方案:
const CheckOrient = () =>
console.log('screenHeight:' + Dimensions.get('screen').height + ', screenWidth: ' + Dimensions.get('screen').width);
return ( <
View onLayout =
() => CheckOrient()
>
............
<
/View>
【讨论】:
以上是关于React Native:不同的样式应用于方向变化的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 styled-components 将样式应用于 React Native 中的自定义组件?
react-native-web 更新:TypeError:无法读取未定义的属性“样式”