Страницы

Поиск по вопросам

Показаны сообщения с ярлыком recyclerview. Показать все сообщения
Показаны сообщения с ярлыком recyclerview. Показать все сообщения

воскресенье, 1 марта 2020 г.

Как сделать открытие recyclerView с конца?

#java #android #recyclerview


Я сделал клиент по обмену сообщениями, в котором сообщения отображаются при помощи
recyclerView, но список открывается в самом начале(наверху), а мне надо, чтобы открывался
с конца(снизу), как во всех мессенджерах
    


Ответы

Ответ 1



Добавьте свойство reverseLayout в xml, там где описывается ваш RecyclerView. Оно перевернет ваш RecyclerView. Через xml: app:reverseLayout="true" app:stackFromEnd="true" Программно: LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); layoutManager.setReverseLayout(true); layoutManager.setStackFromEnd(true); recyclerView.setLayoutManager(layoutManager); Надеюсь вам помог мой ответ. update исправил ошибку в коде.

Ответ 2



После присвоения адаптера используйте: recyclerView.scrollToPosition(rv.getAdapter().getItemCount());

пятница, 14 февраля 2020 г.

Добавить разделитель элементов в RecyclerView

#android #recyclerview


Есть такая разметка для элемента (т.е. пункта списка для RecyclerView)




    

        

        

            

            
        

    




Мне нужно чтобы элементы в списке были разделены также как на картинке, т.е чтобы
после каждого элемента была линия. Как это сделать?


    


Ответы

Ответ 1



Для этого можно прописать View- ку

Ответ 2



Я это делаю с помощью класса DividerItemDecoration унаследованного от RecyclerView.ItemDecoration: public class DividerItemDecoration extends RecyclerView.ItemDecoration { private Drawable mDivider; private int mOrientation; public DividerItemDecoration(Drawable divider) { mDivider = divider; } @Override public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { if (mOrientation == LinearLayoutManager.HORIZONTAL) { drawHorizontalDividers(canvas, parent); } else if (mOrientation == LinearLayoutManager.VERTICAL) { drawVerticalDividers(canvas, parent); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if (parent.getChildAdapterPosition(view) == 0) { return; } mOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation(); if (mOrientation == LinearLayoutManager.HORIZONTAL) { outRect.left = mDivider.getIntrinsicWidth(); } else if (mOrientation == LinearLayoutManager.VERTICAL) { outRect.top = mDivider.getIntrinsicHeight(); } } private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) { int parentTop = parent.getPaddingTop(); int parentBottom = parent.getHeight() - parent.getPaddingBottom(); int childCount = parent.getChildCount(); for (int i = 0; i < childCount - 1; i++) { View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int parentLeft = child.getRight() + params.rightMargin; int parentRight = parentLeft + mDivider.getIntrinsicWidth(); mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom); mDivider.draw(canvas); } } private void drawVerticalDividers(Canvas canvas, RecyclerView parent) { int parentLeft = parent.getPaddingTop() * 9; int parentRight = parent.getWidth() - parent.getPaddingRight(); int childCount = parent.getChildCount(); for (int i = 0; i < childCount - 1; i++) { View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int parentTop = child.getBottom() + params.bottomMargin; int parentBottom = parentTop + mDivider.getIntrinsicHeight(); mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom); mDivider.draw(canvas); } } } Где parent.getPaddingTop() * 9: parent.getPaddingTop - верхний Padding RecyclerView, в моем случае он равен 10dp А умножаю на 9, так как от левого края округленного ImageView до текста отступ 90dp И в Activity или где вы инициализируете ваш RecyclerView: private void initRecyclerView() { mChatsRV.setLayoutManager(new LinearLayoutManager(getApplicationContext())); mChatsRV.addItemDecoration(new DividerItemDecoration(ContextCompat.getDrawable(this, R.drawable.items_divider))); } И сама линия(разметка) items_divider.xml:

Оптимальное количество элементов подгружаемых из SQLite в RecyclerView за один запрос

#android #sqlite #recyclerview


Есть новостной агрегатор. Статьи грузятся из сети, и хранятся в локальной SQLite.
UI организован стандартно: RecyclerView, элементами которого являются превью статей. 

Вопрос следующий: как оптимально организовать подгрузку данных в RecyclerView из
SQLite, если на экране помещается ≈ 3 элемента?

Поясню: в данном вопросе временем загрузки статей из сети, их размещением в БД и
временем преобразования статей в превью пренебрегаем. Интересует механизм подгрузки
элементов в RecyclerView за один запрос и организация этой подгрузки. Стандартный механизм
(загружаем сколько-то элементов -> пользователь прокручивает список до конца -> загружаем
новую порцию элементов) не подходит.

Например, возможно стоит ловить скроллинг на середине списка и уже тогда начинать
подгрузку новых данных? Кроме того, возможно у уважаемого сообщества есть опыт о том,
какое количество элементов из SQLite оптимально забирать за один запрос? Т.к. есть
подозрение, что загрузка 3 элементов из SQLite будет примерно равна по времени загрузке
30 элементов, т.к. большая часть времени тратится на саму транзакцию, но уже скажем
запрашивать 300 элементов за запрос будет неоптимально. 
    


Ответы

Ответ 1



@pavloff, конечно все верно расписал: за один запрос нужно "забирать" все нужные данные, нет никакого смысла делить их на маленькие части. Такое применимо к сетевым запросам, но никак не к локальным БД. Cursor сам подгрузит что надо, в нужном ему количестве, насколько мне помниться. В любом случае не стоит делать из этого проблем Но проблема в том, что RecyclerView из коробки не умеет поддерживать адаптер с курсором, то есть надо самому заморачиваться с изобретением адаптера, который будет читать из БД или же придумывать адаптер, который работает поверх Cursor, по аналогии с CursorAdapter, который имеется в комплекте ListView. К счастью умные люди уже все написали все. Возьмите RecyclerViewCursorAdapter - подайте ему на вход заранее подготовленный Cursor и будет вам счастье

Ответ 2



Разобрался в проблеме. Представлю свой ответ. Во-первых, почему для достаточно больших списков не стоит загружать сразу все данные для RecyclerView из SQLite: Нет смысла грузить данные, которые пользователь, возможно, не увидит. Если при той же отзывчивости интерфейса (без тормозов), мы можем обеспечить плавную подгрузку данных и, при этом, наше приложение будет кушать меньше памяти, то почему бы не сделать так? Использование SQLiteCursor имеет ту особенность, что при выборке данных размером больше 2 MB появляются дополнительные расходы по времени на выборку каждых следующих 2 MB данных. Речь о том, что размер используемого SQLiteCursor буфера строк (CursorWindow) около 2 MB. Более подробно об этой проблеме см. в этой статье: оригинал (en) и перевод на русский. Существуют решения по плавной подгрузке данных в RecyclerView из SQLite: см., например, этот цикл статей (en). Есть данные, что оптимально загружать за один раз не менее чем в 10 раз больше элементов RecyclerView, чем помещается на экране. Т.е., если на экране 3 элемента, подгружаем за один раз около 30. Подгрузку следующей порции данных, следует начинать на половине или 2/3 скроллинга до конца списка. Это общие советы, т.к. стоит учитывать размер данных, возможное их преобразование в POJO-объекты для списка, особенности реализации RecyclerView. Наконец, есть библиотека от Google Paging Library (еще не вышла в релиз 1.0), которая призвана в том числе решать проблему, затронутую в вопросе. Еще один пример, реализация подгрузки c использованием RxJava (1 часть, 2 часть).

