Flutter: древовидный список с анимацией

Задача: показать двухуровневый список с анимацией отображения выбора второго уровня.

Что сделаем? Отображением займутся два вложенных друг в друга ListView.builder. Отлавливает выбор модели при помощи GestureDetector и при отрисовке второго ListView.builder со списокм автомобилей, показываем только совпадающие с моделью. Также отображение моделей обертываем в AnimatedOpacity для плавного показа.

class _AddAutoState extends State<AddAuto> {
  final AddAuto_scaffoldKey = GlobalKey<ScaffoldState>();
  bool auto_list_is_loading=false;
  List ListAutoes=[];
  List FilteredListAutoes=[];
  int cur_model_select=0;
  void LoadListAutoes(context){
    TRequests req=new TRequests();
    req.request("GetListVendorsAndModels", jsonEncode({}), (List result){
      setState(() {
        ListAutoes=result;
        FilteredListAutoes=result;
        auto_list_is_loading=true;
      });
    }, (String error){
      EasyLoading.showToast(error);
    });
  }

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((_) => LoadListAutoes(context));  // эвент после того как страница отобразилась - обновим данные по пользователю
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        key: AddAuto_scaffoldKey,
        appBar: AppBar(
          iconTheme: IconThemeData(color: Colors.black),
          backgroundColor: Colors.white,
          //automaticallyImplyLeading: false, // убрать кнопку "назад"
          title: Text('Добавление автомобиля', style: TextStyle(fontFamily: 'Poppins', color: Colors.black, fontSize: 22,),),
          actions: [],
          centerTitle: false,
          elevation: 2,
        ),
        body:
        SingleChildScrollView(
          child:
          Column(
            children: [
              Visibility(
                  visible: auto_list_is_loading,
                  child:
                  Container(
                    padding: const EdgeInsets.all(8),
                    child:
                    ListView.builder(
                        shrinkWrap: true,
                        physics: NeverScrollableScrollPhysics(),
                        padding: const EdgeInsets.all(0),
                        itemCount: FilteredListAutoes.length,
                        itemBuilder: (BuildContext context, int index) {
                          return GestureDetector(
                              onTap: () {
                                setState(() {
                                  print("тыкнули по модели автомобиля..");
                                  cur_model_select=index;
                                });
                              },
                              child:
                              Column(
                                  children: [
                                    Row(
                                      mainAxisSize: MainAxisSize.max,
                                      mainAxisAlignment: MainAxisAlignment.start,
                                      children: [
                                        Container(
                                          height: 16,
                                          width: 16,
                                          child:
                                            Transform.rotate(
                                              angle: index==cur_model_select?90 * 3.14/180:0,
                                              child:
                                                IconButton (
                                                  padding: const EdgeInsets.all(0),
                                                  icon: SvgPicture.asset('lib/images/arrow-right-small.svg',height: 16,width: 16,),
                                                  onPressed: () {print('IconButton pressed ...');},
                                                ),
                                            )
                                        ),
                                        Text(
                                            FilteredListAutoes[index]["name"]+" ("+FilteredListAutoes[index]["cars"].length.toString()+")",
                                            style: TextStyle(
                                                fontWeight: index==cur_model_select?FontWeight.bold:FontWeight.w100,
                                            )
                                        )
                                      ],
                                    ),
                                    // рисуем список автомобилей
                                        AnimatedOpacity(
                                          opacity: index==cur_model_select ? 1.0 : 0.0,
                                          duration: const Duration(milliseconds: 1500),
                                          child:
                                          ListView.builder(
                                              shrinkWrap: true,
                                              physics: NeverScrollableScrollPhysics(),
                                              padding: const EdgeInsets.all(0),
                                              itemCount: FilteredListAutoes[index]["cars"].length,
                                              itemBuilder: (BuildContext context, int index2) {
                                                return
                                                  Visibility(
                                                    visible: index==cur_model_select?true:false,
                                                    child:
                                                        Padding(
                                                            padding: const EdgeInsets.only(left: 20,top: 4),
                                                            child:
                                                            GestureDetector(
                                                              onTap: () {
                                                                print("тыкнули по автомобилю.."+index2.toString());
                                                              },
                                                              child: Text(FilteredListAutoes[index]["cars"][index2]["name"]),
                                                            )
                                                        )
                                                  );
                                              }
                                          ),
                                    ),
                                    new Divider()
                                  ]
                              )
                          );
                        }
                    ),
                  )
              ),
              Visibility(
                  visible: !auto_list_is_loading,
                  child:
                  Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: const [
                        CardLoading(
                          height: 30,
                          borderRadius: BorderRadius.all(Radius.circular(15)),
                          width: 100,
                          margin: EdgeInsets.only(bottom: 10),
                        ),
                        CardLoading(
                          height: 100,
                          borderRadius: BorderRadius.all(Radius.circular(15)),
                          margin: EdgeInsets.only(bottom: 10),
                        ),
                      ]
                  )
              ),
            ],
          )
        )
    );
  }
}

