android - RecyclerView - Horizontal LinearLayoutManager create / bind methods called way too often -


currently i'm @ end of ideas on following issue linearlayoutmanagers , recyclerviews on android:

what scenario wanted achieve

a horizontal recyclerview on user can swipe fast without limitations on fling. items being fullscreen sized making them big recyclerview itself. when fling has stopped or user stops manually, recycler should scroll 1 item (mimicing viewpager bit) (i'm using support revision 25.1.0)

code snippets

the pager-class itself

public class velocitypager extends recyclerview {      private int mcurrentitem = 0;      @nonnull     private linearlayoutmanager mlayoutmanager;      @nullable     private onpagechangelistener monpagechangelistener = null;      @nonnull     private rect mviewrect = new rect();      @nonnull     private onscrolllistener monscrolllistener = new onscrolllistener() {          private int mlastitem = 0;          @override         public void onscrolled(recyclerview recyclerview, int dx, int dy) {             if (monpagechangelistener == null) return;             mcurrentitem = mlayoutmanager.findfirstvisibleitemposition();             final view view = mlayoutmanager.findviewbyposition(mcurrentitem);             view.getlocalvisiblerect(mviewrect);             final float offset = (float) mviewrect.left / ((view) view.getparent()).getwidth();             monpagechangelistener.onpagescrolled(mcurrentitem, offset, 0);             if (mcurrentitem != mlastitem) {                 monpagechangelistener.onpageselected(mcurrentitem);                 mlastitem = mcurrentitem;             }         }          @override         public void onscrollstatechanged(recyclerview recyclerview, int newstate) {             if (monpagechangelistener == null) return;             monpagechangelistener.onpagescrollstatechanged(newstate);         }      };      public velocitypager(@nonnull context context) {         this(context, null);     }      public velocitypager(@nonnull context context, @nullable attributeset attrs) {         this(context, attrs, 0);     }      public velocitypager(@nonnull context context, @nullable attributeset attrs, int defstyle) {         super(context, attrs, defstyle);         mlayoutmanager = createlayoutmanager();         init();     }      @nonnull     private linearlayoutmanager createlayoutmanager() {         return new linearlayoutmanager(getcontext(), linearlayoutmanager.horizontal, false);     }      @override     protected void onattachedtowindow() {         super.onattachedtowindow();         addonscrolllistener(monscrolllistener);     }      @override     protected void ondetachedfromwindow() {         super.ondetachedfromwindow();         removeonscrolllistener(monscrolllistener);     }      @override     public void onscrollstatechanged(int state) {         // if tap on phone while recyclerview scrolling stop in middle.         // code fixes this. code not strictly necessary improves behaviour.         if (state == scroll_state_idle) {             linearlayoutmanager linearlayoutmanager = (linearlayoutmanager) getlayoutmanager();              int screenwidth = resources.getsystem().getdisplaymetrics().widthpixels;              // views on screen             int lastvisibleitemposition = linearlayoutmanager.findlastvisibleitemposition();             view lastview = linearlayoutmanager.findviewbyposition(lastvisibleitemposition);             int firstvisibleitemposition = linearlayoutmanager.findfirstvisibleitemposition();             view firstview = linearlayoutmanager.findviewbyposition(firstvisibleitemposition);              // distance need scroll             int leftmargin = (screenwidth - lastview.getwidth()) / 2;             int rightmargin = (screenwidth - firstview.getwidth()) / 2 + firstview.getwidth();             int leftedge = lastview.getleft();             int rightedge = firstview.getright();             int scrolldistanceleft = leftedge - leftmargin;             int scrolldistanceright = rightmargin - rightedge;              if (leftedge > screenwidth / 2) {                 smoothscrollby(-scrolldistanceright, 0);             } else if (rightedge < screenwidth / 2) {                 smoothscrollby(scrolldistanceleft, 0);             }         }     }      private void init() {         setlayoutmanager(mlayoutmanager);         setitemanimator(new defaultitemanimator());         sethasfixedsize(true);     }      public void setcurrentitem(int index, boolean smoothscroll) {         if (monpagechangelistener != null) {             monpagechangelistener.onpageselected(index);         }         if (smoothscroll) smoothscrolltoposition(index);         if (!smoothscroll) scrolltoposition(index);     }      public int getcurrentitem() {         return mcurrentitem;     }      public void setonpagechangelistener(@nullable onpagechangelistener onpagechangelistener) {         monpagechangelistener = onpagechangelistener;     }      public interface onpagechangelistener {          /**          * method invoked when current page scrolled, either part          * of programmatically initiated smooth scroll or user initiated touch scroll.          *          * @param position             position index of first page being displayed.          *                             page position+1 visible if positionoffset nonzero.          * @param positionoffset       value [0, 1) indicating offset page @ position.          * @param positionoffsetpixels value in pixels indicating offset position.          */         void onpagescrolled(int position, float positionoffset, int positionoffsetpixels);          /**          * method invoked when new page becomes selected. animation not          * complete.          *          * @param position position index of new selected page.          */         void onpageselected(int position);          /**          * called when scroll state changes. useful discovering when user          * begins dragging, when pager automatically settling current page,          * or when stopped/idle.          *          * @param state new scroll state.          * @see velocitypager#scroll_state_idle          * @see velocitypager#scroll_state_dragging          * @see velocitypager#scroll_state_settling          */         void onpagescrollstatechanged(int state);      }  } 

the item's xml layout

(note: root view has clickable other purposes inside app)

<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:clickable="true">      <linearlayout         android:id="@+id/icon_container_top"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_alignparentend="true"         android:layout_alignparentright="true"         android:layout_alignparenttop="true"         android:layout_gravity="top|end"         android:layout_marginend="16dp"         android:layout_marginright="16dp"         android:layout_margintop="16dp"         android:alpha="0"         android:background="@drawable/info_background"         android:orientation="horizontal"         android:padding="4dp"         tools:alpha="1">          <imageview             android:id="@+id/delete"             style="@style/selectableitembackground"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:clickable="true"             android:contentdescription="@string/desc_delete"             android:padding="12dp"             android:src="@drawable/ic_delete_white_24dp"             android:tint="@color/icons" />      </linearlayout>      <linearlayout         android:id="@+id/icon_container_bottom"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_alignparentend="true"         android:layout_alignparentright="true"         android:layout_centervertical="true"         android:layout_marginbottom="16dp"         android:layout_marginend="16dp"         android:layout_marginright="16dp"         android:alpha="0"         android:background="@drawable/info_background"         android:orientation="vertical"         android:padding="4dp"         tools:alpha="1">          <imageview             android:id="@+id/size"             style="@style/selectableitembackground"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:clickable="true"             android:contentdescription="@string/desc_size"             android:padding="12dp"             android:src="@drawable/ic_straighten_white_24dp"             android:tint="@color/icons" />          <imageview             android:id="@+id/palette"             style="@style/selectableitembackground"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:clickable="true"             android:contentdescription="@string/desc_palette"             android:padding="12dp"             android:src="@drawable/ic_palette_white_24dp"             android:tint="@color/icons" />      </linearlayout> </relativelayout> 

the xml layout pager itself

(quite nested? might cause of problem? don't know... )

<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.drawerlayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     xmlns:tools="http://schemas.android.com/tools"     android:id="@+id/drawer"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:fitssystemwindows="true"     tools:opendrawer="end">      <swiperefreshlayout         android:id="@+id/refresh_layout"         android:layout_width="match_parent"         android:layout_height="match_parent">          <android.support.design.widget.coordinatorlayout             android:id="@+id/coordinator"             android:layout_width="match_parent"             android:layout_height="match_parent"             android:fitssystemwindows="false">              <framelayout                 android:id="@+id/container"                 android:layout_width="match_parent"                 android:layout_height="match_parent" />              <com.my.example.optionalviewpager                 android:id="@+id/view_pager"                 android:layout_width="match_parent"                 android:layout_height="match_parent"                 android:scrollbars="horizontal"                 app:layout_behavior="com.my.example.moveupbehavior" />              <android.support.v7.widget.toolbar                 android:id="@+id/toolbar"                 android:layout_width="match_parent"                 android:layout_height="?attr/actionbarsize"                 android:background="@android:color/transparent"                 android:clickable="false"                 android:fitssystemwindows="false"                 app:contentinsetleft="0dp"                 app:contentinsetstart="0dp"                 app:contentinsetstartwithnavigation="0dp"                 app:layout_collapsemode="pin"                 app:navigationicon="@drawable/ic_menu_white_24dp" />          </android.support.design.widget.coordinatorlayout>      </swiperefreshlayout>      <include layout="@layout/layout_drawer" />  </android.support.v4.widget.drawerlayout> 

part of adapter relevant viewholders

@override     public int getitemcount() {         return dataset.size();     }      @override     public myviewholder oncreateviewholder(viewgroup parent, int viewtype) {         log.v("adapter", "createviewholder");         final layoutinflater layoutinflater = layoutinflater.from(parent.getcontext());         final view rootview = layoutinflater.inflate(r.layout.page, parent, false);         return new myviewholder(rootview);     }      @override     public void onbindviewholder(myviewholder page, int position) {         log.v("adapter", string.format("bindviewholder(%d)", position));         final viewdata viewdata = dataset.get(position);         page.bind(viewdata);         listener.onviewadded(position, viewdata.getdata());     }      @override     public void onviewrecycled(myviewholder page) {         if (page.getdata() == null) return;         listener.onviewremoved(page.getdata().id);     }      @override     public int getitemviewtype(int position) {         return 0;     } 

the viewholder

public class myviewholder extends recyclerview.viewholder implements mylistener {      @bindview(r.id.info_container)     viewgroup minfocontainer;      @bindview(r.id.icon_container_top)     viewgroup miconcontainertop;      @bindview(r.id.icon_container_bottom)     viewgroup miconcontainerbottom;      @bindview(r.id.info_rows)     viewgroup minforows;      @bindview(r.id.loading)     view micloading;      @bindview(r.id.sync_status)     view micsyncstatus;      @bindview(r.id.delete)     view micdelete;      @bindview(r.id.ic_fav)     view micfavorite;      @bindview(r.id.size)     view micsize;      @bindview(r.id.palette)     view micpalette;      @bindview(r.id.name)     textview mname;      @bindview(r.id.length)     textview mlength;      @bindview(r.id.threads)     textview mthreads;      @bindview(r.id.price)     textview mprice;      @nullable     private mymodel mmodel = null;      @nullable     private activity mactivity;      public myviewholder(view itemview) {         super(itemview);         butterknife.bind(this, itemview);         mactivity= (activity) itemview.getcontext();         if (mactivity!= null) mactivity.addmylistener(this);     }      @onclick(r.id.delete)     protected void clickdeletebtn() {         if (mactivity == null || mactivity.getmode() != mode.edit) return;         if (mmodel == null) return;         animations.pop(micdelete);         final int modelid = mmodel.id;         if (mmodel.delete()) {             mactivity.delete(modelid);         }     }      @onclick(r.id.size)     protected void clicksizebtn() {         if (mactivity== null) return;         mactivity.setuimode(mode.edit_size);         animations.pop(micsize);     }      @onclick(r.id.palette)     protected void clickpalettebtn() {         if (mactivity== null) return;         mactivity.setuimode(mode.edit_length);         animations.pop(micpalette);     }      private void initmodelviews() {         if (mdata == null) return;         final locale locale = locale.getdefault();         mname.setvalue(string.format(locale, "model#%d", mmodel.id));         mlength.setvalue(html.fromhtml(string.format(locale, itemview.getcontext().getstring(r.string.template_length), mmodel.meters)));     }      /**      * set icon container off screen @ beginning      */     private void prepareviews() {         new expectanim().expect(miconcontainertop).tobe(outofscreen(gravity.end), visible())                 .toanimation()                 .setnow();         new expectanim().expect(miconcontainerbottom).tobe(outofscreen(gravity.end), visible())                 .toanimation()                 .setnow();      }      @nullable     public mymodel getdata() {         return mmodel;     }      private void enableedit() {         new expectanim()                 .expect(miconcontainerbottom)                 .tobe(atitsoriginalposition())                 .toanimation()                 .start();     }      private void disableedit() {         new expectanim()                 .expect(miconcontainerbottom)                 .tobe(outofscreen(gravity.end))                 .toanimation()                 .start();     }      private void enableinfo() {         new expectanim()                 .expect(minfocontainer)                 .tobe(atitsoriginalposition())                 .toanimation()                 .start();     }      private void disableinfo() {         new expectanim()                 .expect(minfocontainer)                 .tobe(outofscreen(gravity.bottom))                 .toanimation()                 .start();     }      private void enabledelete() {         if (miconcontainertop == null) return;         new expectanim()                 .expect(miconcontainertop)                 .tobe(atitsoriginalposition(), visible())                 .toanimation()                 .start();     }      private void disabledelete() {         if (miconcontainertop == null) return;         new expectanim()                 .expect(miconcontainertop)                 .tobe(outofscreen(gravity.end), invisible())                 .toanimation()                 .start();     }      public void bind(@nonnull final viewdata viewdata) {         mmodel = viewdata.getdata();         prepareviews();         initmodelviews();     }  } 

so, here's issue these!

when intializing adapter insert 15 17 items via observable. seems correct:

logging initializing okay

but when swiping horizontally recyclerview's callbacks seem totally messed , produce weird results:

messed loggings

do see recycler not try recycle old viewholders @ all? image shows small portion of "spamming" going on. create new viewholder more 2 times same position while scroll recycler slowly!

onbind spam

another side problem is: listener should allow me pass bind / recycle events underlying game engine create destroy entities on screen. due excessive spamming of events create entities excessively!

i excpected recycler create new viewholder first (let's in example 17) times , reuse items how should.

please help, i'm stuck on problem 2 days , i'm frustrated after searching people same issues without luck. thank you!

there's problem viewholder recycling. i'm guessing animations you're running inside myviewholder might prevent recyclerview recycling holders properly. make sure cancel animations @ point, e.g. in recyclerview.adapter#onviewdetachedfromwindow().

after you've fixed this, suggest follow @eugenpechanec's suggestion reduce amount of custom calculations done in onscrolllisteners. it's better rely on support library classes , tweak behavior little.


Comments

Popular posts from this blog

javascript - Clear button on addentry page doesn't work -

c# - Selenium Authentication Popup preventing driver close or quit -

tensorflow when input_data MNIST_data , zlib.error: Error -3 while decompressing: invalid block type -