воскресенье, 9 февраля 2020 г.

Добавление View программно

#android #android_studio #listview #recyclerview #view


При программном добавлении сразу двух View в макет возникает ошибка такого рода:

Process: com.money.evgeny.ShoppingBudget, PID: 14676
java.lang.IllegalStateException: The specified child already has a parent. You must
call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:4310)
at android.view.ViewGroup.addView(ViewGroup.java:4146)
at android.view.ViewGroup.addView(ViewGroup.java:4087)
at android.view.ViewGroup.addView(ViewGroup.java:4060)
at com.money.evgeny.ShoppingBudget.Adapters.RVAdapter_WhatNew.onBindViewHolder(RVAdapter_WhatNew.java:60)
at com.money.evgeny.ShoppingBudget.Adapters.RVAdapter_WhatNew.onBindViewHolder(RVAdapter_WhatNew.java:16)


Код ACtivity с RecyclerView (куда добавляю view):




    

        

    

    

    




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

public class RVAdapter_WhatNew extends RecyclerView.Adapter{
    private List mas_data;
    LinearLayout card_liner;
    LinearLayout item_text;

    //Класс view holder-а, с помощью которого мы получаем ссылку на каждый элемент
пункта списка
    class ViewHolder extends RecyclerView.ViewHolder {
        TextView tv_item_version;
        TextView tv_item_date;
        TextView tv_item_text;

        ViewHolder(View itemView) {
            super(itemView);

            tv_item_version = (TextView) itemView.findViewById(R.id.tv_item_version);
            tv_item_date = (TextView) itemView.findViewById(R.id.tv_item_date);
            tv_item_text = (TextView) itemView.findViewById(R.id.tv_item_text);
        }
    }

    //Конструктор для данных
    public RVAdapter_WhatNew(List list){
        this.mas_data = list;
    }

    @Override   //Создает view (вызывается LayoutManager-ом)
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_what_new_activity,
parent, false);

        card_liner = (LinearLayout) v.findViewById(R.id.card_liner);
        item_text = (LinearLayout) View.inflate(v.getContext(), R.layout.sub_item_what_new_activity,
null);
        card_liner.addView(item_text);
        card_liner.addView(item_text);

        return new ViewHolder(v);
    }

    @Override   //Заменяет контент view (вызывается LayoutManager-ом)
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.tv_item_version.setText(mas_data.get(position).version);
        holder.tv_item_date.setText(mas_data.get(position).date);
        holder.tv_item_text.setText(mas_data.get(position).text[0]);
    }

    @Override   //Возвращает размер данных (вызывается LayoutManager-ом)
    public int getItemCount() {
        return mas_data.size();
    }
}


Метод в котором происходит программное добавление view и ошибка:

@Override   //Создает view (вызывается LayoutManager-ом)
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_what_new_activity,
parent, false);

        card_liner = (LinearLayout) v.findViewById(R.id.card_liner);
        item_text = (LinearLayout) View.inflate(v.getContext(), R.layout.sub_item_what_new_activity,
null);
        card_liner.addView(item_text);
        card_liner.addView(item_text);

        return new ViewHolder(v);
    }

    


Ответы

Ответ 1



Необходимо 2 раза вызвать inflate, т.к. в вашем случае вы пытаетесь добавить один и тот же view @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_what_new_activity, parent, false); card_liner = (LinearLayout) v.findViewById(R.id.card_liner); item_text = (LinearLayout) View.inflate(v.getContext(), R.layout.sub_item_what_new_activity, null); item_text2 = (LinearLayout) View.inflate(v.getContext(), R.layout.sub_item_what_new_activity, null); card_liner.addView(item_text); card_liner.addView(item_text2); return new ViewHolder(v); }

Ответ 2



Если R.layout.sub_item_what_new_activity - это просто TextView, то можно просто два раза создать его программно. Это будет гораздо быстрее, чем инфлейтить лэйаут (особенно, два раза).

Можно ли убрать индикатор окончания прокрутки в RecyclerView?

#android #recyclerview


Можно ли убрать эту штуку, что бы в конце прокрутки список просто останавливался
без всякой лишней индикации?


    


Ответы

Ответ 1



В XML у RecyclerView добавьте android:overScrollMode="never"

Как сделать эффект оттяжки у RecyclerView?

#android #recyclerview


Как сделать такой эффект как у iOS когда в RecyclerView доходишь до последнего элемента(или
до первого), то когда тянешь дальше то вью отьезжает вниз с оттяжкой как бы давая понять,
что это крайний элемент в списке. 

В андроиде это реализовано по другому, появляется такой полукруг но мне нужна реализация
как в иос. 

Как это сделать? Куда смотреть?
    


Ответы

Ответ 1



Можно сделать на основе CoordinatorLayout.Behavior Вот нужное Вам решение, на англоязычной версии SO: https://stackoverflow.com/questions/40758635/ios-like-over-scroll-effect-on-android

среда, 5 февраля 2020 г.

Как использовать один adapter для нескольких фрагментов?

#java #android #recyclerview #adapter


Имеется несколько фрагментов. В каждом из фрагментов есть RecyclerView со своим adapter'ом.
На момент создания 3-го адаптера понял, что они в принципе схожи по своей структуре,
ну что разве за исключением массивов в которых я храню текст и картинки.
Ну и собственно меня натолкнуло на мысль, как возможно, а главное стоит ли использовать
для всех моих фрагментов?
На данный момент код одного из моих адаптеров следующий:

public class CardAdapter extends RecyclerView.Adapter {

    private String[] titles = {"Chapter One",
            "Chapter Two",
            "Chapter Three",
            "Chapter Four",
            "Chapter Five",
            "Chapter Six",
            "Chapter Seven",
            "Chapter Eight"};

    private String[] details = {"Item one details",
            "Item two details", "Item three details",
            "Item four details", "Item file details",
            "Item six details", "Item seven details",
            "Item eight details"};

    private int[] images = { R.drawable.android_image_1,
            R.drawable.android_image_2,
            R.drawable.android_image_3,
            R.drawable.android_image_4,
            R.drawable.android_image_5,
            R.drawable.android_image_6,
            R.drawable.android_image_7,
            R.drawable.android_image_8 };

    class ViewHolder extends RecyclerView.ViewHolder{

        public int currentItem;
        public ImageView itemImage;
        public TextView itemTitle;
        public TextView itemDetail;

        public ViewHolder(View itemView) {
            super(itemView);
            itemImage = (ImageView)itemView.findViewById(R.id.item_image);
            itemTitle = (TextView)itemView.findViewById(R.id.item_title);
            itemDetail =
                    (TextView)itemView.findViewById(R.id.item_detail);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override public void onClick(View v) {
                    int position = getAdapterPosition();

                    Snackbar.make(v, "Click detected on item " + position,
                            Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
                }
            });
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.card_layout, viewGroup, false);
        ViewHolder viewHolder = new ViewHolder(v);
        return viewHolder;
    }


    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        viewHolder.itemTitle.setText(titles[i]);
        viewHolder.itemDetail.setText(details[i]);
        viewHolder.itemImage.setImageResource(images[i]);
    }

    @Override
    public int getItemCount() {
        return titles.length;
    }
}

    


Ответы

Ответ 1