Flutter: особенности отображения listview внутри виджета showdialog

А именно, проблема заключается в том, что ничего не отображается, пока listview не будет обёрнут в контейнер с указанной высотой. И это на самом деле проблема, т.к. в этом случае нельзя указать «резиновую» высоту. Обёртывание в виджет Expanded тоже не поможет. Остаётся один выход — рассчитывать высоту в случае динамического списка. Например:

            Container(
              height: EzsInfo["connectors"].length.toDouble()*72,
              width: double.maxFinite,
              child:  ListView.builder(
                  shrinkWrap: true,
                  physics: NeverScrollableScrollPhysics(),
                  padding: const EdgeInsets.all(8),
                  itemCount: EzsInfo["connectors"].length,
                  itemBuilder: (BuildContext context, int index) {
                    return Container(
                      //padding: EdgeInsets.symmetric(vertical: 10),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Row(
                              mainAxisSize: MainAxisSize.max,
                              mainAxisAlignment: MainAxisAlignment.start,
                              children: [
                                Padding(
                                  padding: const EdgeInsets.only(bottom: 0,left: 0,right: 10),
                                  child: Text(EzsInfo["connectors"][index]["station_name"],style: TextStyle(fontWeight: FontWeight.w100, fontSize: 8.0, color: Colors.black)),
                                ),
                                Container(
                                  width: 6.0,
                                  height: 6.0,
                                  decoration: BoxDecoration(
                                    color: EzsInfo["connectors"][index]["status"]==3?Colors.grey:Colors.green,
                                    shape: BoxShape.circle,
                                  ),),
                                Padding(
                                  padding: const EdgeInsets.only(bottom: 0,left: 2,right: 0),
                                  child: Text(EzsInfo["connectors"][index]["status_name"],style: TextStyle(fontWeight: FontWeight.w100, fontSize: 8.0, color: Colors.black)),
                                ),

                              ],
                            ),
                            Row(
                              children: [
                                IconButton (
                                  padding: const EdgeInsets.only(bottom: 0,left: 0,right: 0),
                                  icon: Image.asset('lib/images/'+EzsInfo["connectors"][index]["url_connector"],height: 32,),
                                  onPressed: () {print('IconButton pressed ...');},
                                ),
                                Container(
                                  alignment: Alignment.topLeft,
                                  width: 150,
                                  child: Column(
                                    children: [
                                      Align(
                                        alignment: Alignment.topLeft,
                                        child: Text(EzsInfo["connectors"][index]["connector_name"],style: TextStyle(fontSize: 12.0, color: Colors.black),textAlign: TextAlign.left,),
                                      ),
                                      Align(
                                        alignment: Alignment.topLeft,
                                        child: Text(EzsInfo["connectors"][index]["maxpower"].toString()+" кВт",style: TextStyle(fontSize: 12.0, color: Colors.black),textAlign: TextAlign.left),
                                      ),
                                    ],
                                  ),
                                ),
                                Text(EzsInfo["connectors"][index]["price"].toString()+"Р кВт*ч",style: TextStyle(fontSize: 12.0, color: Colors.black)),
                              ],
                            ),
                            new Divider(),
                          ],
                        )
                    );
                  }
              ),
            )

Результат:

Flutter: скроллинг до элемента в списке

Задача: позиционировать по нажатию кнопки список на нужном элементе списка.

Решение: используем пакет scroll_to_index:

dependencies:
  scroll_to_index : any

Код:

class _MainMenuState extends State<MainMenu> {
  final scaffoldKey = GlobalKey<ScaffoldState>();

  final scrollDirection = Axis.vertical;
  late AutoScrollController controller;

  @override
  void initState() {
    print("-инициализация класса..");
    controller = AutoScrollController(
        viewportBoundaryGetter: () =>
            Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
            suggestedRowHeight: 2000, // если большой список, и  скролл визуально идёт медленно, то это "шаг" скролла. Т.е. чем больше, тем быстрее.
            axis: scrollDirection
    );
  }

  Future _scrollToCounter(int counter) async {

    await controller.scrollToIndex(counter, duration: Duration(seconds: 1));
    controller.highlight(counter);
  }
....
...
      // список ТМЦ для инвентаризации (если есть)
      body: ListView.builder(
          physics: NeverScrollableScrollPhysics(),
          scrollDirection: scrollDirection,
          controller: controller,
          padding: const EdgeInsets.all(8),
          itemCount: globals.TMCList.length,
          itemBuilder: (BuildContext context, int index) {
            return AutoScrollTag(
              key: ValueKey(index),
              controller: controller,
              index: index,
              child: Container(
                  padding: EdgeInsets.symmetric(vertical: 10),
                  child: Column(
                    children: [
                      Text(
                        globals.TMCList[index]["name"],
                        textAlign: TextAlign.start,
                      ),
                      Align(
.....
        floatingActionButton: FloatingActionButton(
          onPressed: (){
_scrollToCounter(10);
          },

Android Studio: ListView c фотографией и текстом

Задача: разместить в виджете ListView картинку из файла + текст. Результат должен выглядеть как то так:

Решение: будем писать свой «адаптер», на входе которого будет массив из id и photo_name (имя файла картинки)

Экран list_photos.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/imgv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        tools:srcCompat="@tools:sample/avatars" />

    <TextView
        android:id="@+id/photo_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="photo_id" />

    <TextView
        android:id="@+id/photo_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="photo_name" />
</LinearLayout>

Код адаптера:


        ListView listView = findViewById(R.id.ListPhotos);
        // используем адаптер данных
        adapter=new PhotosAdapter(this,R.layout.list_photos, arrayList);
        listView.setAdapter(adapter);


    }
    // Пишем свой класс-адаптер
    private class PhotosAdapter extends ArrayAdapter<String> {
        PhotosAdapter(Context context, int textViewResourceId, ArrayList objects) {
            super(context, textViewResourceId, objects);
        }
        @NonNull
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            LayoutInflater inflater = getLayoutInflater();
            View row = inflater.inflate(R.layout.list_photos, parent, false);
            map=arrayList.get(position);
            TextView pid = (TextView) row.findViewById(R.id.photo_id);
            TextView pname = (TextView) row.findViewById(R.id.photo_name);
            File imgFile = new  File(map.get("photo_name"));
            pid.setText(map.get("photo_id"));
            Bitmap myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
            ImageView iconImageView = (ImageView) row.findViewById(R.id.imgv);
            iconImageView.setImageBitmap(myBitmap);
            return row;
        }
    }

Двухуровневый список ListView в приложении для Android

Хотите двух (много) уровневый список в приложении Android? — их есть у меня (с).

» Читать далее