admin管理员组文章数量:1354474
I'm trying to implement a profile screen in Flutter similar to the Threads app.
- When scrolling down, only the
TabBar
remains fixed at the top. - When scrolling up, the
AppBar
,profile info
, andTabBar
return to their original positions and are fully visible.
The issue occurs when there is little or no content below the TabBar
- If there is no content, the screen should not scroll.
- However, in my current code, the screen scrolls up to where the
TabBar
gets fixed, even when there is not enough content.
How can I make the screen scroll only as much as the content allows, just like in the Threads app?
Demo video for my app: result
Demo video for Thread app: result
import 'package:flutter/material.dart';
import 'dart:developer' as dev;
class TestSearchScreen extends StatefulWidget {
const TestSearchScreen({super.key});
@override
State<TestSearchScreen> createState() => _TestSearchScreenState();
}
class _TestSearchScreenState extends State<TestSearchScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
_tabController.dispose();
super.dispose();
}
void _scrollListener() {
dev.log('''
[스크롤 정보]
• 현재 스크롤 위치: ${_scrollController.position.pixels}
• 최대 스크롤 범위: ${_scrollController.position.maxScrollExtent}
• 최소 스크롤 범위: ${_scrollController.position.minScrollExtent}
• 뷰포트 크기: ${_scrollController.position.viewportDimension}
• 현재 방향: ${_scrollController.position.userScrollDirection}
• 현재 활성화된 탭: ${_tabController.index}
''');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
return false;
},
child: NestedScrollView(
controller: _scrollController,
physics: const BouncingScrollPhysics(),
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
// 앱바
SliverAppBar(
title: const Text('Test Search'),
backgroundColor: Colors.white,
elevation: 0,
floating: true,
snap: true,
),
// 프로필 정보
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.all(70),
color: Colors.grey[200],
child: const Text('Profile Info Here'),
),
),
// 탭바
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
TabBar(
controller: _tabController,
tabs: const [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
),
),
),
];
},
body: TabBarView(
controller: _tabController,
physics: const NeverScrollableScrollPhysics(),
children: [
_buildTabContent(7),
_buildTabContent(5),
_buildTabContent(1),
],
),
),
),
),
);
}
Widget _buildTabContent(int itemCount) {
return LayoutBuilder(
builder: (context, constraints) {
final double itemHeight = 116.0; // 아이템 높이(100) + 마진(16)
final double filterHeight = 48.0; // 필터 높이
final double totalContentHeight =
(itemHeight * itemCount) + filterHeight;
final bool hasScrollableContent =
totalContentHeight > constraints.maxHeight;
return CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
slivers: [
// 필터
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.all(16),
color: Colors.blue[100],
child: const Text('Filter Here'),
),
),
// 아이템 리스트
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
height: 100,
margin: const EdgeInsets.all(8),
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('Item $index')),
);
},
childCount: itemCount,
),
),
// 스크롤이 불가능한 경우에도 bounce 효과를 위한 추가 공간
if (!hasScrollableContent)
SliverFillRemaining(
hasScrollBody: false,
child: Container(),
),
],
);
},
);
}
}
// 탭바를 위한 SliverPersistentHeaderDelegate
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
@override
double get minExtent => _tabBar.preferredSize.height;
@override
double get maxExtent => _tabBar.preferredSize.height;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.white,
child: _tabBar,
);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
void main() {
runApp(const MaterialApp(home: TestSearchScreen()));
}
本文标签: Implementing a Profile Screen Like Threads App in Flutter how toStack Overflow
版权声明:本文标题:Implementing a Profile Screen Like Threads App in Flutter.. how to..? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1743966259a2569941.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论