Как вариант, можно определить адаптер следующим образом: public class CardAdapter extends RecyclerView.Adapter { private String[] titles; private String[] details; private int[] images; class ViewHolder extends RecyclerView.ViewHolder{ public int currentItem; public ImageView itemImage; public TextView itemTitle; public TextView itemDetail; public CardAdapter(String[] titles, String[] details, int[] images) { this.titles = new String[titles.length]; System.arraycopy(titles, 0, this.titles, 0, titles.length); this.details = new String[details.length]; System.arraycopy(details, 0, this.details, 0, details.length); this.images = new int[images.length]; System.arraycopy(images, 0, this.images, 0, images.length); } public ViewHolder(View itemView) { super(itemView); itemImage = (ImageView)itemView.findViewById(R.id.item_image); itemTitle = (TextView)itemView.findViewById(R.id.item_title); itemDetail = (TextView)itemView.findViewById(R.id.item_detail); itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = getAdapterPosition(); Snackbar.make(v, "Click detected on item " + position, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); } } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View v = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.card_layout, viewGroup, false); ViewHolder viewHolder = new ViewHolder(v); return viewHolder; } @Override public void onBindViewHolder(ViewHolder viewHolder, int i) { viewHolder.itemTitle.setText(titles[i]); viewHolder.itemDetail.setText(details[i]); viewHolder.itemImage.setImageResource(images[i]); } @Override public int getItemCount() { return titles.length; } } И при создании адаптера использовать следующий конструктор: public CardAdapter(String[] titles, String[] details, int[] images) Куда передавать Ваши конкретные для каждого адаптера titles, details и images. А вообще, если у Вас элементы списка постоянны, то правильнее будем воспользоваться наследованием, чем выносить инициализацию этих статичных элементов куда-либо за пределы адаптера. А самый правильный способ, наверное, заключается в реализации в адаптере методов, которые будут инициализировать массивы. Добавляете в адаптер конструктор, который будет принимать некоторое значение – тип адаптера, и, в зависимости от этого типа из конструктора будет вызываться соответствующий метод для инициализации массивов. Пример: public CardAdapter(int type) { switch (type) { case 0: // вызов метода для инициализации массивов одними значениями break; case 1: // вызов метода для инициализации массивов другими значениями break; case 2: // вызов метода для инициализации массивов третьими значениями break; } }

Ответ 2



Во первых не используй массивы. Используй List! Во вторых не используй несколько массивов. Используй один List. public class YourItem{ private String title; private String detail; private int image; ... } и в адатер передавай список этих итемов. и тогда сможешь один адаптер для нескольких юзать.

понедельник, 3 февраля 2020 г.

RecyclerView убивается в ScrollView

#android #recyclerview #android_scrollview


При попытке поместить RecyclerView внутри ScrollView получаем коллапс первого. Теоретически
можно внутри адаптера сделать метод getView(), чтобы его получить и измерить и на основе
него  в коде принудительно установить высоту для RecyclerView, я так делал для ListView.
Но в данном сслучае есть такая проблема - все поля в CardView надо отображать полностью,
и высота у них может быть разная, а MEASURE_UNSPECIFIED возвращает высоту стандартную
для данной разметки (например для TextView это высота одной строки, так как в разметке
он однострочный, а в реальном использовании этот же TextView заполняется пользовательским
комментарием, который может содержать сколько угодно строк, потому и высота разная.
А Измерение все арвно будет, как и у одной строки). Есть ещё какие либо способы развернуть
RecyclerView в ScrollView? От ScrollView уйти не получается, потому что на странице
достаточно много информации и иначе она не помещается в экран. 

Кстати параллельный вопрос, в приложении AliExpress явно используется RecyclerView,
причем карточки там заполнены разным образом и помещены в список. То есть явно разработчик
их помещал туда либо каким то извращённым адаптером, либо как то ещё. Возник вопрос,
можно ли создавать CardView или наследоваться от него, и создавать Карту по аналогии
с фрагментом? А потом создавать разметку непосредственно с конкретными CardView внутри
RecyclerView
    


Ответы

Ответ 1



Нашел решение на английском стаке, переопределяем LayoutManager: public class CommentLinearLayoutManager extends LinearLayoutManager { public CommentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } private int[] mMeasuredDimension = new int[2]; @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { final int widthMode = View.MeasureSpec.getMode(widthSpec); final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); int width = 0; int height = 0; for (int i = 0; i < getItemCount(); i++) { measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); if (getOrientation() == HORIZONTAL) { width = width + mMeasuredDimension[0]; if (i == 0) { height = mMeasuredDimension[1]; } } else { height = height + mMeasuredDimension[1]; if (i == 0) { width = mMeasuredDimension[0]; } } } switch (widthMode) { case View.MeasureSpec.EXACTLY: width = widthSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } switch (heightMode) { case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } setMeasuredDimension(width, height); } private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.getViewForPosition(position); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), p.width); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height); view.measure(childWidthSpec, childHeightSpec); measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin; measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; recycler.recycleView(view); } } }

Ответ 2



Код выше не всегда хорошо работает. В случае если вы хотите чтобы элементы тоже были wrap_content, т.к. он измеряет ширину и высоты с MeasureSpec.UNSPECIFIED, лучше использовать вот это: public class MyLinearLayoutManager extends LinearLayoutManager { public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } private int[] mMeasuredDimension = new int[2]; @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { final int widthMode = View.MeasureSpec.getMode(widthSpec); final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); int width = 0; int height = 0; for (int i = 0; i < getItemCount(); i++) { if (getOrientation() == HORIZONTAL) { measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), heightSpec, mMeasuredDimension); width = width + mMeasuredDimension[0]; if (i == 0) { height = mMeasuredDimension[1]; } } else { measureScrapChild(recycler, i, widthSpec, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); height = height + mMeasuredDimension[1]; if (i == 0) { width = mMeasuredDimension[0]; } } } switch (widthMode) { case View.MeasureSpec.EXACTLY: width = widthSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } switch (heightMode) { case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } setMeasuredDimension(width, height); } private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.getViewForPosition(position); recycler.bindViewToPosition(view, position); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), p.width); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height); view.measure(childWidthSpec, childHeightSpec); measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin; measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; recycler.recycleView(view); } } }

пятница, 31 января 2020 г.

Как изменять содержимое CardView элементов в RecyclerView

#java #android #recyclerview


Задача:

У меня есть RecyclerView, который я заполняю карточками, содержание которых берется
с сервера. Эти карточки - диалоги. 

И в этих карточках есть специальное поле - последнее сообщение. Когда пользователь
отправляет мне сообщение это поле должно измениться на это последнее сообщение соответственно.

Проблема:

Мне как-то нужно реализовать динамическое изменение этих элементов RecyclerView. 

Подробности:

Меня интересует именно вопрос изменения элементов в реальном времени, а не способ
реализации отправки сообщений.

Если диалоги, я могу добавить adapter.addAll(data);, то вот как их изменять я не
знаю. Причем это, как я сказал ранее, должно происходить динамически, то есть на глазах
пользователя (без перезагрузки Activity или Fragment или самого приложения)

Код, адаптер и холдер

 private class RecyclerAdapter extends 
 RecyclerView.Adapter {
    private ArrayList items = new ArrayList<>();

    void addAll(List fakeItems) {
        int pos = getItemCount();
        this.items.addAll(fakeItems);
        notifyItemRangeInserted(pos, this.items.size());
    }

    @Override
    public int getItemViewType(int position)
    {
        return 0;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        RecyclerView.ViewHolder vh=null;
        View itemLayoutView;

        switch (getItemViewType(viewType))
        {
            case 0:
                itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.dialog_structure,
parent, false);
                vh = new RecyclerViewHolder(itemLayoutView);
                break;

        }
        return vh;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        switch (this.getItemViewType(position))
        {
            case 0:
                RecyclerViewHolder simple = (RecyclerViewHolder) holder;
                simple.bind(items.get(position));
                break;
        }
    }


    @Override
    public int getItemCount() {
        return items.size();
    }

}

