Паша, спасибо большое за твой труд. Доклад получился суперским, ничего подобного еще не видел. Собрал самые лучшие практики прямо как я и искал. Кирилл, спасибо тебе за контент и приглашенного гостя!
смотрел это видео в субботу,,,, еле сдержал себя что-бы комп не включить(выходные не для работы! (берегите глазки)). Павел спасибо,,, слушаю вас и вспоминаю зачем пошёл программистом работать (последние пару лет - как на каторгу хожу(и бабло не помогает)), видеть как человеку искренне (как мне показалось) интересно и вас заражает (жаль только не надолго).
Огромное спасибо Паше - очень приятно слушать. Кирилл - спасибо за организацию. С Payload лично мне в голову не приходил вопрос про анимацию вставки элемента. Я там в коде увидел, что если не возвращать пейлоды, то не вызывается unbind() и как-то все встало на места. Если мы возвращаем пейлоды - значит хотим мальца сами подправить. А если мы сами берем на себя такую ответственность, то и за нас перетирать вьюшку не нужно. Мне было проще понять именно так.
Супер материал, хотя и делаю немного по другому. Побольше бы так технологии разбирать, на 2часа) Было бы очень удобно с таймингами на видео, напр. в данном случае по неймингам веток. Спасибо!)
По поводу динамического определения максимальной высоты horizontal recycler view относительно текста. Я решил эту задачу переопределив on measure где для каждого view item вычислял on measure с максимально длинным текстом, вставив самый длинный текст перед вызовом super.onMeasure. Ну и потом применял measure height для каждого элемента списка. P.S. Но во время разработки тоже были кое какие кастыли. Хотелось бы увидеть альтернативную реализацию.
Поменял notifyDataSetChanged на diffUtils, пока экран не весь заполнен элементами списка, новые элементы добавляются нормально, в верх списка, а остальные сдвигаются вниз. Но как только элементы заполняют весь экран до низа, новые элементы добавляются вверх и их не видно, то есть надо проскроллить вверх, чтоб увидеть их.
Вопрос: как правильно добавить тень на группу айтемов ?) Ну конечно вместе с закругленными углами для группы. С углами как правило нет проблем, а вот тень это боль.
С композом есть свои проблемы, особенно с большим количеством элементов в LazyColumn с LazyRow. Пока это выражается в большем лагании, и расходовании памяти. Возможно надо примерять секретные оптимизаци или ждать отпимизаций от гугла. Но RecyclerView на тех же объемах данных работает заметно плавнее.
Спасибо за видео, но очень затянуто. И мне кажется это решение не очень удобное: для простого примера надо по 3 класса по 100 строк кода, хотя большая часть из него (дифутилсы, сам холдер, создание холдера, проброс тайпов и т.д.) просто копипаста из класса в класс. Плюс в UI очень много поисков по мапе с кастами и прочего, что не сильно про оптимизацию. Ну и очень много логики с ластАдаптерПозишен, каррентАдаптерПозишен и прочим, что надо везде учитывать. + Один код во фрагменте (декораторы), второй в айтеме (ещё что-то), третий во вьюхолдере (биндинги), четвёртый ещё где-то. Сложно найти кто и где выставил этот декоратор и почему. Мы пошли по-другому пути: у нас ВМ в бэке создаёт "ячейки" c готовыми кликлистенерам, декораторам, данными и всем остальным, что понадобится для отображения или для уведомления ВМ о каких-то ивентах. Эти ячейки - это интерфейс, который имеют сразу дефолтный ViewHolder/onCreateViewHolder/DiffUtils/клик (но всё можно переопределить там же), так же все остальные методы вьюхолдера. А всё обрабатывает либовский адаптер (он просто делегирует всё, но без кастов, как здесь). То есть ячейка сразу рисует всё, что ей дали, без проверок "я последний айтем?" или "я после айтема типа Х?", т.к. ей ВМ сразу даст нужный декоратор (именнно декоратор, а не паддинги), что надо с таким отступом снизу или с таким разделителем сверху и т.д., т.к. она знает, какие айтемы находятся возле этой ячейки. Это всё без кодгена и т.д в либе на 100+- строк. (создание холдера для каждого айтема в списке можно сделать своё: databinding/viewbinding/customView/кодом).
я старался показать чуть более приземленные к RV вещи чтобы осветить как можно больше тем. возможно ваше решение действительно удобнее и гибче. а возможность посмотреть на эту либу?
@@WoffkaG94 я бы послушал. я соглашусь с тем что размазывать код связанный элементами списка по приложению не самая удобная затея. касательно декораторов наверно даже оптимальнее будет прокидывать все необходимые отступы аналогично тому как я указал фиксированную длинну списка(не говоря уже о том что это и правда удобнее когда все рядом лежит). Про DiffUtil видимо у вас есть дефолтная имплементация где вы сравниваете по умолчанию хешкоды и по equals элементы. В любом случае очень интересно взглянуть на ваше решение! буду ждать новостей)
@@pbKruasan Я опубликовал версию github.com/netcosports/CompositeAdapter_Android DiffUtils основан на том, что у нас должен быть kotlin data class или класс с нормальным equals - а значит мы можем их сравнить по дефолту (и в редких случаях, если это надо, дать возможность переопределить это). А так же у класса должен быть какой-то ID. Раньше у нас был интерфейс, но это создавало неудобства. Теперь надо явно его передать. Хэшкод не подойдёт, т.к. нам надо знать, что это тот же объект, у которого поменялось какое-то поле. Примерно на таком принципе и строится всё остальное: создание вьюхолдера, передача типов, передача лаяутов, передача кликов и т.д.. Они в 99% одинаковые, а значит можно их не копипастить. А так же декораторы, которые "биндятся" к вьюхолдеру почти как "дата". Их высчитывает вьюмоделька при создании списка, а не фрагмент, который на момент создания вью не знает, какие данные придут в адаптер одной из его вьюх и навряд ли должен знать. Декораторы участвуют в диффах тоже, т.к. если мы удалим последний айтем, то у предпоследнего дата не поменяется, но поменяется декоратор (допустим, у последнего айтема отступ снизу больше).
16:33 А, собственно, насколько правильно держать Drawable внутри каждого элемента списка, вместо его id? Мы ведь не знаем насколько большой у нас Drawable.
33:32 Насколько правильно с точки зрения оптимизации делать так как в видео? 1. Создавать новую копию Item'а с изменённым состоянием like'а 2. Создавать 2 раза новые ArrayList'ы - один раз в методе toList, второй раз новая копия ArrayList'а создаётся внутри ListAdapter'a 3. Считать для всего списка DiifCalback. Если список будет длинный, то данное действие может занять заметный пользователю промежуток времени. На мой взгляд оптимальнее было бы сделать поле like'а изменяемым (var) и вызвать метод notifyItemChanged(pos:Int, payload:Any?) private fun onSavePost(post: UserPost) { val postIndex = feed.indexOf(post) post.isSaved = !post.isSaved adapter.notifyItemChanged(postIndex,post.isSaved) }
1. Как я говорил, делал я копию в демонстрационнных целях. В этом случае мы будем терять в том что создаем новый объект и запускаем DiffUtil, в качестве альтернативы конечно можно делать чтобы при клике ViewHolder без посредников сам обновлял свое состояние а вам бы кидал коллбек что нужно отправить соответствующий запрос на сервер (ну и плюс както обрабатывать еще когда нам сервер вернет ошибку). Можно конечно сделать и так, но тут я бы задался вопросом - а точно ли обновление своего состояния задача ViewHolder? Ведь мы когда пишем фрагмент то абсолютно всю логику уносим во ViewModel/Presenter, тут кажется вполне аналогичная история. А говорить вам как делать я не могу, поэтому тут уже вам нужно выбрать что вам подходить больше. 2. Да про дополнительный вызов `.toList()` это на самом деле мой косяк, в нем нет необходимости, не успел провести рефакторинг( 3. Да, ваш пример вполне валидный. Но тут я бы исходил из необходимости, ведь если у вас в коде есть DiffUtil то другой разработчик не будет ожидать что вы еще гдето в обход DiffUtil обновляете список и если будут возникать какие-либо ошибки то это может увеличить время поиска проблемы. Я бы все же попробовал все изменения прокидывать через DiffUtil, и если вдруг пользователи начали бы жаловаться на проблемы тогда бы уже использовал `adapter.notifyItemChanged(postIndex,post.isSaved)` и чтоб совсем красиво было еще бы покрыл все это дело тестами, написал бы документацию/комментарии чтоб у другого разработчика было меньше шансов чтото поломать
Паша, спасибо большое за твой труд. Доклад получился суперским, ничего подобного еще не видел. Собрал самые лучшие практики прямо как я и искал.
Кирилл, спасибо тебе за контент и приглашенного гостя!
смотрел это видео в субботу,,,, еле сдержал себя что-бы комп не включить(выходные не для работы! (берегите глазки)).
Павел спасибо,,, слушаю вас и вспоминаю зачем пошёл программистом работать (последние пару лет - как на каторгу хожу(и бабло не помогает)), видеть как человеку искренне (как мне показалось) интересно и вас заражает (жаль только не надолго).
Надо видео по CustomView! Однозначно! Другого пути нет!
Позитивный спикер, ему надо задуматься о карьере ютуб-блоггера - приятно смотреть
спасибо большое ^^
Огромное спасибо Паше - очень приятно слушать. Кирилл - спасибо за организацию. С Payload лично мне в голову не приходил вопрос про анимацию вставки элемента. Я там в коде увидел, что если не возвращать пейлоды, то не вызывается unbind() и как-то все встало на места. Если мы возвращаем пейлоды - значит хотим мальца сами подправить. А если мы сами берем на себя такую ответственность, то и за нас перетирать вьюшку не нужно. Мне было проще понять именно так.
Огромная благодарность за проделанный труд,многое для себя узнал!Спасибо, Кирилл! Спасибо, Павел!
Офигенное видео! Спасибо огромное, вы меня спасли :D
Еще не досмотрел, но было бы очень познавательно на каждой итерации оптимизации выполнять замеры производительности и отображать их
Замечательный практикум, узнал много нового, большое вам спасибо!
Павел очень классный спикер.
Пересматриваю второй раз, огромное спасибо, всё по полочкам, побольше бы таких видео на разные темы по андроиду
Спасибо, очень познавательно и по делу.
Отличнейшее видео! Спасибо!!!
Лучшее, что есть по RecyclerView на русском.
Супер материал, хотя и делаю немного по другому. Побольше бы так технологии разбирать, на 2часа) Было бы очень удобно с таймингами на видео, напр. в данном случае по неймингам веток. Спасибо!)
Тайминг отрубил RUclips за нарушение (
По поводу динамического определения максимальной высоты horizontal recycler view относительно текста.
Я решил эту задачу переопределив on measure где для каждого view item вычислял on measure с максимально длинным текстом, вставив самый длинный текст перед вызовом super.onMeasure. Ну и потом применял measure height для каждого элемента списка.
P.S. Но во время разработки тоже были кое какие кастыли.
Хотелось бы увидеть альтернативную реализацию.
Шикарная инфа и отличная подача!!!!
Классный урок. Паша монстр!
Да, и видос отличный получился
Поменял notifyDataSetChanged на diffUtils, пока экран не весь заполнен элементами списка, новые элементы добавляются нормально, в верх списка, а остальные сдвигаются вниз. Но как только элементы заполняют весь экран до низа, новые элементы добавляются вверх и их не видно, то есть надо проскроллить вверх, чтоб увидеть их.
Огонь 🔥👏⭐
Очень полезный выпуск 👍
Вопрос: как правильно добавить тень на группу айтемов ?) Ну конечно вместе с закругленными углами для группы.
С углами как правило нет проблем, а вот тень это боль.
С композом есть свои проблемы, особенно с большим количеством элементов в LazyColumn с LazyRow. Пока это выражается в
большем лагании, и расходовании памяти. Возможно надо примерять секретные оптимизаци или ждать отпимизаций от гугла. Но RecyclerView на тех же объемах данных работает заметно плавнее.
Спасибо очень полезно
Хочу КастомВью ), только что нибудь сложное а не аватарку с анимашкой
Что например?
@@AndroidBroadcast ну можно что то типа графиков или диаграмм. Аля Диаграмма Ганта или еще что-нибудь
@@torskandinav4634 график свечей по акциям с интерактивом ?
@@AndroidBroadcast можно хотя бы click to expand
Ооочень сложно, но интересно). 60% всего не понял
Спасибо за видео, но очень затянуто.
И мне кажется это решение не очень удобное: для простого примера надо по 3 класса по 100 строк кода, хотя большая часть из него (дифутилсы, сам холдер, создание холдера, проброс тайпов и т.д.) просто копипаста из класса в класс.
Плюс в UI очень много поисков по мапе с кастами и прочего, что не сильно про оптимизацию. Ну и очень много логики с ластАдаптерПозишен, каррентАдаптерПозишен и прочим, что надо везде учитывать. + Один код во фрагменте (декораторы), второй в айтеме (ещё что-то), третий во вьюхолдере (биндинги), четвёртый ещё где-то. Сложно найти кто и где выставил этот декоратор и почему.
Мы пошли по-другому пути: у нас ВМ в бэке создаёт "ячейки" c готовыми кликлистенерам, декораторам, данными и всем остальным, что понадобится для отображения или для уведомления ВМ о каких-то ивентах.
Эти ячейки - это интерфейс, который имеют сразу дефолтный ViewHolder/onCreateViewHolder/DiffUtils/клик (но всё можно переопределить там же), так же все остальные методы вьюхолдера.
А всё обрабатывает либовский адаптер (он просто делегирует всё, но без кастов, как здесь).
То есть ячейка сразу рисует всё, что ей дали, без проверок "я последний айтем?" или "я после айтема типа Х?", т.к. ей ВМ сразу даст нужный декоратор (именнно декоратор, а не паддинги), что надо с таким отступом снизу или с таким разделителем сверху и т.д., т.к. она знает, какие айтемы находятся возле этой ячейки.
Это всё без кодгена и т.д в либе на 100+- строк. (создание холдера для каждого айтема в списке можно сделать своё: databinding/viewbinding/customView/кодом).
я старался показать чуть более приземленные к RV вещи чтобы осветить как можно больше тем. возможно ваше решение действительно удобнее и гибче. а возможность посмотреть на эту либу?
@@pbKruasan Пока что либа приватная. Возможно опубликуем скоро, а может и расскажем, если попросят.
@@WoffkaG94 я бы послушал. я соглашусь с тем что размазывать код связанный элементами списка по приложению не самая удобная затея. касательно декораторов наверно даже оптимальнее будет прокидывать все необходимые отступы аналогично тому как я указал фиксированную длинну списка(не говоря уже о том что это и правда удобнее когда все рядом лежит). Про DiffUtil видимо у вас есть дефолтная имплементация где вы сравниваете по умолчанию хешкоды и по equals элементы. В любом случае очень интересно взглянуть на ваше решение! буду ждать новостей)
@@pbKruasan
Я опубликовал версию
github.com/netcosports/CompositeAdapter_Android
DiffUtils основан на том, что у нас должен быть kotlin data class или класс с нормальным equals - а значит мы можем их сравнить по дефолту (и в редких случаях, если это надо, дать возможность переопределить это). А так же у класса должен быть какой-то ID. Раньше у нас был интерфейс, но это создавало неудобства. Теперь надо явно его передать.
Хэшкод не подойдёт, т.к. нам надо знать, что это тот же объект, у которого поменялось какое-то поле.
Примерно на таком принципе и строится всё остальное: создание вьюхолдера, передача типов, передача лаяутов, передача кликов и т.д.. Они в 99% одинаковые, а значит можно их не копипастить.
А так же декораторы, которые "биндятся" к вьюхолдеру почти как "дата". Их высчитывает вьюмоделька при создании списка, а не фрагмент, который на момент создания вью не знает, какие данные придут в адаптер одной из его вьюх и навряд ли должен знать.
Декораторы участвуют в диффах тоже, т.к. если мы удалим последний айтем, то у предпоследнего дата не поменяется, но поменяется декоратор (допустим, у последнего айтема отступ снизу больше).
Custom view! :)
очень полезное видео
почему имя адаптера называется Fingerprint
16:33 А, собственно, насколько правильно держать Drawable внутри каждого элемента списка, вместо его id? Мы ведь не знаем насколько большой у нас Drawable.
А ещё там при сравнении элементов с драваблами а не со ссылками / айдишниками будут проблемы
После того, как вместо констант вью тайп он вставил id лейаутов, дальше можно не смотреть)
Как корректно сравнивать в DiffUtil наследников Sealed класса?
Так вы можете вызывать equals, либо перебирать все sealed классы в when и собственным правилом сравнивать
10:30 no new line at end of file
приложение падает при удалении последнего элемента
верно! спасибо за информацию! чуть позже поправлю
LazyColumn 🗿🗿🗿
В закладки
33:32
Насколько правильно с точки зрения оптимизации делать так как в видео?
1. Создавать новую копию Item'а с изменённым состоянием like'а
2. Создавать 2 раза новые ArrayList'ы - один раз в методе toList, второй раз новая копия ArrayList'а создаётся внутри ListAdapter'a
3. Считать для всего списка DiifCalback.
Если список будет длинный, то данное действие может занять заметный пользователю промежуток времени.
На мой взгляд оптимальнее было бы сделать поле like'а изменяемым (var) и вызвать метод notifyItemChanged(pos:Int, payload:Any?)
private fun onSavePost(post: UserPost) {
val postIndex = feed.indexOf(post)
post.isSaved = !post.isSaved
adapter.notifyItemChanged(postIndex,post.isSaved)
}
1. Как я говорил, делал я копию в демонстрационнных целях. В этом случае мы будем терять в том что создаем новый объект и запускаем DiffUtil, в качестве альтернативы конечно можно делать чтобы при клике ViewHolder без посредников сам обновлял свое состояние а вам бы кидал коллбек что нужно отправить соответствующий запрос на сервер (ну и плюс както обрабатывать еще когда нам сервер вернет ошибку). Можно конечно сделать и так, но тут я бы задался вопросом - а точно ли обновление своего состояния задача ViewHolder? Ведь мы когда пишем фрагмент то абсолютно всю логику уносим во ViewModel/Presenter, тут кажется вполне аналогичная история. А говорить вам как делать я не могу, поэтому тут уже вам нужно выбрать что вам подходить больше.
2. Да про дополнительный вызов `.toList()` это на самом деле мой косяк, в нем нет необходимости, не успел провести рефакторинг(
3. Да, ваш пример вполне валидный. Но тут я бы исходил из необходимости, ведь если у вас в коде есть DiffUtil то другой разработчик не будет ожидать что вы еще гдето в обход DiffUtil обновляете список и если будут возникать какие-либо ошибки то это может увеличить время поиска проблемы. Я бы все же попробовал все изменения прокидывать через DiffUtil, и если вдруг пользователи начали бы жаловаться на проблемы тогда бы уже использовал `adapter.notifyItemChanged(postIndex,post.isSaved)` и чтоб совсем красиво было еще бы покрыл все это дело тестами, написал бы документацию/комментарии чтоб у другого разработчика было меньше шансов чтото поломать
@@pbKruasan спасибо за развернутый ответ.
contact adapter😂
1:45
"Наклеечка символизирует что я из Краснодара"
-на наклеечке изображена мусорка
Как иронично...
ну это один из символов города) что у нас и мусорки красивые) Варламов рекомендует)
CustomView
Еще больше рекламы засунуть не пробовал????