شرح عمل انتقال لعناصر list من tabs مع انميشن عند النقر على اي عنصر باستخدام flutter

شرح عمل انتقال لعناصر list من tabs مع انميشن عند النقر على اي عنصر باستخدام flutter

شرح عمل انتقال لعناصر list من tabs مع انميشن عند النقر على اي عنصر باستخدام flutter

في هذا المقال سوف نقوم بعمل صفحة تحتوي على مجموعه من الاقسام الرئيسيه التي تكون بالاعلى وعند النقر على اي عنصر منهم او اي قسم بمعنى اصح سوف يتم الانتقال اليه من داخل ال list كما هو موضح بالصورة وبعدها سوف تقوم بادراج العناصر التي يحتويها هذا القسم كما ترغب او تنفيذ اي امر معين وايضا عندما تقوم بعمل scroll سوف يقارن العناصر ويعرف اي موقعك وبالتالي يجعل العنصر الذي تقف عنده واضح عن باقي العناصر كما هو موضح بالصورة الخاصه بالمقال .


The data you need to work

هذا الدرس يحتوي على هذه البيانات وهذه البيانات التي تم استخدامها يمكنك نسخ الاكواد التاليه اذا كنت ترغب بتنفيذ نفس الفكرة وبعدها قم بتغيرها كما ترغب في تطبيقك دون ادنى مشاكل .


The data you need to work

data.dart

class RappiCategory {
  final String name;
  final List<RappiProducts> products;

  const RappiCategory({required this.name, required this.products});
}

class RappiProducts {
  final String name;
  final String disc;
  final String price;
  final String img;

  const RappiProducts(
      {required this.name, required this.disc, required this.img, required this.price});
}

const String img = 'https://images.unsplash.com/photo-1676322607729-c64554d6a7a5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80';

const rappiCategories = [
  RappiCategory(
      name: 'Orange',
      products: [
        RappiProducts(
            name: 'name 2',
            disc: 'disc',
            img: img,
            price: '200'),
        RappiProducts(
            name: 'name 3',
            disc: 'disc',
            img: img,
            price: '300'),
        RappiProducts(
            name: 'name 4',
            disc: 'disc',
            img: img,
            price: '500'),
        RappiProducts(
            name: 'name 5',
            disc: 'disc',
            img: img,
            price: '100'),
      ]),
  RappiCategory(
      name: 'red',
      products: [
        RappiProducts(
            name: 'name 1',
            disc: 'disc',
            img: img,
            price: '200'),
        RappiProducts(
            name: 'name 2',
            disc: 'disc',
            img: img,
            price: '300'),
      ]),
  RappiCategory(
      name: 'black',
      products: [
        RappiProducts(
            name: 'name 1',
            disc: 'disc',
            img: img,
            price: '200'),
        RappiProducts(
            name: 'name 2',
            disc: 'disc',
            img: img,
            price: '300'),
        RappiProducts(
            name: 'name 2',
            disc: 'disc',
            img: img,
            price: '300'),
      ]),
  RappiCategory(
      name: 'white',
      products: [
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '200'),
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '300'),
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '300'),
      ]),
  RappiCategory(
      name: 'yellow',
      products: [
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '200'),
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '300'),
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '500'),
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '100'),
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '100'),
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '100'),
        RappiProducts(
            name: 'name',
            disc: 'disc',
            img: img,
            price: '100'),
      ]),
];

class RappiTabCategory{
  final RappiCategory category;
  final bool selected;
  final double offset;
  final double offsetTo;

  RappiTabCategory(
      {
        required this.category,
        required this.selected,
        required this.offset,
        required this.offsetTo,
      });

  RappiTabCategory copyWith(bool selected) {
    return RappiTabCategory(
      category: category,
      selected: selected,
      offset: offset,
      offsetTo: offsetTo,
    );
  }
}

class RappiItem {
  final RappiCategory? category;
  final RappiProducts? products;
  bool get isCategory => category != null;

  RappiItem({ this.category, this.products});
}

work code we do (Bloc statemanagemt)


هذا الجزء هو الذي يعتمد عليه كل شيئ فهذا هو الكود المستخدم والطريقة المستخدمه والتي تمت كتابتها بداخل ملف bloc حتى يكون سهل الوصول اليها ويمكنك استخدام اي state management ترغب بها اذا لم تكن تفضل ال bloc او يمكنك تنفيذها بدون اي state management .

work code we do (Bloc statemanagemt)

bloc.dart

class MainBlocCubit extends Cubit<MainBlocState> {
  MainBlocCubit() : super(MainBlocInitial());

  static MainBlocCubit get(context) => BlocProvider.of(context);
  late TabController tabController;
  List<RappiTabCategory> tabs = [];
  List<RappiItem> items = [];
  ScrollController scrollController = ScrollController();
  bool isListen = true;