private class RecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    //simple
    private TextView name;
    private TextView time;
    private CircleImageView circleImageView;
    private TextView lastMessage;

    RecyclerViewHolder(View itemView) {
        super(itemView);
        name = itemView.findViewById(R.id.person_name);
        time = itemView.findViewById(R.id.message_time);
        circleImageView = itemView.findViewById(R.id.person_photo);
        lastMessage = itemView.findViewById(R.id.last_message);

    }

    void bind(DialogInfo modelItem) {

        try {
            Picasso.with(getActivity().getApplicationContext()).load("http://46.173.213.127/media?t=avatar&query="+modelItem.getPhoto()).into(circleImageView);
        } catch (Exception ignored) {
        }

        name.setText(modelItem.getName());
        time.setText(modelItem.getTime());
        lastMessage.setText(modelItem.getLastMessage());
    }

    @Override
    public void onClick(View view) {

    }
}


Добавление элементов:

for(int i = 0; i < contacts.size(); i++){
        data.add(new DialogInfo("lol",contacts.get(i), "lol","lol"));

    }

    adapter = new RecyclerAdapter();
    rv.setAdapter(adapter);
    adapter.addAll(data);
    data.set(1, new DialogInfo("lol",contacts.get(1), "keke","kek"));//вот здесь
я пытался изменить нужный мне элемент, но он не изменяется в реальном времени.




Заранее, за все предложенные варианты добрыми людьми, говорю спасибо!

ДОПОЛНЕНИЕ:

adapter = new RecyclerAdapter();
    rv.setAdapter(adapter);
    adapter.addAll(data);
    data.set(1, new DialogInfo("lol",contacts.get(1), "keke","kek"));
    adapter.notifyDataSetChanged();

    


Ответы

Ответ 1



Ваша проблема заключается в том, что списки у вас разные. Первый вариант: вам надо редактировать один список, т.е. в фрагменте/активити держите список и его же передаете в адаптер. class MainActivity ... { . ... private List data = new ArrayList<>(); void initAdapter(){ adapter = new RecyclerAdapter(); adapter.setItems(data); rv.setAdapter(adapter); } void onClick(View view){ switch(view.getId()){ case R.id.edit: editItem(1, new DialogInfo("lol",contacts.get(1), "keke","kek")); break; case R.id.add: addItem(new DialogInfo("lol",contacts.get(1), "keke","kek")); break; } } private void editItem(int position, DialogInfo value) { data.set(position, value); adapter.notifyItemChange(position); } private void addItem(DialogInfo value) { data.add(value); adapter.notifyItemInserted(data.size()-1); } } private class RecyclerAdapter ... { private List items; ... public void setItems(List data){ this.items = data; } ... } Второй вариант: реализуете методы добавления, удаления, редактирования в адаптере. class MainActivity ... { . ... private List data = new ArrayList<>(); void initAdapter(){ adapter = new RecyclerAdapter(); adapter.addItems(data); rv.setAdapter(adapter); } void onClick(View view){ switch(view.getId()){ case R.id.edit: adapter.editItem(1, new DialogInfo("lol",contacts.get(1), "keke","kek")); break; case R.id.add: adapter.addItem(new DialogInfo("lol",contacts.get(1), "keke","kek")); break; } } } private class RecyclerAdapter ... { private List items = new ArrayList<>(); ... public void addItems(List data){ int pos = getItemCount(); this.items.addAll(data); notifyItemRangeInserted(pos, this.items.size()-1); } public void editItem(int position, DialogInfo value) { items.set(position, value); notifyItemChange(position); } public void addItem(int position, DialogInfo value) { items.add(value); items.notifyItemInserted(items.size()-1); } ... } Некоторые сложности будут, если вы захотите инициировать редактирование из holder'ов, вам надо будет в первом случае пробросить до активити/фрагмента вызов соответствующих колбэков, во втором случае логика редактирования в адаптере может выглядеть неуместной, и колбэки все равно будут. Особенно это хорошо будет видно на вашем примере, когда все классы у вас отдельные (не вложенные).

Ответ 2



Если я правильно понял Вам надо перерисовать Ресайкл. У адаптера есть метод adapter.notifyDataSetChanged() - который перерисовывает данные. Т.е. вам надо перезаписать data и потом дергать adapter.notifyDataSetChanged()

Ответ 3



Для обновления данных для определенного итема желательно использовать adapter.notifiyItemChanged(int position) Вызывать перерисовку списка, обновляя адаптер notifyDataSetChanged() это должно быть последним, что вы должны сделать, так как это полностью рефрешит структуру списка. Это два разных класса. Данные рекомендации описаны в оф документации. Дополнительно: CardView сложный элемент и предназначен для отображения сложных по структуре ячеек. Также в адаптере очень часто к item привязывают слушатели, аттачат обсерверы etc, поэтому я рекомендую более правильно обновлять данные, бонус ко всему, только с помощью таких манипуляций вы можете обеспечить правильное обновление и анимировать ваши ячейки. Обновляя список полностью вам необходимо также обеспечить целостность данных, если они имеет связность или даже транзитивность, то может возникнуть визуальная коллизия, это уже зависит от реализации и правильной архитектуры.

пятница, 24 января 2020 г.

Обновить listview методом notifyDataSetChanged сохранив текущее положение

#java #android #listview #recyclerview


Есть listview в котором данные обновляются сверху, как сообщения вконтакте, проблема
в том, что когда я получаю следующую часть items listview для обновления. 




Пробовал сохранять текущее положение firstVisibleItem в методе onScrollStateChanged(),
но это неправильно работает:

public void onScrollStateChanged(AbsListView view, int scrollState)
{
    if (enableLoading)
    {
        if (currentVisibleItemCount > 0 && scrollState ==  SCROLL_STATE_TOUCH_SCROLL)
        {
            int i = 0;
            if (currentFirstVisibleItem == 7)
            {
                updateMessagesList();//get 20 elements
            }
            _listView.setSelection(i+20);
        }
    }
}

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount)
{
    currentFirstVisibleItem = firstVisibleItem;
    currentVisibleItemCount = visibleItemCount;
    currentTotalItemCount = totalItemCount;
}


Догрузка должна быть какбы "незаметной" и не влиять на текущее положение. 

Итак, я начал скролить, сработало условие firstVisibleItem == 7
Если я буду сетать значение в listview.setSelection(firstVisibleItem), а я сейчас
продолжаю например скролить listview, тогда и метод listView.setSelection(i+20); будет
работать, если только я 7 елемент "дергну" и остановлюсь на нем же. А на самом деле
как будет - меня попросту вернет назад(в моему случае на 27 елемент в списке), потому
что я продолжаю скролить, а currentFirstVisibleItem уже не равен  - 7, а тот на котором
я остановлюсь, а ведь я могу и не остановливаться, а просто скролить, что-то например
искать; плюс еще возможна задержка 1-2 секунды, потому данные приходят с API. 

Данные обновляю методом  adapter.notifyDataSetChanged();

Как мне добавлять значения до listview как бы на заднем фоне, и чтобы при добавлении
не терялся currentPosition?

Решение

