背景
我们想使用scrollview实现在内容不满容器时默认高度是撑满容器的 当内容超出容器时正常滚动。 代码如下:
less
<View style={{ width: '100%', height: '100%', backgroundColor: 'pink' }}>
<ScrollView
style={{ backgroundColor: 'blue',flex:1 }}
contentContainerStyle={{ flex: 1, backgroundColor: 'yellow' }}
bounces={false}
>
{list.map((item, index) => (
<View
key={index}
style={{
height: 100,
width: '100%',
marginBottom: 20,
backgroundColor: 'red',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text>{index}</Text>
</View>
))}
</ScrollView>
</View>
发现在不满容器时的确可以撑满容器 然而当内容超出容器时 scrollview无法滚动...? why?
滚动的本质
当内容不满容器时 是不会发生滚动的
只有当容器高度固定
并且内容大于容器高度
才可以发生滚动...
ScrollView的双层设计
在 React Native 中的 ScrollView 组件采用了一种"双层"设计结构。这意味着 ScrollView 本身作为一个外层容器,内部还包含一个叫做"content container"的内层容器,而开发者直接提供的子组件则被放置在这个内层容器内。
- 外层容器(ScrollView) :这层主要管理滚动的行为,如触控事件的接收和处理、滚动条的显示等。它控制着整个可滚动视图的表现和行为。
- 内层容器(Content Container) :这是承载所有子组件的实际容器。它的大小和布局由内部内容的总大小决定,这通常是由内部子组件的尺寸和布局累加而成的。开发者可以通过 contentContainerStyle 属性来定制这个容器的样式,如对齐方式、填充、尺寸等。 所以当我们有如下代码时:
xml
<View style={{styles.father}}>
<ScrollView
style={styles.scrollview}
contentContainerStyle={styles.contentContainer}
bounces={false}
>
{list.map((item, index) => (
<View
key={index}
style={styles.child}
>
<Text>{index}</Text>
</View>
))}
</ScrollView>
</View>
实际的视图结构是这样的:
scrollview
默认是填充满父组件的。
content container
默认是包裹child内容的。
为何采用双层设计?
个人理解
这种设计使得 ScrollView 更加灵活和强大,因为它区分了滚动机制和内容布局
两部分的责任,允许开发者单独管理和调整它们。
- 内容尺寸动态性:ScrollView 的内层容器,称为 contentContainer, 用于承载所有的子组件。这种结构设计允许 ScrollView 动态计算其内容的实际大小,从而决定是否需要滚动、滚动条的展示等。此外,这种设计也让 contentContainer 的样式设置变得灵活(例如通过 contentContainerStyle 属性),这对于定制内部布局非常有用。
- 优化性能:通过将布局的责任分散到内层容器,可以更精细地控制重绘和重排(reflow)行为。内层容器的调整不必直接影响外层 ScrollView 自身的属性,这可能帮助减少不必要的计算和渲染,特别是在滚动操作中。
- 灵活的滚动处理:ScrollView 需要管理滚动输入(如触摸事件)和视觉反馈(如滚动条)。将这些与直接的内容渲染分开,有助于优化处理滚动行为的代码路径,使之与内容布局和渲染解耦。
不能滚动的原因
1:外层容器高度不定
记住 ScrollView 必须有一个确定的高度才能正常工作,因为它实际上所做的就是将一系列不确定高度的子组件装进一个确定高度的容器(通过滚动操作)。要给 ScrollView 一个确定的高度的话,要么直接给它设置高度(不建议),要么确定所有的父容器都有确定的高度。一般来说我们会给 ScrollView 设置flex: 1
以使其自动填充父容器的空余空间,但前提条件是所有的父容器本身也设置了 flex 或者指定了高度,否则就会导致无法正常滚动。
以上为官网原话。
2:内层容器被压缩导致无法滚动
当我们设置如下代码时就会导致内层容器被压缩。
css
contentContainerStyle={{ flex: 1, backgroundColor: 'yellow' }}
因为flex:1
等于flexGrow:1
, flexShrink: 1
和 flexBasis: auto
的简写。
是在表示当有剩余空间时 可以撑满容器。
当容器不够时 可以压缩内容适应容器。
所以当内容超过内层容器时 被压缩以适配内层容器 当值内层容器永远小于等于scrollview大小 所以无法滚动。
滚动的解决方案
所以在scrollview的场景下 想让其滚动 就需要在scrollview高度固定的情况下内层容器高度大于scrollview高度
。
第一步:指定scrollview高度
根据官网文档 我们可以直接通过style属性 给scrollview设置一个固定高度。
ini
style={{ backgroundColor: 'blue',height: 500 }}
然鹅 实际使用发现设置并不能生效...
具体问题react-native-scrollview-height-always-stays-static-and-does-not-change
这是因为默认scrollview的style是有flex:1
限制的。
所以我们想要调整滚动视图的大小 有两种方式
- 设置外层组件 需要在scrollview外面在包装一层父View 通过设置这个View的大小 可以实现改变scrollview的大小。
- 直接改变style的flex布局
less
flex: 1 => flexBasis: 500(期望高度), flexGrow: 0
第二步:设置content container的样式
如果child列表本身就已经超过scrollview的高度了 那么现在就已经可以滚动。
然而 有时我们需要实现在不满容器时填充容器 在超出容器时滚动。 这事可以通过设置contentContainerStyle
来实现。
css
contentContainerStyle={{ flexGrow: 1 }}
// 注意不要设置flex:1 否则会出现上面提到的内层容器被压缩导致无法滚动
flexGrow:1
内层容器默认填满 scrollview高度且内容大于容器后 不会被压缩导致不能滚动。
总结
scrollview不能滚动的本质是因为开发者没有理解scrollview的双层设计以及没有熟练掌握flex布局...