Реализация действия поиска в AppBar

В сегодняшнем рецепте я покажу вам, как действие поиска можно интегрировать в верхнюю часть страницы. Я буду использовать Flutter SearchDelegateкомпонент для достижения этой цели. Он поставляется с поддержкой заполнения предложений в строке поиска, добавления элементов действий в правую часть панели поиска. Я использую библиотеку дартс english_words для заполнения списка слов.

Целевая аудитория: Новичок

Рецепт: Реализация действия поиска в AppBar с использованием мобильных приложений Flutter для Android и iOS.

Виджет фокуса: SearchDelegate

Цель: В этом рецепте я покажу вам: 1. Как заполнить отсортированный список английских слов в приложении и найти заданное слово с помощью панели поиска в верхней части приложения. 2. Как создать список предложений для панели поиска. 3. Как добавить такие действия, как «очистить», чтобы сбросить панель поиска, и фиктивное действие «микрофон» для голосового ввода.

Отсортированный список слов Искать в списке слов

Проверка в действии:

Поехали!

Шаг №0: Создайте проект Flutter Application в Android Studio.

Шаг 1. Получить зависимости. Первое, что нужно добавить english_words зависимость вpubspec.yaml чтобы иметь возможность заполнять английские слова в списке слов нашего приложения рецепта.

dependencies:
  flutter:
    sdk: flutter
  # This is needed to populate English words in app's word list.
  english_words: ^3.1.3

Шаг 2. Показать список слов Чтобы отобразить список английских слов в виде списка в приложении, первым делом нужно импортировать зависимость в main.dart файл:

import 'package:english\_words/english\_words.dart' as words;

Шаг 3. Инициализировать список слов в SearchAppBarRecipeState учебный класс. Теперь, в _SearchAppBarRecipeState class, получите список слов, как показано ниже. Убедитесь, что у вас есть структура данных списка для хранения списка слов. я использую kWords для этой цели.

class _SearchAppBarRecipeState extends State<SeachAppBarRecipe> {
  //Data structure to hold word list
  final List<String> kWords;
  
  ...

    //Initializing kWords list with data fetched from english_words library
  _SearchAppBarRecipeState()
      : kWords = List.from(Set.from(words.all)),
        super();
        
  ...
}

Шаг №4. Сортировка списка слов. Вы заметите, что в этот момент данные не отсортированы. Давайте сделаем это в алфавитном порядке, как показано ниже:

class _SearchAppBarRecipeState extends State<SeachAppBarRecipe> {
  final List<String> kWords;
  ...

  //Initializing with sorted list of english words
  _SearchAppBarRecipeState()
      : kWords = List.from(Set.from(words.all))
    ..sort(
          (w1, w2) => w1.toLowerCase().compareTo(w2.toLowerCase()),
    ),
        super();
   ...
}

Шаг № 5. Настраивать SearchDelegate для реализации Search AppBar. Делегат поиска — это место, где происходит вся магия. Он содержит два списка слов. Один список представляет собой общий список слов, переданный во время инициализации. Другой список _history содержит список слов истории. Вы можете предварительно заполнить _history список, чтобы дать основу для элементов поиска в истории. Этот список будет показан, когда пользователь нажмет на строку поиска.

class _SearchAppBarDelegate extends SearchDelegate<String> {
  //list holds the full word list
  final List<String> _words;
  
  //list holds history search words.
  final List<String> _history;

    //initialize delegate with full word list and history words
  _SearchAppBarDelegate(List<String> words)
      : _words = words,
  //pre-populated history of words
        _history = <String>['apple', 'orange', 'banana', 'watermelon'],
        super();
        
  ...
}

Шаг №6. После настройки параметров списка слов в делегате пришло время выбрать значок, который будет размещен в левой части панели поиска. В основном это меню значков предназначено для возврата к предыдущему экрану.

class _SearchAppBarDelegate extends SearchDelegate<String> {
  ...
  
    // Setting leading icon for the search bar.
    //Clicking on back arrow will take control to main page
    @override
    Widget buildLeading(BuildContext context) {
      return IconButton(
        tooltip: 'Back',
        icon: AnimatedIcon(
          icon: AnimatedIcons.menu_arrow,
          progress: transitionAnimation,
        ),
        onPressed: () {
          //Take control back to previous page
          this.close(context, null);
        },
      );
    }
        