public void onScrolled(RecyclerView recyclerView, int dx, int dy) 
{    
    firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
    if(firstVisibleItem == 0){
    if (loading == true) 
    {
        loading = false;
        loadMoreItems();
    }
}


Плюс перестроил активити с listview на recyclerview. Даже скорость адпатера увеличилась.
То есть заметно при работе с большим количеством.
    


Ответы

Ответ 1



Попробуйте переделать всё на современный RecyclerView вместо устаревшего ListView. В первом есть методы типа notifyItemRangeInserted, которые уведомят адаптер о наличии новых элементов в некотором диапазоне и не вызовут перерисовки всего списка

RecyclerView ItemTouchHelper swipe undo

#java #android #recyclerview #swipe #swipelayout


Делаю свайп с удалением для RecyclerView 
ItemTouchHelper 

есть FrameLayout с двумя лайоутами верхний front свайпится в бок.
а под ним второй layout_back с кнопками "Скрыть" и "Отмена".

После свайпа, под ним появляется второй слой с кнопками, но почему то клики на них
срабатывают не с первого раза.

public class MyItemTouchHelper extends ItemTouchHelper.SimpleCallback {

    private ItemAdapter itemAdapter;
    private RecyclerView recyclerView;

    public MyItemTouchHelper(RecyclerView mRecyclerView, ItemAdapter itemAdapter) {
        super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
        this.itemAdapter = itemAdapter;
        this.recyclerView = mRecyclerView;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder
viewHolder) {
        return makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.RIGHT)
| makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
    }

    @Override
    public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        return 0;
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
        return false;
    }

    @Override
    public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) {
        ((ItemAdapter.ItemViewHolder) viewHolder).itemCancel.setEnabled(true);
        ((ItemAdapter.ItemViewHolder) viewHolder).itemDelete.setEnabled(true);
        ((ItemAdapter.ItemViewHolder) viewHolder).itemContainer.setVisibility(View.VISIBLE);
        ((ItemAdapter.ItemViewHolder) viewHolder).itemContainer.bringToFront();
        ((ItemAdapter.ItemViewHolder) viewHolder).itemContainer.invalidate();
        ((ItemAdapter.ItemViewHolder) viewHolder).itemDelete.setOnClickListener(new
View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(v.getContext(), "DELETE " + viewHolder.getAdapterPosition(),
Toast.LENGTH_SHORT).show();
                itemAdapter.removeItem(viewHolder.getAdapterPosition());

            }
        });
        ((ItemAdapter.ItemViewHolder) viewHolder).itemCancel.setOnClickListener(new
View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(v.getContext(), "CANCEL " + viewHolder.getAdapterPosition(),
Toast.LENGTH_SHORT).show();
                ((ItemAdapter.ItemViewHolder) viewHolder).itemContainer.setVisibility(View.INVISIBLE);
                recyclerView.getAdapter().notifyItemChanged(viewHolder.getAdapterPosition());
                clearView(recyclerView, viewHolder);
                ((ItemAdapter.ItemViewHolder) viewHolder).itemCancel.setEnabled(false);
                ((ItemAdapter.ItemViewHolder) viewHolder).itemDelete.setEnabled(false);
                ((ItemAdapter.ItemViewHolder) viewHolder).itemContext.bringToFront();
                ((ItemAdapter.ItemViewHolder) viewHolder).itemContext.invalidate();
            }
        });
    }

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder
viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        if (viewHolder.getAdapterPosition() == -1) {
            return;
        }
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            if (dX > 0 || dX < 0) {
                getDefaultUIUtil().onDraw(c, recyclerView, ((ItemAdapter.ItemViewHolder)
viewHolder).getSwipableView(), dX, dY, actionState, isCurrentlyActive);
            }
        }
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        View foreground = ((ItemAdapter.ItemViewHolder) viewHolder).getSwipableView();
        getDefaultUIUtil().clearView(foreground);
    }
}














    

    






layout_back:

 



 


пытаюсь вынести layout_back на передний план 
bringToFront() и перерисовываю invalidate(), но все равно не помогает, срабатывают
клики на кнопках раз на 5.
    


Ответы

Ответ 1



Два дня просидев с аналогичной проблемой, решил использовать AndroidSwipeLayout: https://github.com/daimajia/AndroidSwipeLayout собрал за час.

среда, 22 января 2020 г.

Recyclerview не растягивается при освобождении места в layout несмотря на match_parent

#android #recyclerview #android_animation #android_constraintlayout


Ломаю голову и рою гугл уже несколько дней, не могу понять, по какой причине recyclerView
не растягивается при появлении свободного пространства.  

Задача такая: при прокрутке списка (recycler) вниз на хотя бы 1 элемент, view-элементы
над списком "ужимаются" и дают дополнительное место для списка. "Ужимаю" я их с помощью
анимации и библиотеки expectAnim. Привожу ниже xml layout'а, а так же скрины проблемы
и фрагмент кода анимации.





































А вот как это выглядит в результате:




При этом не суть важно, если ресайкл перемещается вслед за линией, например, то такое
же пространство у него появляется ПОД ним.

А как заставить его растягиваться? Пробовал и setHasFixedSize(false), и получать
layout и через params.height задавать значение, а после requestLayout() дёргать. Но
всё без толку.

Может быть я вообще не под тем углом смотрю на задачу?
    


Ответы

Ответ 1



На всякий случай отвечу на собственный вопрос, мало ли кто столкнётся с похожей проблемой. Проблема была в том, что проанимированные элементы не исчезали (GONE), а... не знаю даже, что там с ними библиотека делала. Похоже, что просто оставляла за пределами видимой части экрана. После проигрывания анимации ставлю им View.GONE, и делаю LinearLayout resize для RecyclerView - всё работает. Правда, появились доп. проблемы, как то, что при увеличении ресайкла "вверх", список в нём надо прокручивать (через вызов scrollToPosition). Но это уже мелочи.

воскресенье, 12 января 2020 г.

Как выбирать действия в адаптере в зависимости от активити

#android #activity #recyclerview #adapter #cardview


у меня один адаптер и два активити. И хочу чтоб в каждом из этих активити отображались
разные переходы по клику на item.

 itemHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(context, EventActivity.class);
                intent.putExtra("event_id", item.event.getId());
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    context.startActivity(intent);
                    ((Activity)context).overridePendingTransition(R.anim.slide_in_from_right,
R.anim.push_to_back);
                } else {

                    Pair p1 = Pair.create((View) itemHolder.mTitleView,
"event_title");
                    Pair p2 = Pair.create((View) itemHolder.mInterestViewColor,
"event_interest_bg");
                    Pair p3 = Pair.create((View) itemHolder.mImageView,
"ivent_image");
                    Pair p4 = Pair.create((View) itemHolder.mInterestTitle,
"event_interest_name");
                    ActivityOptionsCompat options = ActivityOptionsCompat.
                            makeSceneTransitionAnimation((Activity) context, p1,
p2, p3, p4);
                    context.startActivity(intent, options.toBundle());
                }
            }
        });


Как сделать выбор, если он находится в активити1, то по нажатию на item, переходит
на одну страницу, если находится в активти2, то по нажатию на этот Же item, но в другую
страницу.

Item это отдельный layout, для recyclerview. 
    


Ответы

Ответ 1