  void init(TickerProvider ticker){

    tabController = TabController(
        length: rappiCategories.length,
        vsync: ticker);

    double offsetFrom = 0.0;
    double offsetTo = 0.0;

    for(int i = 0;i<rappiCategories.length;i++) {
      final category = rappiCategories[i];

      if (i>0) {
        offsetFrom += rappiCategories[i-1].products.length * prodouctHeight;
      }

      if (i<rappiCategories.length -1) {
        offsetTo = offsetFrom + rappiCategories[i+1].products.length * prodouctHeight;
      } else {
        offsetTo = double.infinity;
      }

      tabs.add(RappiTabCategory(
          category: category,
          selected: (i==0),
          offset:  categoryHeight * i + offsetFrom,
          offsetTo: offsetTo,
      ));
      items.add(RappiItem(category: category));
      for (int j = 0 ; j<category.products.length ; j++) {
        items.add(RappiItem(products: category.products[j]));
      }
    }

    scrollController.addListener(onScroll);

  }

  void onScroll() {
    if (isListen) {
      for (int i = 0; i < tabs.length; i++) {
        final tab = tabs[i];
        if (scrollController.offset >= tab.offset && scrollController.offset <= tab.offsetTo && !tab.selected) {
          changeItem(i,animationRequired: false);
          tabController.animateTo(i);
          break;
        }
      }
    }
  }

  void changeItem(int index,{bool animationRequired = true}) async {
    final selected = tabs[index];
    for (int i = 0; i < tabs.length; i++) {
      tabs[i] = tabs[i].copyWith(selected.category.name == tabs[i].category.name);
    }

    if (animationRequired){
      isListen = false;
      await scrollController.animateTo(
          selected.offset,
          duration: Duration(milliseconds: 350),
          curve: Curves.linear);
      isListen = true;
    }

    emit(ChangeItem());
  }
}

Final design Ui


الجزء الاخير وهو كود ال ui والذي يعبر عن كل ما سبق البيانات والهيكليه وتستطيع تغير التصميم والتعديل عليه لتصل الى التصميم الذي ترغب به في مشروعك والامر ليس صعب اطلاقا فكل شيئ موجود كل ما سوف تقوم به هو عملية التعديل وليس الا وبهذا تكون انتهت من تنفيذ الانميشن والتاثير بكل سهوله وبدون اي مشاكل باذن الله .

Final design Ui

ui.dart

const prodouctHeight = 100.0;
const categoryHeight = 55.0;

class ScrollSmooth extends StatefulWidget {
  const ScrollSmooth({Key? key}) : super(key: key);

  @override
  State<ScrollSmooth> createState() => _ScrollSmothState();
}

class _ScrollSmothState extends State<ScrollSmooth>
    with SingleTickerProviderStateMixin {
  late MainBlocCubit bloc;

  @override
  void initState() {
    bloc = context.read<MainBlocCubit>();
    bloc.init(this);
    super.initState();
  }

  @override
  void dispose() {
    bloc.scrollController.dispose();
    bloc.scrollController.removeListener(bloc.onScroll);
    bloc.tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<MainBlocCubit, MainBlocState>(
      builder: (context, state) {
        return Scaffold(
          body: SafeArea(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  color: Colors.grey.shade50,
                  height: 80,
                  child: TabBar(
                    onTap:
                    bloc.changeItem,
                    controller: bloc.tabController,
                    isScrollable: true,
                    indicatorWeight: 0.1,
                    tabs: bloc.tabs
                        .map((e) =>
                        RappiTabWidget(
                          category: e,
                        ))
                        .toList(),
                  ),
                ),
                Expanded(
                    child: ListView.builder(
                        itemCount: bloc.items.length,
                        controller: bloc.scrollController,
                        padding: EdgeInsets.symmetric(horizontal: 20),
                        itemBuilder: (_, index) {
                          final item = bloc.items[index];
                          if (item.isCategory) {
                            return RappiCategoryItem(categoryItem: item.category!,);
                          } else {
                            return RappiProductItem(products: item.products!,);
                          }
                        }))
              ],
            ),
          ),
        );
      },
    );
  }
}

class RappiTabWidget extends StatelessWidget {
  final RappiTabCategory category;

  const RappiTabWidget({Key? key, required this.category}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final selected = category.selected;
    return Opacity(
      opacity: selected ? 1 : 0.5,
      child: Card(
        elevation: selected ? 6 : 0,
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text(
            category.category.name,
            style: TextStyle(
              color: Colors.blue,
              fontWeight: FontWeight.bold,
              fontSize: 13,
            ),
          ),
        ),
      ),
    );
  }
}

class RappiCategoryItem extends StatelessWidget {
  final RappiCategory categoryItem;
  const RappiCategoryItem({Key? key,required this.categoryItem}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.centerLeft,
      height: categoryHeight,
      color: Colors.white,
      child: Text(
        categoryItem.name,
      style: TextStyle(
        color: Colors.blue,
        fontWeight: FontWeight.bold,
        fontSize: 16
      ),),
    );
  }
}

class RappiProductItem extends StatelessWidget {
  final RappiProducts products;
  const RappiProductItem({Key? key,required this.products}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: prodouctHeight,
      child: Card(
        elevation: 6,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12)
        ),
        child: Row(
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: CircleAvatar(
                radius: 35,
                  backgroundImage: NetworkImage(products.img)),
            ),
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(products.name),
                Text(products.disc),
                Text(products.price),
              ],
            )
          ],
        ),
      ),
    );
  }
}

تعليقات