  ...
}

Хорошо, мы получили значок для панели поиска! Что дальше ?

Шаг №7. Теперь пришло время реализовать buildResults способ показать искомый элемент. Здесь поисковый запрос this.query отобразит искомый элемент. Для простоты я добавлю только два виджета. Первый виджет Text и отобразит сообщение «===Ваш выбор слова===». Второй виджетGestureDetector у которого есть другой Text виджет как его child. Нажав на Text виджет вернет управление на предыдущую страницу вместе с this.query параметр для отображения искомого слова на главной странице.

class _SearchAppBarDelegate extends SearchDelegate<String> {
  ...
  
  //Builds page to populate search results.
  @override
  Widget buildResults(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Text('===Your Word Choice==='),
            GestureDetector(
              onTap: () {
                //Define your action when clicking on result item.
                //In this example, it simply closes the page
                this.close(context, this.query);
              },
              child: Text(
                this.query,
                style: Theme.of(context)
                    .textTheme
                    .display2
                    .copyWith(fontWeight: FontWeight.normal),
              ),
            ),
          ],
        ),
      ),
    );
  }
        
  ...
}

Шаг №8. Далее пришло время для реализации buildSuggestions метод класса SearchDelegate. Итак, что означают предложения по построению? Это список вариантов слов, которые вы увидите при вводе поискового запроса в строке поиска. Есть две части этого. Во-первых, мы должны передать список слов, которые могут отображаться в качестве предложений. Во-вторых, нам нужно реализовать виджет пользовательского интерфейса для отображения этого списка предложений.

Для части данных мы отображаем предварительно заполненные слова истории, когда термин запроса пуст. Когда пользователь начинает печатать, мы показываем все слова, которые начинаются с поисковых запросов, введенных в строку поиска. Вот и все !

class _SearchAppBarDelegate extends SearchDelegate<String> {
  ...
  
  // Suggestions list while typing search query - this.query.
  @override
  Widget buildSuggestions(BuildContext context) {
    final Iterable<String> suggestions = this.query.isEmpty
        ? _history
        : _words.where((word) => word.startsWith(query));
        
        ...
  }
        
  ...
}

Теперь очередь виджета пользовательского интерфейса. Как вы видете buildSuggestions метод возвращает Widget. Мы сделаем отдельный класс сказать _WordSuggestionList чтобы заполнить виджет списка предложений и вернуть его из buildSuggestions._WordSuggestionList будет StatelessWidget, и имеет три вещи: во-первых, список предложений. Во-вторых, поисковый запрос и последний обратный вызов говорят ValueChanged<String> onSelected. Это будет ListViewот размера количества предоставленных предложений. Каждое предложение отображается в ListTile виджет. Этот виджет покажетhistory значок только для элементов, извлеченных из предварительно заполненных слов истории. Текст для виджета будет обновляться по мере того, как пользователь вводит поисковый запрос.

class _WordSuggestionList extends StatelessWidget {
  const _WordSuggestionList({this.suggestions, this.query, this.onSelected});

  final List<String> suggestions;
  final String query;
  final ValueChanged<String> onSelected;

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme.subhead;
    return ListView.builder(
      itemCount: suggestions.length,
      itemBuilder: (BuildContext context, int i) {
        final String suggestion = suggestions[i];
        return ListTile(
          leading: query.isEmpty ? Icon(Icons.history) : Icon(null),
          // Highlight the substring that matched the query.
          title: RichText(
            text: TextSpan(
              text: suggestion.substring(0, query.length),
              style: textTheme.copyWith(fontWeight: FontWeight.bold),
              children: <TextSpan>[
                TextSpan(
                  text: suggestion.substring(query.length),
                  style: textTheme,
                ),
              ],
            ),
          ),
          onTap: () {
            onSelected(suggestion);
          },
        );
      },
    );
  }
}

Давайте подключим _WordSuggestionList обратно в buildSuggestions метод:

class _SearchAppBarDelegate extends SearchDelegate<String> {
  ...
  