В данном случае проще и архитектурно правильно прокинуть интерфейс из адаптера и регистрировать его в вызывающей активити, чем вешать действия слушателей прямо в адаптере. Данный подход позволит реализовать любую уникальную логику по клику в каждой вызывающей активити, без всяких костылей и нагромождения вариантов переходов в кликере адаптера. Примерный код (исключены все методы не относящиеся к реализации): class SomeAdapter extends RecyclerView.Adapter { interface OnCardClickListener { void onCardClick(View view, int position, boolean isValid); } private static OnCardClickListener mListener; public void setOnCardClickListener(OnCardClickListener listener) { mListener = listener; } public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ CardView mCard; public ItemHolder(View v) { super(v); mCard = (CardView) v.findViewById(R.id.bell_card); mCard.setOnClickListener(this); } @Override public void onClick(View v) { int position = getAdapterPosition(); boolean isValid = (position != RecyclerView.NO_POSITION); mListener.onCardClick(v, position, isValid); } } } Далее в активити используем так: public class SomeActivity extends Activity implements SomeAdapter.OnCardClickListener { RecyclerView mList; SomeAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mList = (RecyclerView) findViewById(R.id.list); mList.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new SomeAdapter(mData); mAdapter.setOnCardClickListener(this); mList.setAdapter(mAdapter); } @Override public void onCardClick(View view,final int position, boolean isValid) { if (isValid) { switch (view.getId()) { case R.id.button1: return true; default: return false; } } } } В каждой активити может быть своя реализация действий в переопределенном методе onCardClick(). Колбэк передает в активити View, по которому кликнули, позицию в адаптере и признак валидности этой позиции. Вы можете передавать из адаптера и другие произвольные данные, которые вам могут требоваться.

суббота, 11 января 2020 г.

RecyclerView - динамичное добавление header

#android #recyclerview #header


Мне приходит JSON массив с сервера с датой и количеством сообщений. Мне нужно сделать
список в котором дата будет находиться перед началом сообщений этой даты. Никак не
пойму как можно научить адаптер строить такой список. 

Помогите, покажите пальцем, куда копать? 

Есть примерный способ реализации:


Завести массив с датами сообщений в формате HH:mm
???

    


Ответы

Ответ 1



Организация данных такая, что нужно распарсить JSON в объекты модели, каждая модель будет содержать поле даты и поле записи. Коллекцию необходимо отсортировать по возрастанию даты. Для начала нам понадобится два типа разметок. Первая будет отображать дату и запись (layout.header), вторая - только запись (layout.record). Разметка layout.header включает в себя разметку layout.record через include. layout.record: layout.header: Логика определения, какой тип View выводить основана на сравнении даты текущей записи и прошлой - если они не равны, то выводить заголовок с датой и записью, иначе только запись. Нулевой элемент обрабатывается отдельно и всегда содержит дату и запись. Так же прошу обратить внимание на организацию блока switch - case в методе onBindViewHolder(). При выводе View с датой case выводит дату на разметку и "проваливается" на следующий case (нет оператора break), где на разметку выводится запись. Если заголовок не требуется, то на разметку дата не выводится (срабатывает второй case) class SomeAdapter extends RecyclerView.Adapter { private ArrayList mData; private final int TYPE_HEADER = 0; private final int TYPE_ITEM = 1; public SomeAdapter (ArrayList data) { mData = data; } @Override public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v; switch (viewType) { case TYPE_HEADER: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false); break; default: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.record, parent, false); } return new ItemHolder(v); } @Override public void onBindViewHolder( ItemHolder holder, int position) { int type = getItemViewType(position); switch (type) { case TYPE_HEADER: holder.mHeaderDate.setText(mData.get(position).getDate())); case TYPE_ITEM: holder.mItemRecord.setText(mData.get(position).getRecord()); break; } } @Override public int getItemCount() { return mData.size(); } @Override public int getItemViewType(int position) { if (isIdentType(position)) return TYPE_ITEM; return TYPE_HEADER; } private boolean isIdentType (int position ){ if (!(position == 0)&&(mData.get(position).getDate()).equals(mData.get(position-1).getDate())) return true; return false; } public static class ItemHolder extends RecyclerView.ViewHolder{ TextView mHeaderDate; TextView mItemRecord; public ItemHolder(View v) { super(v); mHeaderDate = (TextView) v.findViewById(R.id.date); mItemRecord = (TextView) v.findViewById(R.id.record); } } Естественно, разметки нужно оформить покрасивее как то, выделить дату можно другим фоном или другое. Данный пример - только идея.

пятница, 10 января 2020 г.

Хорошая практика в подгрузке списка

#android #recyclerview


У меня есть метод который возвращает некоторое количество записей и общее их число
в json, ответ примерно выглядит так:

{
    hits:100
    ...тут идут переданные записи
}


В параметрах запроса можно указать два параметра limit - число передаваемых записей
и page сдвиг по записям на limit.
Записей может быть около 1000 и грузить их сразу все очевидно нельзя. Я сделал некоторое
подобие автоматической подгрузки, работает оно примерно так:


Делается превый запрос с page 0 и limit 50 
После разбора ответа смотрим hits и делаем запрос еще раз. 
Повторять до достижения цели


Мне не нравится несколько моментов


Грузятся сразу все элементы 
При обновлении списка RecyclerView перескакивает на первый элемент


Как лучше поступить с этими двумя проблемами?
    


Ответы

Ответ 1



// 1. У вас на экране не все, только то что видите, все остальные элементы дестроятся, как только "ушли" за пределы экрана. // Для сохранения текущей позиции запомните высоту списка до //обновления, к примеру через `OnScrollListener`: // private int mScrollY; // private RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() { // @Override // public void onScrolled(RecyclerView recyclerView, int dx, int dy) { // super.onScrolled(recyclerView, dx, dy); // mScrollY += dy; // } // }; // После обновления данных вызывайте программно скролл // mRecyclerView.scrollBy(0, mStateScrollY); UPDATE Со временем было вычитано более правильное решение. Добавьте метод в ваш адаптер для RecyclerView public void setData(List data) { mStringData.addAll(data); // ваша коллекция данных для адаптера notifyItemInserted(getItemCount() - data.size()); } Как следствие без велосипедов адаптер добавит еще элементы в конец списка, без каких либо подергиваний или запоминании позиции скролла на экране.

RecyclerView как в ВК (убрать поля и скругление углов в CardView)

#java #android #recyclerview


Хочу сделать свой RecyclerView как в ВК, что бы не было этих в CardView закруглений
и отступов по краям.

Конкретно что бы статья доходила до боковых стенок и между статьями был промежуток.

Где про это почитать? или подскажите как сделать это?


    


Ответы

Ответ 1



Вы путаете понятия поля (margins) и отступы (paddings). Поля (данного элемента) – это расстояние между границей родительского элемента и границей данного (дочернего) элемента. Отступы (данного элемента) – это расстояние между границей данного элемента и границей его дочерних элементов. Вам нужно убрать поля (слева и справа) у виджета CardView. Самый простой виджет CardView можно построить вот так: Теперь добавим этому CardView поля (margins) сверху и снизу: И уберем скругление углов: Выглядеть это будет так (для наглядности я добавил внутрь CardView LinearLayout с двумя TextView): Вроде как получилось именно то, что Вам необходимо.

пятница, 27 декабря 2019 г.

Как добавить обработчик нажатия на элемент в RecyclerView?

#java #android #recyclerview


Мне нужен горизонтальный лист вью. Стал рыть в интернете, и нашел совет юзать RecyclerView.
Вроде это новая штука от гугл и тд. Вобщем вставил я его себе в проект. Выводит он
все нормально. Написал адаптер и тд. Но как теперь написать обработчик нажатия на айтем???
нашел реализацию этого всего вот тут RecyclerView onClick

Но в итоге получаю такое:

