本文手把手教你自定义一个丝滑的 TabBar,让你在面对同类需求时,游刃有余,作用如下:
本文将运用到 React Native Anima动画片少儿小猪佩奇ted API,假如你不太熟悉,可能需求先了解下,这儿有elementary几篇不错的文章:
-
React Native 动画基础
-
运用 React Native 动画进行插值
-
动画和 React Native 翻滚视图
根本结构
咱们先来看看页面根本动画片汪汪队结构:
import PagerView from "react-native-pager-view"
const langs = ["JavaScript", "Golang", "Objective-C"]
function TabBarDemo() {
return (
<View style={styles.container}>
<TabBar style={styles.tabbar} />
<PagerView
onPageScroll={onPageScroll}
onPageSelected={onPageSelected}
onPageScrollStateChanged={onPageScrollStateChanged}>
{langs.map((lang) => (
<LangPage key={lang} language={lang} />
))}
</PagerView>
</View>
)
}
Pagerelements中文翻译View
PagerView 有三个比较重要的回调:
-
onPageScroll
当以手动或编程方法切换页面时,奉告当时地点页面以及页面偏移量。 -
onPageSelected
一旦 PagerViandroidstudio安装教程ew 完结了对所选页面的导航,会触发此回调,奉告当时地点页面。 -
onPageScrollelementuiStateChanged
奉告 PagerView 的翻滚状况,dragging
、settling
或android是什么系统idle
。
以上这三个回调在 iOS 和 Andriod 渠道上的调用机遇略有差异,这动画片汪汪队使得咱们需求做一些适配作业。
LangPage
LangPage 便是一个个可切换的页面,它是非常简略的
// LangPage.tsx
export default function LangPage({ language }: LangPageProps) {
return (
<View style={styles.container}>
<Text style={styles.text}>{language}</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
alignItems: 'center',
},
})
这儿依据 react-native-pager-view Known IObjective-Cssues 的提示,将页面宽高都设置成 100%
,但如同设置成 flex: 1
也没什么问题。
TabBar
TabBar 是本文的主角,它的初始代码如下:
// TabBar.tsx
import TabBarItem from "./TabBarItem"
export default function TabBar() {
return (
<View style={[styles.tabbar, style]}>
{tabs.map((tab: string, index: number) => {
return <TabBarItem key={tab} title={tab} />
})}
</View>
)
}
它运reactnative停止维护用到的 TabBarItem 代码也很简略
TabBarItem 也称作 Tab,本文运用 Tab 指代 Tabandroid的drawable类BarItem。
// TabBarItem.tsx
export default function TabBarItem() {
return (
<Pressable onPress={onPress} style={[styles.tab, style]}>
<Text style={[styles.label, labelStyle]}>{title}</Text>
</Pressable>
)
}
现在页面长下面这个姿态
相关 TabBar 和 PagerView
PagerView 需求回调函数,而 TabBar 需求能够从 PagerView 的回调函数中获得数据。这是一个较为通用的逻辑Objective-C。
咱们封装一个可复用的 React Hook
,把 PagerView 和 TabBar 相相关的逻辑写在一同,并为它们供给各自所需的东西,譬如React+Native,为 PagerV动画片熊出没iew 供给回调,为 TabBar 供给数据。
现在它看起来比较简略,但随着本文的展开,它会变得完善起来。
// usePagerView.ts
import type { default as PagerView } from "react-native-pager-view"
export default function usePagerView() {
const pagerRef = useRef<PagerView>(null)
const setPage = useCallback((page: number, animated = true) => {
if (animated) {
pagerRef.current?.setPage(page)
} else {
pagerRef.current?.setPageWithoutAnimation(page)
}
console.log(time() + " setPage", page)
}, [])
return {
pagerRef,
setPage,
}
}
usePagerView
供给了一个 refelementary翻译 来引证 PagerView,一个函数来以编程方法切换页面。
修正 TabBarDemo,将 pagerRef
传递给 PagerView,将 setPage
传递给 TabBar。
function TabBarDemo() {
const { pagerRef, setPage } = usePagerView()
return (
<View style={styles.container}>
<TabBar tabs={langs} onTabPress={setPage} />
<PagerView ref={pagerRef}>
{langs.map((lang) => (
<LangPage key={lang} language={lang} />
))}
</PagerView>
</View>
)
}
TabBar 在调用 setPage
时,动画大放映传入 Tab 的索引。
// TabBar.tsx
export default function TabBar({ onTabPress }) {
return (
<View style={[styles.tabbar, style]}>
{tabs.map((tab: string, index: number) => {
return (
<TabBarItem key={tab} title={tab} onPress={() => onTabPress(index)} />
)
})}
</View>
)
}
现在点击 Tab 就会切换到相应的页面。
指示器
咱们需求一个指示器来表明当时正android下载安装处于哪个页面,哪个方位。它实质是对 PagerView 的 onPageScroll 事情的响应。
指示器的初始代码如下:
// TabBarIndicator.tsx
interface TabBarIndicatorProps {
style: StyleProp<ViewStyle>
scrollX: Animated.AnimatedInterpolation
}
export default function TabBarIndicator({ style, scrollX }: TabBarIndicatorProps) {
return (
<Animated.View
key={"indicator"}
style={[styles.indicator, style, { transform: [{ translateX: scrollX }] }]}
/>
)
}
const styles = StyleSheet.create({
indicator: {
position: "absolute",
left: 0,
bottom: 0,
width: 24,
height: 4,
backgroundColor: "#448AFF",
borderRadius: 2,
},
})
TabBarIobjective-c和c++的区别ndicator 的布局方法是肯定定位,然后依据 scandroid平板电脑价格rollX
的值来动态调整方位。scrollXandroidstudio安装教程
是个动画值,它映射到 onPageScroll 事情的 position
和 oandroid是什么系统ffset
特点。
供给动画值
修正 usePagerView动画头像
,让动画片猫和老鼠它供给 onPageScroll
回调函数 和动画头像 position
、offset
这两个动画值。
// usePagerView.ts
export default function usePagerView() {
const position = useRef(new Animated.Value(0)).current
const offset = useRef(new Animated.Value(0)).current
const onPageScroll = useMemo(
() =>
Animated.event<PagerViewOnPageScrollEventData>(
[
{
nativeEvent: {
position: position,
offset: offset,
},
},
],
{
listener: ({ nativeEvent: { position, offset } }) => {
console.log(time() + " onPageScroll", position, offset)
},
useNativeDriver: true,
}
),
[position, offset]
)
return {
pagerRef,
setPage,
position,
offset,
onPageScroll,
}
}
onPageScroll
没有像下面这样运用一般的函数来实现:
const onPageScroll = useCallback(
(event: NativeSyntheticEvent<PagerViewOnPageScrollEventData>) => {
position.setValue(event.nativeEvent.position)
offset.setValue(event.nativeEvent.offset)
},
[offset, position]
)
而是运用了 Animated.event()
这个辅助函数,将动画值映射到事情值上。在这儿,会把 position
和 offset
这两个动画值映射到 onPageScroll 事情的 position
和 offs动画头像et
特点上。
因为设置了 useNati动画大放映veDriandroid平板电脑价格ver
为 true
,这个映射背后并不会调用 position
或 offelementaryset
动画值的 setVaObjective-Clue
方法,而是以一种优化的方法,在 UI 主线程动画电影完结绑定。这就使得动画是同步跟随手势的。
修正 TabBarDemo,将 onPageScroll
函数挂载objective-c和c++的区别到 PagerView 的 onPageScroll 事情钩子上,将 position
和 offset
这两个动画值传递给 TaAndroidbBar。
因为 onPagelement滑板eScroll
运用了一个 Animated
事情,一般组件无法理解 Animated
相关 API,需求运用 Animated.cr动画片少儿小猪佩奇eateAnimatedComponent()
将 Pagandroidstudio安装教程erView 变成动画组objective-c和c++的区别件。
const AnimatedPagerView = Animated.createAnimatedComponent<any>(PagerView)
function TabBarDemo() {
const { onPageScroll, position, offset } = usePagerView()
return (
<View style={styles.container}>
<TabBar position={position} offset={offset} />
<AnimatedPagerView onPageScroll={onPageScroll}>
{langs.map((lang) => (
<LangPage key={lang} language={lang} />
))}
</AnimatedPagerView>
</View>
)
}
核算 scrollX
接下来便是困难的部分了。咱们需求在 TabBar 中,把 position
和 offset
映射成 scrollX
。
// TabBar.tsx
const scrollX = Animated.add(position, offset).interpolate({
inputRange,
outputRange,
})
position
是个整数,表明当elements中文翻译时页面,它的取值规模便是 Tab 索引的调集。
offset
表明页面偏移量,它的取值规模是 [0, 1]
。
Animated.add(position, offset)
的取值规模是 [0, 最大 Tab 索引]
。
scrollX
是指示器在 x 轴的偏移量,由 interpolateandroid什么意思
映射得到。
interpolate
用于将指定输入规模的数据依照elementary某种曲线映射到指定输出规模的数据上。
首要确定输入规模 inputRange
,其实便是 Tab 索引的调集。
// TabBar.tsx
const inputRange = useMemo(() => tabs.map((_, index) => index), [tabs])
接下来核算输出规模 outputRangandroid/harmonyose
,其实便是指示器在 x 轴偏移量的调集,一个 Tab动画制作软件 索引对应一个 x 轴偏移量。
// 将每个 Tab 对应的指示器 x 轴偏移量都初始化为 0
const [outputRange, setOutputRange] = useState(inputRange.map(() => 0))
// 获取指示器的宽度,这个值一般是固定的
const indicatorWidth = getIndicatorWidth(indicatorStyle)
// 寄存每个 Tab 的方位和大小
const layouts = useRef<Layout[]>([]).current
const handleTabLayout = useCallback(
(index: number, layout: Layout) => {
layouts[index] = layout
const length = layouts.filter((layout) => layout.width > 0).length
if (length !== tabs.length) {
// 等一切 Tab 都现已测量完结,再更新 `outputRange`
return
}
const range: number[] = []
for (let index = 0; index < length; index++) {
const { x, width } = layouts[index]
// 咱们期望指示器和所选 Tab 垂直居中对齐
// 那么指示器的 x 轴偏移量便是 Tab 的 center.x - 指示器的 center.x
const tabCenterX = x + width / 2
const indicatorCenterX = indicatorWidth / 2
range.push(tabCenterX - indicatorCenterX)
}
console.log("---------------onTabLayout-------------------")
setOutputRange(range)
},
[tabs, layouts, indicatorWidth]
)
注册事情
把上面的 handleTabLayout
函数注册到 Tab 的 onLaandroid下载安装yout
事情钩子上。
export default function TabBar() {
return (
<View style={[styles.tabbar, style]}>
{tabs.map((tab: string, index: number) => {
return (
<TabBarItem
key={tab}
title={tab}
onLayout={(event) => handleTabLayout(index, event.nativeEvent.layout)}
/>
)
})}
<TabBarIndicator
style={[styles.indicator, indicatorStyle]}
scrollX={scrollX}
/>
</View>
)
}
至此,指示器就能够正常作element是什么意思业了。
Tab 的平滑过渡
假定咱们并不仅仅满足于指示器,咱们期望选中页面和未选中页面的 Tab 在款式上有所区别。
根本实现
首要,咱们需求让 TabBar 知道,动画片汪汪队选中的页面element是什么意思是哪个。
修正 usePagerVAndroidiew
,供给 onPageSelected
回调函数和 page
状况变量动画电影。
// usePagerView.tsx
export default function usePagerView(initialPage = 0) {
const [activePage, setActivePage] = useState(initialPage)
const setPage = useCallback((page: number, animated = true) => {
if (animated) {
pagerRef.current?.setPage(page)
} else {
pagerRef.current?.setPageWithoutAnimation(page)
}
console.log(time() + " setPage", page)
setActivePage(page)
}, [])
const onPageSelected = useCallback((e: PagerViewOnPageSelectedEvent) => {
console.log(time() + " onPageSelected", e.nativeEvent.position)
setActivePage(e.nativeEvent.position)
}, [])
return {
setPage,
page: activePage,
onPageSelected,
}
}
上述代码中,为什么要在Element setPa动画制作软件ge
中也android什么意思调用 setActivePage
呢?
这首要是因为 onPageSelected
在两个渠道上的调用机遇不同。在 Android 渠道,elementary是什么意思onPageSelected
在 setPage
之后立刻触发。而在 iOS 渠道,调用 setPageReact+Native
之后,要等到动画结束,onPageSelected
才会被调用。
在 setPage
中调用 setActivePage
,一方面能让 TabBar 尽早知道当时选中的页面,另一方面也统了两个渠道的行为。
修正 TabBarDemo,将 onPageSelected
回调函数挂载到 PagerViewandroidstudio安装教程 的 onPageSelected 事情钩子上,将 page
状况变量传递给 TabBar。
function TabBarDemo() {
const { page, onPageSelected } = usePagerView()
return (
<View style={styles.container}>
<TabBar style={styles.tabbar} tabs={langs} page={page} />
<AnimatedPagerView style={styles.pager} onPageSelected={onPageSelected}>
{langs.map((lang) => (
<LangPage key={lang} language={lang} />
))}
</AnimatedPagerView>
</View>
)
}
修正 TabBar 代码,给选中的和未选中的 Tab 设置不同的款式。
export default function TabBar({ tabs, page }) {
return (
<View style={[styles.tabbar, style]}>
{tabs.map((tab: string, index: number) => {
const isActive = index === page
const opacity = isActive ? 1 : 0.8
const scale = isActive ? 1.2 : 1
return (
<TabBarItem
key={tab}
title={tab}
style={[tabStyle, { marginLeft: index === 0 ? 0 : spacing }]}
labelStyle={[labelStyle, { opacity, transform: [{ scale }] }]}
/>
)
})}
<TabBarIndicator scrollX={scrollX} />
</View>
)
}
现在 Tab 的过渡作用有些僵硬,这是因为没有过渡动画。
增加过渡动画
首要让 Tab 支持动画,运用 Animated.Text
替换Objective-C Text
。
export default function TabBarItem() {
return (
<Pressable style={[styles.tab, style]} onPress={onPress} onLayout={onLayout}>
<Animated.Text style={[styles.label, labelStyle]}>{title}</Animated.Text>
</Pressable>
)
}
当时选中的 Tab 和前次选中的 Tab 都是需求设置过渡动画的Element。
page
便是当elementanimation时选中的 Tab 的索引。那么android手机前次选中的 Tab 的索引便是 page
的上一个值,咱们需求在elementary是什么意思恰当的机遇把这个值记录动画头像下来。而这个机遇便是 PagerView 处于 idle
状elementui况时。
修正 usePagerView
代码,增加动画片猫和老鼠一个 is动画电影Idle
状况变量。
export default function usePagerView(initialPage = 0) {
const [activePage, setActivePage] = useState(initialPage)
const [isIdle, setIdle] = useState(true)
const setPage = useCallback(
(page: number, animated = true) => {
console.log(time() + " setPage", page)
setActivePage(page)
if (activePage !== page) {
setIdle(false)
}
},
[activePage]
)
const onPageSelected = useCallback((e: PagerViewOnPageSelectedEvent) => {
console.log(time() + " onPageSelected", e.nativeEvent.position)
setActivePage(e.nativeEvent.position)
if (Platform.OS === "ios") {
setIdle(true)
}
}, [])
const onPageScrollStateChanged = useCallback(
({ nativeEvent: { pageScrollState } }: PageScrollStateChangedNativeEvent) => {
console.log(time() + " onPageScrollStateChanged", pageScrollState)
setIdle(pageScrollState === "idle")
},
[]
)
return {
isIdle,
onPageScrollStateChanged,
}
}
当调用 setPage
切换页面时,咱们就认为 PagerView 处于非闲暇状况。
而设置为闲暇状况的机遇,则因为 Pagereactnative停止维护rView 的回调事情在不同渠道的触发机遇不同而不同。
-
在 Android 渠道,onPagandroid手机eScroll动画片猫和老鼠StateChanged 事情会在页面切换动画完结后触发androidstudio安装教程。明显,这是设置为闲暇状React+Native况的最佳机遇。
-
在 iOS 渠道,当经过编程方法,也便是调用
setPage
切换页面时,onPageScro动画专业llStelementary是什么意思ateChanged 事情并不会被触发。幸运的是,在页面切换动画完结后,onPageSelected 事情会被触发,因而 iOS 在这儿设置为闲暇状况。 -
但在 Android 渠道,onPageSelected 事情在调用
setPage
之后,会当即触发,然后播放页面切换动画。明显不是设置为动画专业闲暇状况的机遇。
现在android什么意思咱们知道 PagerViandroid是什么系统ew 是否处于 idle
状况了,让咱们来记录 page
的上一个值。
export default function TabBar({ tabs, page, idle }) {
const lastPage = useLastPage(page, idle)
}
function useLastPage(page: number, idle: boolean) {
const lastPageRef = useRef(0)
useEffect(() => {
if (idle) {
lastPageRef.current = page
}
}, [idle, page])
return lastPageRef.current
}
有了 page
和 lastPage
,能够开端编写过elements渡动画了。
// TabBar.tsx
export default function TabBar() {
const lastPage = useLastPage(page, isIdle)
return (
<View style={[styles.tabbar, style]}>
{tabs.map((tab: string, index: number) => {
const enhanced = index === page || index === lastPage
const scale = Animated.add(offset, position).interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [1, enhanced ? 1.2 : 1, 1],
extrapolate: "clamp",
})
const opacity = Animated.add(offset, position).interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [0.8, enhanced ? 1 : 0.8, 0.8],
extrapolate: "clamp",
})
return (
<TabBarItem
style={[tabStyle, { marginLeft: index === 0 ? 0 : spacing }]}
// @ts-ignore
labelStyle={[labelStyle, { opacity, transform: [{ scale }] }]}
/>
)
})}
<TabBarIndicator scrollX={scrollX} />
</View>
)
}
动画原理
咱们讲解下如下要害代码是什么意思
const enhanced = index === page || index === lastPage
const scale = Animated.add(offset, position).interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [1, enhanced ? 1.2 : 1, 1],
extrapolate: "clamp",
})
首要要明白,这段代码是在一个循动画片少儿小猪佩奇环里elements中文翻译的,每个 Tab 都有它自己的动画。
因而要确保每个 Tab 的动画都不会影响到动画片猫和老鼠其他的 Tab。
position
是个整数,表明当时页面,它的取值规模便是 Tab 索引的调集。
offset
表明页面偏移量,它的取值规模是 [0, 1]
。
Anandroid是什么手机牌子imated.ad动画d(offset, position)
的取值规模是 [0, 最大 Tab 索引]
。
inputRange: [index - 1, index, index + 1]
配合 extrapolate: "clamp"
运用,非常有意思。确保了每个 Tab 的动画都不会影响到其他的 Tab。elements
假定当时处于页面 1 lastPage === 1
,用androidstudio安装教程户点击 Tab 往页面 2 page ==android什么意思= 2
移动。动画片熊出没
那么 poelementuisition
的值是 1,offsreactnative停止维护et
的值是 0.x,它俩的和是 1.x动画电影。
1.x 射中了索引为 1 的 Tab 的 inputRange: [0, 1, 2]
,依据 outputRange: [1, 1.2, 1]
设定, Tab 的 scale 值介于 1.2 和动画制作软件 1 之间,趋向缩小。
1.x 射中了索引为 2 的 Tab 的 inputRange: [1, 2, 3]
,依据 outputRange: [1, 1.2, 1]
设定,Tab动画片熊出没 的 scelementary是什么意思ale 值介于 1 和 1.2 之间,趋向放大。
1.x 没有射中索引为 3 的 Tab 的 inputRange: [2, 3, 4]
, 1.x 在 2 的左边,因为设置了 extrapolate: "clamp"
,Tab 的 scale 值为 outputRange: [1, 1, 1]
最左边的值,也便是 1。
处理 iOS 跳页问题
现在点击 Tab,Android 能够优雅地作业了,可是 iOS 还存在一些问题。
当点击相邻reactnative停止维护的 Tab 时,iOS 也能够正常地作业,可是当点击相邻较远android的drawable类的 Tab 时,前一个 Tab 没有过渡作用,显得比较突兀。如图,在 J动画电影avaScriptelements 和 Golang 之间切换,一切正常,但在 Javaelement是什么意思Script 和 Objective-C 之间切换,就有些问题了。
查看 Android 的日志,从 JavaScript 切换到 Objective-C 时,position 会有比较好的过渡。
可是在 iOS 渠道上,从 JavaScript 切换到 Objective-动画制作软件C 时,直接跳过了 position 为 0 的状况,导致没有了过渡作用。
针对这一状况,把缺失的动画补回来便可。
// TabBar.tsx
const enhanced = index === page || index === lastPage
let scale = Animated.add(...)
let opacity = Animated.add(...)
if (Platform.OS === "ios" && Math.abs(page - lastPage) > 1 && index === lastPage) {
scale = Animated.add(offset, position).interpolate({
inputRange: [page - 1, page, page + 1],
outputRange: [1.2, 1, 1.2],
extrapolate: "clamp",
})
opacity = Animated.add(offset, position).interpolate({
inputRange: [page - 1, page, page + 1],
outputRange: [1, 0.8, 1],
extrapolate: "clamp",
})
}
处理手动切换页面的问题
以上都是经过点击 Tandroid下载安装ab,也便是编程方法,来进android/harmonyos行页面切换,但假如是手动element滑板方法,也便是拖拽页面的方法呢?
PagerView 供给了 onPageScrollStateChanged 事情来告诉页面翻滚状况 (dragging
、settling
、idle
),但在 iOS 和 Android 渠道上有所差异。iOS 只要在手动方法下,才会触发该事情,Android 在编程方法下也会触发该事情。幸运的是android平板电脑价格,Android 只要在手动方法下,才会发生 dragging
状况。
修正 usePagerView
,露elementui出 scrollreactnative停止维护State
状况。
// usePagerView.ts
export default function usePagerView(initialPage = 0) {
const [scrollState, setScrollState] = useState<PageScrollState>("idle")
const onPageScrollStateChanged = useCallback(
({ nativeEvent: { pageScrollState } }: PageScrollStateChangedNativeEvent) => {
console.log(time() + " onPageScrollStateChanged", pageScrollState)
setScrollState(pageScrollState)
setIdle(pageScrollState === "idle")
},
[]
)
}
return {
scrollState,
onPageScrollStateChanged,
}
因为渠道差异,咱们先定义什么是手动中。
-
只要经过拖拽页面进入
dragging
状况,就被判定为手动中。 -
关于 Android 渠道,只要进入
idle
状况,就被判定为非手动中。 -
关于 iOS 渠道,动画电影在接收到
idle
状况时,只要上一个Objective-C状况是settling
,才答应判定为非手动中。此外,需求将 PageView 的overdrag
特点设置为true
,以避免只要settling
没有idle
的状动画专业况android平板电脑价格。
// TabBar.tsx
function useInteractive(scrollState: "idle" | "dragging" | "settling") {
const interactiveRef = useRef(false)
const scrollStateRef = useRef<"idle" | "dragging" | "settling">(scrollState)
useEffect(() => {
scrollStateRef.current = scrollState
}, [scrollState])
if (scrollState === "dragging") {
interactiveRef.current = true
}
if (
scrollState === "idle" &&
// 避免 overdrag 时,读到过期的 idle 回调信息
(Platform.OS === "android" || scrollStateRef.current === "settling")
) {
interactiveRef.current = false
}
return interactiveRef.current
}
修正 TabBarDemo,设置 PagerView 的 overdrag
特点为 true
。
function TabBarDemo() {
return (
<View style={styles.container}>
<TabBar style={styles.tabbar} />
<AnimatedPagerView
ref={pagerRef}
style={styles.pager}
overdrag={true}
overScrollMode="always">
{langs.map((lang) => (
<LangPage key={lang} language={lang} />
))}
</AnimatedPagerView>
</View>
)
}
接下来,只需求稍稍改动下 TabBar,就能够在手动切换elementary是什么意思页面时,让 Tab 的款式平滑地过渡了。
export default function TabBar() {
const lastPage = useLastPage(page, isIdle)
const interactive = useInteractive(scrollState)
return (
<View style={[styles.tabbar, style]}>
{tabs.map((tab: string, index: number) => {
const enhanced = interactive || index === page || index === lastPage
return (<TabBarItem />)
})}
<TabBarIndicator scrollX={scrollX} />
</View>
)
}
依据前面说到的动画原理,你能分分element是什么意思出,为什么拖拽时,只要相邻的两个 Tab 才会有动画作用吗?
实现翻滚作用
当时咱们的页面较少,Tab 的数量也少,但假如 Tab 的数量比较多呢?
现在去 TabBarDemo 增加多几门言语,结果发现放不android是什么手机牌子下了。
解决办法便是让 Tabelement滑板Bar 翻滚起来,能够在 TabBar 里边放一个 ScrollView,但也能够把 Taandroid是什么手机牌子bBar 放在 ScrollView 里边。
作者的选择是把 TabBar 放在 ScrollView 里边,因为这样复用性、组合性、维护性都比较好。
现在封装一个叫 ScrollBar 的组件,其结构如下:
export default function ScrollBar({ style, page, children }) {
return (
<ScrollView
ref={scrollRef}
style={[styles.scrollbar, style]}
horizontal
onContentSizeChange={onContentSizeChange}
onLayout={onLayout}
bounces={true}
showsHorizontalScrollIndicator={false}
{...props}>
{React.cloneElement(children as any, { onTabsLayout })}
</ScrollView>
)
}
获取布局信息
上面代码,有三个回调,经过这三个回调,咱们获得了所需求的布局信息,然后就能够核算出 ScrollView 应该翻滚到的方位。
-
经过
onContentS动画制作软件izeChange
回调获得 TabBar 的宽度。 -
经过
onelement是什么意思Layout
回调elementui获得 ScrollBar 的宽度,通常和屏幕宽度共同。 -
上面的
chilandroid什么意思dren
便是 TabBar,onTabsandroid的drawable类Layout
用来获取 Tab 的方位和大小,咱们在前面的指示器的实现中现已获element翻译得过了。
略微修正下 TabBar,让它承受动画片少儿小猪佩奇 onTabsLa动画片少儿小猪佩奇yout
参数
export default function TabBar({ onTabsLayout }: TabBarProps) {
const layouts = useRef<Layout[]>([]).current
const handleTabLayout = useCallback(
(index: number, layout: Layout) => {
layouts[index] = layout
console.log("---------------onTabLayout-------------------")
setOutputRange(range)
onTabsLayout?.(layouts)
},
[onTabsLayout, tabs, layouts, indicatorWidth]
)
}
改动不大,对吧。
核算翻滚距离
咱们尽可能让选中的 Tab 居中显现,为了满足这个需求,需求核算出 ScrollView 应该翻滚到哪。
以下是核算步骤:
useEffect(() => {
if (tabLayouts.length - 1 < page || contentWidth === 0 || scrollBarWidth === 0) {
return
}
// 获得选中的 Tab 布局数据
const tabLayout = tabLayouts[page]
// 核算 Tab 中心到 ScrollBar 中心的 x 轴距离
const dx = tabLayout.x + tabLayout.width / 2 - scrollBarWidth / 2
// 核算出 ScrollView 的最大可翻滚距离,ScrollView 的可翻滚规模是 [0, maxScrollX]
const maxScrollX = contentWidth - scrollBarWidth
// 核算出 ScrollView 应该翻滚到的 x 坐标,它有必要大于等于 0 而且小于等于 maxScrollX
const x = Math.min(Math.max(0, dx), maxScrollX)
scrollRef.current?.scrollTo({ x })
}, [page, tabLayouts, contentWidth, scrollBarWidth])
组合
运用方法也比较简略,把 TabBar 嵌套在 ScrollBar 里边即可,别忘了调整 TabBaobjective-c和c++的区别r 的款式特点,让它的高度等同于 ScrollBaandroid手机r 的高度。在给 ScrollBar 指定高度时,需求设置 flexGrow: 0
,不然它会尽可能占满屏幕,这是因为 ScrollAndroidView 有一个androidstudio安装教程默许的 flex: 1
款式特点。
function TabBarDemo() {
return (
<View style={styles.container}>
<ScrollBar style={styles.scrollbar} page={page}>
<TabBar style={styles.tabbar} />
</ScrollBar>
<AnimatedPagerView ref={pagerRef} style={styles.pager}>
{langs.map((lang) => (
<LangPage key={lang} language={lang} />
))}
</AnimatedPagerView>
</View>
)
}
const styles = StyleSheet.create({
scrollbar: {
height: 48,
flexGrow: 0,
},
tabbar: {
height: "100%",
},
})
示例
这儿有一个示例,供你参阅。