클론 코딩을 진행하는중에 기억을 정리하기 위함.
위 영상처럼 좌우 왕복하는 탭바를 구현했던 기록을 작성한다.
class 부모 extends StatefulWidget {
class _StockFragmentState extends State<StockFragment> with SingleTickerProviderStateMixin {
late final TabController controller = TabController(length: 2, vsync: this);
int currentIndex = 0;
...
Widget get tabBar => Container(
color: color,
child: Column(
children: [
TabBar(
onTap: (newIndex) {
setState(() {
currentIndex = newIndex;
});
},
labelStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), /// 라벨(텍스트)의 스타일을 지정
labelPadding: const EdgeInsets.symmetric(vertical: 20), /// 라벨 패딩
indicatorSize: TabBarIndicatorSize.tab, /// 좌우 탭클릭시 같이 움직이는 하단 바의 사이즈 지정
indicatorColor: Colors.white, /// 움직이는 하단 바의 색상 지정
controller: controller, /// TabBar를 사용하기 위해선, TabController()가 필요함.
tabs: [
"내 주식".text.make(), /// 각 탭의 이름 (velocity_x 사용)
"오늘의 발견".text.make() /// 각 탭의 이름 (velocity_x 사용)
]),
Line()
],
),
);
}; // 부모 State
}; // 부모
탭에 코드이며, 아래 코드를 정리한다.
late final TabController controller = TabController(length: 2, vsync: this);
TabBar를 이용하기위해선 TabController()가 필수인데, 두가지(length, vsync
) 필수 파라미터를 갖는다.
length는 탭의 갯수이며, vsync는 탭바를 동기화 시키기 위함이다.
여기서 vsync에 this
를 넣으면 에러가 난다.
공식 홈페이지를 확인해보니, TickerProviderStateMixin and SingleTickerProviderStateMixin classes to obtain a suitable TickerProvider
TickerProviderStateMixin나 SingleTickerProviderStateMixin를 넣으면 된다고한다.
둘의 차이는 필요한애니메이션의 갯수로 알고있다.
class _StockFragmentState extends State<StockFragment> with TickerProviderStateMixin {
late final TabController controller = TabController(length: 2, vsync: this);
/// dispose필수
void dispose() {
controller.dispose();
super.dispose();
}
Tabbar는 controller가 필수이며, 컨트롤러에 접근하러면
TickerProviderStateMixin나 SingleTickerProviderStateMixin가 필요하다.
끝.
Flutter의 상태관리 라이브러리중 하나로,
GetxController
에서 데이터를 가져오는 방법이 몇가지 있는데, 나는 아래와 같이 진행했다.
(그 외, 상태관리 라이브러리는 학습 후 포스팅 할 예정이다)
// 상태 관리할 스크린
class SearchStockScreen extends StatefulWidget {...}
class _SearchStockScreenState extends State<SearchStockScreen> with SearchStockDataProvider {
Get.put(SearchStockData()); /// getXcontroller를 extends했기에 가능
}
---------------
// GetX controller
class SearchStockData extends GetxController {
List<SimpleStock> stocks = [];
void onInit() {
loadLocalStockJson();
super.onInit();
}
Future<void> loadLocalStockJson() async {
final jsonList =
await LocalJson.getObjectList<SimpleStock>("json/stock_item.json");
stocks.addAll(jsonList);
}
}
onInit이 진행 -> loadLocalStockJson메소드 진행 -> stocks에 json데이터 삽입. (더미데이터지만, 후에는 DB에서 값을 받아온다)
그리고 put을 이용해 GetxController
를 컨트롤러를 바인딩한다.
이제 언제든지 controller를 통해 변수와 함수에 접근이 가능해진다.
끝.
TextField 입력시 자동완성 기능에 대한 정리다.
/// 검색 위잿을 사용할 스크린
final controller = TextEditingController(); //미리 선언하여 초기화.
Scaffold(
appBar: StockSearchAppBar(controller: controller,),
...
)
/// 검색 위젯 (StockSearchAppBar)
Expanded(
child: TextFieldWithDelete(
controller: controller, //상위에서 받아온 컨트롤러 삽입
textInputAction: TextInputAction.search, //입력폼에 우측 하단의 버튼을 검색 으로 변경
texthint: "검색영역입니다.", // placeholder
onEditingComplete: (){
// 키보드 감춤(context)
},
)
)
/// initState에 추가하여 사용
Get.put(SearchStockData());
controller.addListener(() {
searchData.search(controller.text); // 위에 선언된 입력 컨트롤러의 텍스트를 검색 기능에 추가한다.
});
/// 검색 기능 - SearchStockDataProvider()
RxList<SimpleStock> autoCompleteList = <SimpleStock>[].obs;
void search(String keyword) {
if(keyword.isEmpty) {
autoCompleteList.clear();
return;
}
autoCompleteList.value =
stocks.where((element) => element.name.contains(keyword)).toList();
}
RxList와 obs가 등장하는데 이는 GetX에 실시간으로 데이터 변경를 위해 사용된다.
search매소드를 통해 받은 keyword
의 값이 없다면 자동완성 리스트를 초기화하고 끝낸다.
값이 존재한다면 Stocks(json에서 받은 리스트)에서 where
을 통해 텍스트의 값과 stocks리스트의 아이템들을 비교한다.
만약 포함되어있다면 새로운 리스트를 만든다. (필터 기능 완료)
Scaffold(
appBar: StockSearchAppBar(controller: controller,),
body : Obx(()=> searchData.autoCompleteList.isEmpty
? "대기".text.make()
: SearchAutoCompleteList(controller),
)
/// SearchAutoCompleteList()
class SearchAutoCompleteList extends StatelessWidget with SearchStockDataProvider {
final TextEditingController controller;
SearchAutoCompleteList(this.controller,{super.key});
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
final stock = searchData.autoCompleteList[index]; // SearchStockDataProvider 를 통해 Get.find()로 상태를 조회할 수 있다.
final stockName = stock.name;
return Tap(
onTap: () {
controller.clear(); // 검색된 리스트를 초기화한다.
Nav.push(StockDetailScreen(stockName: stockName,)); // 검색된 아이템을 클릭하면 상세 페이지로 이동 간단하게 이름만 넘겼다.
} ,
child: stockName.text.make().p(20),
);
},
itemCount: searchData.autoCompleteList.length, // 리스트뷰 빌더는 itemCount를 꼭 사용하자.(에러가 난다)
);
}
}
Obx또한, 실시간으로 변화를 감지해 Obx안의 상태를 변경한다.
장점은 변화를 감지하면 Obx영역만 다시 그린다.
(시연 영상 작성중)
끝.
검색 후 클릭해 상세 페이지로 이동한 주식을 텍스트 필드 아래 나열 한다.
이곳도 실시간으로 데이터의 변화가 필요함으로 Obx
를 활용한다.
class SearchHistoryStockList extends StatefulWidget {...}
class _SearchHistoryStockListState extends State<SearchHistoryStockList> with SearchStockDataProvider {
width : double.infinity,
height: searchData.searchHistoryList.isNotEmpty ? 65 : 20, // 히스토리 리스트가 비었다면 20
child: Obx(
() => ListView.builder(
itemBuilder: (context, index) {
final stockName = searchData.searchHistoryList[index];
return Container(
margin: EdgeInsets.only(right: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Tap(onTap: (){
/// 탭시 디테일 페이지로 이동한다.
Nav.push(StockDetailScreen(stockName: stockName));
}, child: stockName.text.make(), ),
/// x버튼으로 히스토리 제거 기능이다.
Tap(onTap: () {
searchData.removeHistory(stockName);
}, child: Icon(Icons.close))
],
)
.box
.withRounded(value: 6)
.color(context.appColors.roundedLayoutBackground)
.p8
.make(),
],
),
);
},
itemCount: searchData.searchHistoryList.length,
scrollDirection: Axis.horizontal,
),
),
}
/// 추가로 주식 검색후 자동완성을 통해 만들어진 아이템을 클릭하면 historyList에 추가하는 코드도 작성한다.
SearchAutoCompleteList() {
final TextEditingController controller;
SearchAutoCompleteList(this.controller,{super.key});
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
final stock = searchData.autoCompleteList[index];
final stockName = stock.name;
return Tap(
onTap: () {
controller.clear();
searchData.addHistory(stock); //추가한 부분
Nav.push(StockDetailScreen(stockName: stockName,));
} ,
child: stockName.text.make().p(20),
);
},
itemCount: searchData.autoCompleteList.length,
);
}
}
이로써 아래와 같은 로직이 만들어졌다.
검색 버튼 클릭(검색 스크린 이동) -> 텍스트 필드에서 주식 검색(자동완성 리스트 생성) -> 아이템클릭(상세 페이지 이동, 히스토리 리스트 저장, 및 뒤로가기) -> 히스토리 리스트를 기반으로 검색 리스트 생성된
작성중