  // Suggestions list while typing search query - this.query.
  @override
  Widget buildSuggestions(BuildContext context) {
    final Iterable<String> suggestions = this.query.isEmpty
        ? _history
        : _words.where((word) => word.startsWith(query));
        
    //calling wordsuggestion list    
    return _WordSuggestionList(
          query: this.query,
          suggestions: suggestions.toList(),
          onSelected: (String suggestion) {
            this.query = suggestion;
            this._history.insert(0, suggestion);
            showResults(context);
          },
  }
        
  ...
}

Шаг №9. Построение действий в SearchBar. Последний шаг — добавить меню действий в правую часть SearchBar. Я добавлю два действия меню. Одним из них является значок «Очистить», чтобы удалить поисковый запрос из панели поиска. Другой значок — это значок «микрофон», который можно улучшить, чтобы принимать голосовой ввод, что выходит за рамки этого рецепта. Однако, если вы хотите, чтобы я написал о другом рецепте приема голосового ввода, не стесняйтесь обращаться ко мне, как указано в конце этого поста.

class _SearchAppBarDelegate extends SearchDelegate<String> {
  ...
  
// Action buttons at the right of search bar.
  @override
  List<Widget> buildActions(BuildContext context) {
    return <Widget>[
      query.isNotEmpty ?
      IconButton(
        tooltip: 'Clear',
        icon: const Icon(Icons.clear),
        onPressed: () {
          query = '';
          showSuggestions(context);
        },
      ) : IconButton(
        icon: const Icon(Icons.mic),
        tooltip: 'Voice input',
        onPressed: () {
          this.query = 'TBW: Get input from voice';
        },

      ),
    ];
  }
        
  ...
}

Шаг №10. Наконец, позвоните в Search Delegate из SearchAppBarRecipeState. После создания SearchDelegate класс, вызовите его из _SearchAppBarRecipeState класс и инициализировать его в initState.

class _SearchAppBarRecipeState extends State<SeachAppBarRecipe> {
    final List<String> kWords;
    
    //Calling search delegate class
    _SearchAppBarDelegate _searchDelegate;
    ...
    
    @override
    void initState() {
       super.initState();
       //Initializing search delegate with sorted list of English words
       _searchDelegate = _SearchAppBarDelegate(kWords);
    }
    
    ...
}

Вот и все !

Полный пример кода

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart' as words;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SeachAppBarRecipe',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SeachAppBarRecipe(title: 'SeachAppBarRecipe'),
    );
  }
}

class SeachAppBarRecipe extends StatefulWidget {
  SeachAppBarRecipe({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _SearchAppBarRecipeState createState() => _SearchAppBarRecipeState();
}

class _SearchAppBarRecipeState extends State<SeachAppBarRecipe> {
  final List<String> kWords;
  _SearchAppBarDelegate _searchDelegate;

  //Initializing with sorted list of english words
  _SearchAppBarRecipeState()
      : kWords = List.from(Set.from(words.all))
    ..sort(
          (w1, w2) => w1.toLowerCase().compareTo(w2.toLowerCase()),
    ),
        super();


  @override
  void initState() {
    super.initState();
    //Initializing search delegate with sorted list of English words
    _searchDelegate = _SearchAppBarDelegate(kWords);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: Text('Word List'),
        actions: <Widget>[
          //Adding the search widget in AppBar
          IconButton(
            tooltip: 'Search',
            icon: const Icon(Icons.search),
            //Don't block the main thread
            onPressed: () {
              showSearchPage(context, _searchDelegate);
            },
          ),
        ],
      ),
      body: Scrollbar(
        //Displaying all English words in list in app's main page
        child: ListView.builder(
          itemCount: kWords.length,
          itemBuilder: (context, idx) =>
              ListTile(
                title: Text(kWords[idx]),
                onTap: () {
                  Scaffold.of(context).showSnackBar(
                      SnackBar(
                          content: Text("Click the Search action"),
                          action: SnackBarAction(
                            label: 'Search',
                            onPressed: (){
                              showSearchPage(context, _searchDelegate);
                            },
                          )
                      )
                  );
                },
              ),
        ),
      ),
    );
  }