mRecyclerView.addOnItemTouchListener(
                new RecyclerItemClickListener(this, new RecyclerItemClickListener.OnItemClickListener() {
                    @Override public void onItemClick(View view, int position) {
                        Toast.makeText(DashboardActivity.this, "dfsdg"+ position,
Toast.LENGTH_LONG ).show();
                    }
                })
        );


как мне вместо "dfsdg"+ position вывести элемент моего списка??? гугл в очередной
раз меня огорчил.

ДОПОЛНИЛ

я добавил метод в адаптер:

public User getItem(int position){
    return userList.get(position);
}


теперь я имею возможность из вне вытащить свой айтем:

@Override
public void onItemClick(View view, int position) {
    Toast.makeText(DashboardActivity.this, ((RecyclerAdapter)mAdapter).getItem(position).getUserInfo().getName(),
Toast.LENGTH_LONG ).show();
}


Но мне не нравиться то, что я вынужден передавать mAdapter. В старом обычном листе
можно было обратиться к адаптеру из листвью. Тут этого нет, и как это сделать я не знаю. 

Как-то так:

public void onItemClick(AdapterView adapterView, View view, int i, long l) {
    adapterView.getAdapter().getItem(i)......
}


adapterView, а не передавать свой адаптер.
    


Ответы

Ответ 1



Для начала, почему же google не реализовала интерфейс onItemClickListener в своем новом виджете RecyclerView. Объясняется это тем, что данный интерфейс был весьма не совершенен и, в частности, создавал определенные трудности для обработки кликов на вложенных элементах айтема, также были определенные проблемы с получением реальной позиции и некоторые другие. Чтобы избавить себя от подобных проблем google решила доверить реализацию этого сомнительного действия самому программисту ... что же, какие у нас есть варианты. В первую очередь нужно понять, какого экшена мы чаще всего ожидаем от клика по элементу списка? Как правило это переход к другой активити со значением кликнутой позиции (каких то связанных с этой позицией данных адаптера и тп). Реализовать такое простое действие можно просто повешав слушатель прямо в адаптере. Здесь у нас есть доступ к текущей позиции и всем данным адаптера: @Override public void onBindViewHolder( ItemHolder holder, int position) { holder.someView.setOnClickListener (new View.OnClickListener() { @Override public void onClick(View v) { // action on click } }); } Однако IP696 в своем комментарии был прав, вешать сложную логику (если такая вдруг потребовалась для обработки клика по айтему) в адаптер не разумно. Следующий вариант - положить на доводы google и реализовать интерфейс onItemClicklListener() самостоятельно. При такой реализации мы можем передать "на сторону" через интерфейс множество полезных вещей: позицию, данные адаптера по кликнутому айтему, view айтема и тд. , но лишаем себя удобной возможности простым способом обрабатывать клики на дочерних элементах, как это можно было сделать в первом варианте и о чем предупреждал нас google. Кроме того, фактически, как правило, нет никакой прямой необходимости обрабатывать клик по айтему на "внешней" стороне. Пример: public class ListAdapterHolder extends RecyclerView.Adapter { OnItemClickListener mItemClickListener; @Override public ViewHolder onCreateViewHolder(ViewGroup parent , int viewType) { ... return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder , int position) { ... } public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public ViewHolder(View view) { super(view); view.setOnClickListener(this); ... //здесь можно повесить слушатель и на отдельные виджеты в айтеме } @Override public void onClick(View v) { if (mItemClickListener != null) { mItemClickListener.onItemClick(v, getAdapterPosition()); } } } public interface OnItemClickListener { public void onItemClick(View view , int position); } public void SetOnItemClickListener(final OnItemClickListener mItemClickListener) { this.mItemClickListener = mItemClickListener; } } Наиболее логичным решением ситуации я считаю решение обрабатывать клики в классе-холдере адаптера. Здесь у нас есть простой и прямой доступ ко всем элементам айтема, данные самого адаптера (так как класс вложен в адаптер) для простого добавления\изменения\удаления пунктов списка, кроме того через конструктор можно передать дополнительные данные, кроме, собственно View, также логика обработки кликов выведена из адаптера в статический холдер: public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { TextView mText; Button mButton; CardView mCard; public ItemHolder(View v) { super(v); mCard = (CardView) v.findViewById(R.id.item_card); mText = (TextView) v.findViewById(R.id.item_text); mButton = (Button) v.findViewById(R.id.item_button); mCard.setOnClickListener(this); mButton.setOnClickListener(this); } @Override public void onClick(View v) { int position = getAdapterPosition(); if (position != RecyclerView.NO_POSITION) { switch (v.getId()) { case R.id.card: itemClick(position); break; case R.id.button: buttonClick(position); } } } private void itemClick(int position){ //action on item click } private void buttonClick (int position){ //action on button click } } Так же, если клик нужно обработать в активити, то можно просто указать в xml-разметке айтема списка метод обработки клика (android:onClick) и реализовать его в активити. Подробнее

Ответ 2



Ответ @pavlofff верен. Единственный вопрос где обработать нажатие. В своем проекте мы используем для этого шину событий (Otto by Square). Что то вроде: public class Adapter extends RecyclerView.Adapter { @Inject Bus bus; @Override public void onBindViewHolder( ItemHolder holder, final int position) { holder.view.setOnClickListener (new View.OnClickListener() { @Override public void onClick(View v) { bus.post(new ItemClickedEvent(getItem(position))); } }); } } В нужном месте ловим ItemClickedEvent, который содержит в себе модель, ячейка с которой была выбрана. Странно, конечно, что Google не добавила callback как для listview.

recycler view не работает скролл [закрыт]

#android #recyclerview


        
             
                
                    
                        
                            Закрыт. Этот вопрос не по теме. Ответы на него в данный
момент не принимаются.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Update the question so it's
on-topic for Stack Overflow на русском.
                        
                        Закрыт 4 года назад.
                                                                                
           
                
        
Добрый день, столкнулся с проблемой. Тестировал приложение ранее на genymotion (nexus
4 android api 16), samsung gs iv (api 21), samsung gs iii (api 16), nexus 9 (api 23).
На каждом устройстве все работает нормально, решил запустить на попавшем в руки самсунге
gt-i8262 (api 16), почему то не работает скролл во всех RecyclerView. Разметки разные,
в большинстве разметок корневой и единственный элемент это recyclerview, не вижу вообще
возможностей проблемы, тем не менее скролл не работает. Никто не сталкивался с такой
проблемой?

Для примера привожу код, разметка такая 






Фрагмент, в котором используется разметка (выбрал самый не большой,  котором есть
все, и адаптер, и холдер и управление ресайклером).

public class NewsFragment extends Fragment {

    int currentLastArticle = 0;
    int count;
    ArrayList
news = new ArrayList<>(); View view; RecyclerView rv; LinearLayoutManager llm; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(R.layout.recycler_view, container, false); rv = (RecyclerView) view.findViewById(R.id.recycler); llm = new LinearLayoutManager(activity); rv.setLayoutManager(llm); rv.addItemDecoration(new Divider(getActivity())); rv.setOnScrollListener(new EndlessRecyclerScrollListener(llm) { @Override public void onLoadMore(int current_page) { new GetNewsTask().execute(20, currentLastArticle+=20); } }); new GetNewsTask().execute(20, currentLastArticle); return view; } class NewsRecyclerAdapter extends RecyclerView.Adapter { LayoutInflater inflater; public NewsRecyclerAdapter(Context context) { inflater = ((AppCompatActivity)context).getLayoutInflater(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new NewsVH(inflater.inflate(R.layout.news_item, parent, false)); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ((NewsVH)holder).set(position); } @Override public int getItemCount() { return news.size(); } private class NewsVH extends RecyclerView.ViewHolder{ Text title; Text time; ImageView image; View itemView; public NewsVH(View itemView) { super(itemView); this.itemView = itemView; itemView.setPadding( Decorator.getWidthBasedOnIPhone640(8), Decorator.getHeightBasedOnIPhone960(8), Decorator.getWidthBasedOnIPhone640(8), Decorator.getHeightBasedOnIPhone960(8) ); init(itemView); } private void init(View view) { title = (Text) view.findViewById(R.id.title); title.setTextSize(TypedValue.COMPLEX_UNIT_PX, Decorator.getHeightBasedOnIPhone960(24)); Decorator.setMargins(title, 0, 8, 0, 0); time = (Text) view.findViewById(R.id.time); time.setTextSize(TypedValue.COMPLEX_UNIT_PX, Decorator.getHeightBasedOnIPhone960(20)); time.setTextStyle(font.font133l); image = (ImageView) view.findViewById(R.id.image); image.setLayerType(View.LAYER_TYPE_SOFTWARE, null); Decorator.setSquareSizeAndMargins(image, 176, 0, 0, 8, 8); } public void set(final int position) { Article article = news.get(position); title.setText(article.title()); time.setText(article.time()); String image = article.files().length == 0 ? "null" : article.files()[0].url480(); ImageLoader.getInstance().displayImage(image, this.image, Decorator.getImageOptions(svg.zelen.drawable())); itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ArticleFragment fragment = new ArticleFragment(); Bundle bundle = new Bundle(); bundle.putParcelable(getString(R.string.article), news.get(position)); fragment.setArguments(bundle); activity.getSupportFragmentManager() .beginTransaction() .addToBackStack(Flags.CATALOG_BACKSTACK) .replace(R.id.container, fragment, fragment.getClass().getCanonicalName()) .commit(); } }); } } } class GetNewsTask extends AsyncTask { int lowTreshold; int highTreshold; @Override protected void onPreExecute() { ((ProgressSwitcher)activity).showProgress(); } @Override protected JSONArray doInBackground(Integer... params) { lowTreshold = params[1]; highTreshold = params[0]; String newsString = Web.httpGet( Web.url.domain + Web.url.content + "/" + highTreshold + "/" + lowTreshold ); JSONArray jNewsArray = new JSONArray(); try { JSONObject jNews = new JSONObject(newsString); jNewsArray = jNews.getJSONArray("content"); count = jNews.getInt("count"); } catch (JSONException e) { e.printStackTrace(); } return jNewsArray; } @Override protected void onPostExecute(JSONArray jArray) { ((ProgressSwitcher)activity).hideProgress(); int length = jArray.length(); for (int i = 0; i < length; i++) { try { news.add(new Article(jArray.getJSONObject(i))); } catch (JSONException e) { e.printStackTrace(); } } if (rv.getAdapter() == null) { rv.setAdapter(new NewsRecyclerAdapter(activity)); } else { rv.getAdapter().notifyDataSetChanged(); } } } } EDIT: Вчера ночью порешал проблему. Проблема была в вымученном дизайне и давней попытке решить один баг. У меня есть DrawerLayout, в котором при нажатии на итемы NavigationDrawer открываются фрагменты. В одном из фрагментов я должен был реализовать табы (фрагмент - страинца продукта, в табах комментарии, либо описание продукта), содержимое таба открывалось в отдельном контейнере. Как то раз решая баг я дошел до того, что в DrawerLayout поместил основной контейнер для фрагментов и контейнер для табов в скроллвью, высоту которого (и фрагмента в нем) задавал жестко и динамически (исходя из размеров девайса), чтобы скроллвью не скроллился кроме того единственного случая, когда мы видим содержимое таба. В результате на последнем самсунге я поймал баг той функции, которая считала высоту скроллвью, потому что достаточно было одного лишнего пикселя, чтобы он стал свободно болтаться, и получил у себя в приложении скроллящиеся View (RecyclerView) внутри сввободно плавающего ScrollView, что привело к тому, что экран вообще не реагироввал на мои жесты. В итоге я убрал все касающееся страницы продукта в разметку страницы продукта, и получил свою порцию счастья :) сем спасибо за участие и советы, а мне минус в карму, за то, что попытался вырвать задачу из контекста, который важен для её понимания.


Ответы

Ответ 1



Согласно en-SO на некоторых девайсах система несколько изменена и косячит. Решить сие можно грязным хаком - переопределением "неработающего" виджета и изменения некоторых методов. В частности, в вопросе по ссылке в логах была ошибка в методе scrollTo(). Засим посмотрите как по ссылке изменили этого метод, может помочь. Кратко - в методе не вызывают super.onScrollTo()

Как в recyclerview положить view? Как сделать границы прозрачными?

#android #recyclerview


Как в RecyclerView положить View? Мне нужно, чтобы в начале списка была картинка
и текст, которые скрываются при скролинге, как будто это часть RecyclerView. К сожалению,
не нашёл рабочий вариант.
И ещё, как можно сделать так, чтобы элементы в RecyclerView не перекрывались границами?
Нужно сделать так: если элемент перемещается за пределы (границы) RecyclerView, то
он все равно будет отображаться на экране, а не часть его, как реализовано стандартно.
Иными словами, сделать границы (и область заграницами) прозрачными.

UPDATE:


Мне нужно сделать из RecyclerView обычный контейнер. Точнее, я хочу вставить сторонний
View элемент, например, ImageView. При скролинге этот элемент скрывается, как будто
этот элемент является элементом RecyclerView. Схема:


Я делаю Drag and Drop с RecyclerView в виде сетки и хочу, чтобы при долгом нажатии
появлялась картинка, куда бы я перетащил выделенный элемент и удалил его. Возникла
проблема в прорисовке элементов за пределами RecyclerView - элементы не прорисовываются.
То есть нужно сделать, чтобы элементы порорисовывались поверх других View, вот так:




    


Ответы

Ответ 1



Итак, отвечаю на свой второй вопрос. Ответ пришел откуда не ждал, а должен был. Чтобы элементы RecyclerView прорисовывались поверх остальных, нужно указать android:clipToPadding="false" в разметке или recyclerView.setClipToPadding(false); в коде.

понедельник, 23 декабря 2019 г.

Отклик на касание по cardview

#android #recyclerview


Есть список кликабельных CardView построенных с помощью RecyclerView и кастомного
Адаптера со стандартной реализацией. ViewHolder имплементирует OnClickListener. Отсутствует
анимация "касания" элемента. Т.е. при нажатии на любое cardView нет никакого отклика,
что нажатие действительно произошло, срабатывает только лишь один OnClickListener. 

Как можно прикрутить анимацию ripple эффекта? Вероятно есть уже готовые реализации
от самого гугла, но найти ничего не получается почему-то, кроме такого. 
Попробовал сделать, анимация действительно появилась, но теперь не работает метод
OnClickListener :) Вернее, работает, но только если нажимать на края cardview (видимо
там где у меня определен layout_margin и куда анимация нажатия не достает). Как можно
решить проблему? Спасибо

разметка cardview.xml





    


Ответы

Ответ 1



Видимо это особенности разработки под Android, которые нужно просто знать. Убрал из разметки android:clickable="true" и все заработало как нужно. Надеюсь кому-нибудь поможет в будущем.