  //Shows Search result
  void showSearchPage(BuildContext context,
      _SearchAppBarDelegate searchDelegate) async {
    final String selected = await showSearch<String>(
      context: context,
      delegate: searchDelegate,
    );

    if (selected != null) {
      Scaffold.of(context).showSnackBar(
        SnackBar(
          content: Text('Your Word Choice: $selected'),
        ),
      );
    }
  }
}

//Search delegate
class _SearchAppBarDelegate extends SearchDelegate<String> {
  final List<String> _words;
  final List<String> _history;

  _SearchAppBarDelegate(List<String> words)
      : _words = words,
  //pre-populated history of words
        _history = <String>['apple', 'orange', 'banana', 'watermelon'],
        super();

  // Setting leading icon for the search bar.
  //Clicking on back arrow will take control to main page
  @override
  Widget buildLeading(BuildContext context) {
    return IconButton(
      tooltip: 'Back',
      icon: AnimatedIcon(
        icon: AnimatedIcons.menu_arrow,
        progress: transitionAnimation,
      ),
      onPressed: () {
        //Take control back to previous page
        this.close(context, null);
      },
    );
  }

  // Builds page to populate search results.
  @override
  Widget buildResults(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Text('===Your Word Choice==='),
            GestureDetector(
              onTap: () {
                //Define your action when clicking on result item.
                //In this example, it simply closes the page
                this.close(context, this.query);
              },
              child: Text(
                this.query,
                style: Theme.of(context)
                    .textTheme
                    .display2
                    .copyWith(fontWeight: FontWeight.normal),
              ),
            ),
          ],
        ),
      ),
    );
  }

  // Suggestions list while typing search query - this.query.
  @override
  Widget buildSuggestions(BuildContext context) {
    final Iterable<String> suggestions = this.query.isEmpty
        ? _history
        : _words.where((word) => word.startsWith(query));

    return _WordSuggestionList(
      query: this.query,
      suggestions: suggestions.toList(),
      onSelected: (String suggestion) {
        this.query = suggestion;
        this._history.insert(0, suggestion);
        showResults(context);
      },
    );
  }

  // Action buttons at the right of search bar.
  @override
  List<Widget> buildActions(BuildContext context) {
    return <Widget>[
      query.isNotEmpty ?
      IconButton(
        tooltip: 'Clear',
        icon: const Icon(Icons.clear),
        onPressed: () {
          query = '';
          showSuggestions(context);
        },
      ) : IconButton(
        icon: const Icon(Icons.mic),
        tooltip: 'Voice input',
        onPressed: () {
          this.query = 'TBW: Get input from voice';
        },

      ),
    ];
  }
}

// Suggestions list widget displayed in the search page.
class _WordSuggestionList extends StatelessWidget {
  const _WordSuggestionList({this.suggestions, this.query, this.onSelected});

  final List<String> suggestions;
  final String query;
  final ValueChanged<String> onSelected;

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme.subhead;
    return ListView.builder(
      itemCount: suggestions.length,
      itemBuilder: (BuildContext context, int i) {
        final String suggestion = suggestions[i];
        return ListTile(
          leading: query.isEmpty ? Icon(Icons.history) : Icon(null),
          // Highlight the substring that matched the query.
          title: RichText(
            text: TextSpan(
              text: suggestion.substring(0, query.length),
              style: textTheme.copyWith(fontWeight: FontWeight.bold),
              children: <TextSpan>[
                TextSpan(
                  text: suggestion.substring(query.length),
                  style: textTheme,
                ),
              ],
            ),
          ),
          onTap: () {
            onSelected(suggestion);
          },
        );
      },
    );
  }
}

Репозиторий исходного кода: Исходный код рецепта доступен здесь

Использованная литература:

  1. Поиск делегата
  2. Галерея флаттера
  3. BuildActions
  4. Результаты сборки

Удачной готовки с Flutter 😃

Понравилась статья? Не нашли интересующую вас тему? Пожалуйста, оставляйте комментарии или пишите мне по электронной почте о темах, которые вы хотели бы, чтобы я написал!
Кстати, я люблю кексы и кофе 😃

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *