解决RecyclerView无法onItemClick问题的两种方法
对于recyclerview的使用,大家可以查看将替代listview的recyclerview 的使用详解(一)单单从代码结构来说recyclerview确实比listview优化了很多,也简化了我们编写代码量,但是有一个问题会导致开发者不会去用它,更比说替换listview了,我不知道使用过recyclerview的人有没有进一步查看,recyclerview没有提供item的点击事件,我们使用列表不仅仅为了显示数据,同时也可以能会交互,所以recyclerview这个问题导致基本没有人用它,我清楚谷歌是怎么想的,不过recyclerview也并没有把所有的路给堵死,需要我们写代码来实现item的点击事件,我们都知道recyclerview里面新加了viewholder这个静态抽象类,这个类里面有一个方法getposition()可以返回当前viewholder实例的位置,实现onitemclick就是使用它来做的,下面有两种方法来实现:
第一种:不修改源码
这种方法不修改源码,问题是只能在recyclerview.adapter中实现itemclick事件
public static class viewholder extends recyclerview.viewholder { public viewholder(view itemview) { super(itemview); itemview.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { log.e("jwzhangjie", "当前点击的位置:"+getposition()); } }); } }
这种方式直观上看起来不太好,不过也可以实现itemclick事件。
第二种方法:修改recyclerview源码
1、把在recyclerview类里面定义onitemclicklistener接口
/** * interface definition for a callback to be invoked when an item in this * recyclerview.adapter has been clicked. */ public interface onitemclicklistener { /** * callback method to be invoked when an item in this recyclerview.adapter has * been clicked. * <p> * implementers can call getposition(position) if they need * to access the data associated with the selected item. * * @param view the view within the recyclerview.adapter that was clicked (this * will be a view provided by the adapter) * @param position the position of the view in the adapter. */ void onitemclick(view view, int position); } public static onitemclicklistener monitemclicklistener = null; /** * register a callback to be invoked when an item in this adapterview has * been clicked. * * @param listener the callback that will be invoked. */ public void setonitemclicklistener(onitemclicklistener listener) { monitemclicklistener = listener; } /** * @return the callback to be invoked with an item in this adapterview has * been clicked, or null id no callback has been set. */ public final onitemclicklistener getonitemclicklistener() { return monitemclicklistener; }
2、在recyclerview中的抽象类viewholder中添加view的点击事件
public static abstract class viewholder implements onclicklistener{ public final view itemview; int mposition = no_position; int moldposition = no_position; long mitemid = no_id; int mitemviewtype = invalid_type; /** * this viewholder has been bound to a position; mposition, mitemid and mitemviewtype * are all valid. */ static final int flag_bound = 1 << 0; /** * the data this viewholder's view reflects is stale and needs to be rebound * by the adapter. mposition and mitemid are consistent. */ static final int flag_update = 1 << 1; /** * this viewholder's data is invalid. the identity implied by mposition and mitemid * are not to be trusted and may no longer match the item view type. * this viewholder must be fully rebound to different data. */ static final int flag_invalid = 1 << 2; /** * this viewholder points at data that represents an item previously removed from the * data set. its view may still be used for things like outgoing animations. */ static final int flag_removed = 1 << 3; /** * this viewholder should not be recycled. this flag is set via setisrecyclable() * and is intended to keep views around during animations. */ static final int flag_not_recyclable = 1 << 4; private int mflags; private int misrecyclablecount = 0; // if non-null, view is currently considered scrap and may be reused for other data by the // scrap container. private recycler mscrapcontainer = null; @override public void onclick(view v) { if (monitemclicklistener != null) { monitemclicklistener.onitemclick(itemview, getposition()); } } public viewholder(view itemview) { if (itemview == null) { throw new illegalargumentexception("itemview may not be null"); } this.itemview = itemview; this.itemview.setonclicklistener(this); } void offsetposition(int offset) { if (moldposition == no_position) { moldposition = mposition; } mposition += offset; } void clearoldposition() { moldposition = no_position; } public final int getposition() { return moldposition == no_position ? mposition : moldposition; } public final long getitemid() { return mitemid; } public final int getitemviewtype() { return mitemviewtype; } boolean isscrap() { return mscrapcontainer != null; } void unscrap() { mscrapcontainer.unscrapview(this); mscrapcontainer = null; } void setscrapcontainer(recycler recycler) { mscrapcontainer = recycler; } boolean isinvalid() { return (mflags & flag_invalid) != 0; } boolean needsupdate() { return (mflags & flag_update) != 0; } boolean isbound() { return (mflags & flag_bound) != 0; } boolean isremoved() { return (mflags & flag_removed) != 0; } void setflags(int flags, int mask) { mflags = (mflags & ~mask) | (flags & mask); } void addflags(int flags) { mflags |= flags; } void clearflagsforsharedpool() { mflags = 0; } @override public string tostring() { final stringbuilder sb = new stringbuilder("viewholder{" + integer.tohexstring(hashcode()) + " position=" + mposition + " id=" + mitemid); if (isscrap()) sb.append(" scrap"); if (isinvalid()) sb.append(" invalid"); if (!isbound()) sb.append(" unbound"); if (needsupdate()) sb.append(" update"); if (isremoved()) sb.append(" removed"); sb.append("}"); return sb.tostring(); }
3、完成上面的步骤,就可以使用recyclerview来完成itemclick事件了
cashaccountlist.setonitemclicklistener(new onitemclicklistener() { @override public void onitemclick(view view, int position) { applog.e("position: "+position); } });
下面是完整的recyclerview源码:
/* * copyright (c) 2013 the android open source project * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ package android.support.v7.widget; import android.content.context; import android.database.observable; import android.graphics.canvas; import android.graphics.pointf; import android.graphics.rect; import android.os.build; import android.os.parcel; import android.os.parcelable; import android.support.annotation.nullable; import android.support.v4.util.arraymap; import android.support.v4.util.pools; import android.support.v4.view.motioneventcompat; import android.support.v4.view.velocitytrackercompat; import android.support.v4.view.viewcompat; import android.support.v4.widget.edgeeffectcompat; import android.support.v4.widget.scrollercompat; import android.util.attributeset; import android.util.log; import android.util.sparsearray; import android.util.sparseintarray; import android.view.focusfinder; import android.view.motionevent; import android.view.velocitytracker; import android.view.view; import android.view.viewconfiguration; import android.view.viewgroup; import android.view.viewparent; import android.view.animation.interpolator; import java.util.arraylist; import java.util.collections; import java.util.list; /** * a flexible view for providing a limited window into a large data set. * * <h3>glossary of terms:</h3> * * <ul> * <li><em>adapter:</em> a subclass of {@link adapter} responsible for providing views * that represent items in a data set.</li> * <li><em>position:</em> the position of a data item within an <em>adapter</em>.</li> * <li><em>index:</em> the index of an attached child view as used in a call to * {@link viewgroup#getchildat}. contrast with <em>position.</em></li> * <li><em>binding:</em> the process of preparing a child view to display data corresponding * to a <em>position</em> within the adapter.</li> * <li><em>recycle (view):</em> a view previously used to display data for a specific adapter * position may be placed in a cache for later reuse to display the same type of data again * later. this can drastically improve performance by skipping initial layout inflation * or construction.</li> * <li><em>scrap (view):</em> a child view that has entered into a temporarily detached * state during layout. scrap views may be reused without becoming fully detached * from the parent recyclerview, either unmodified if no rebinding is required or modified * by the adapter if the view was considered <em>dirty</em>.</li> * <li><em>dirty (view):</em> a child view that must be rebound by the adapter before * being displayed.</li> * </ul> */ public class recyclerview extends viewgroup { private static final string tag = "recyclerview"; private static final boolean debug = false; private static final boolean enable_predictive_animations = false; private static final boolean dispatch_temp_detach = false; public static final int horizontal = 0; public static final int vertical = 1; public static final int no_position = -1; public static final long no_id = -1; public static final int invalid_type = -1; private static final int max_scroll_duration = 2000; private final recyclerviewdataobserver mobserver = new recyclerviewdataobserver(); private final recycler mrecycler = new recycler(); private savedstate mpendingsavedstate; /** * note: this runnable is only ever posted if: * 1) we've been through first layout * 2) we know we have a fixed size (mhasfixedsize) * 3) we're attached */ private final runnable mupdatechildviewsrunnable = new runnable() { public void run() { if (mpendingupdates.isempty()) { return; } eatrequestlayout(); updatechildviews(); resumerequestlayout(true); } }; private final rect mtemprect = new rect(); private final arraylist<updateop> mpendingupdates = new arraylist<updateop>(); private final arraylist<updateop> mpendinglayoutupdates = new arraylist<updateop>(); private pools.pool<updateop> mupdateoppool = new pools.simplepool<updateop>(updateop.pool_size); private adapter madapter; private layoutmanager mlayout; private recyclerlistener mrecyclerlistener; private final arraylist<itemdecoration> mitemdecorations = new arraylist<itemdecoration>(); private final arraylist<onitemtouchlistener> monitemtouchlisteners = new arraylist<onitemtouchlistener>(); private onitemtouchlistener mactiveonitemtouchlistener; private boolean misattached; private boolean mhasfixedsize; private boolean mfirstlayoutcomplete; private boolean meatrequestlayout; private boolean mlayoutrequesteaten; private boolean madapterupdateduringmeasure; private final boolean mpostupdatesonanimation; private edgeeffectcompat mleftglow, mtopglow, mrightglow, mbottomglow; itemanimator mitemanimator = new defaultitemanimator(); private static final int invalid_pointer = -1; /** * the recyclerview is not currently scrolling. * @see #getscrollstate() */ public static final int scroll_state_idle = 0; /** * the recyclerview is currently being dragged by outside input such as user touch input. * @see #getscrollstate() */ public static final int scroll_state_dragging = 1; /** * the recyclerview is currently animating to a final position while not under * outside control. * @see #getscrollstate() */ public static final int scroll_state_settling = 2; // touch/scrolling handling private int mscrollstate = scroll_state_idle; private int mscrollpointerid = invalid_pointer; private velocitytracker mvelocitytracker; private int minitialtouchx; private int minitialtouchy; private int mlasttouchx; private int mlasttouchy; private final int mtouchslop; private final int mminflingvelocity; private final int mmaxflingvelocity; private final viewflinger mviewflinger = new viewflinger(); private final state mstate = new state(); private onscrolllistener mscrolllistener; // for use in item animations boolean mitemsaddedorremoved = false; boolean mitemschanged = false; int manimatingviewindex = -1; int mnumanimatingviews = 0; boolean minprelayout = false; private itemanimator.itemanimatorlistener mitemanimatorlistener = new itemanimatorrestorelistener(); private boolean mpostedanimatorrunner = false; private runnable mitemanimatorrunner = new runnable() { @override public void run() { if (mitemanimator != null) { mitemanimator.runpendinganimations(); } mpostedanimatorrunner = false; } }; private static final interpolator squinticinterpolator = new interpolator() { public float getinterpolation(float t) { t -= 1.0f; return t * t * t * t * t + 1.0f; } }; public recyclerview(context context) { this(context, null); } public recyclerview(context context, attributeset attrs) { this(context, attrs, 0); } public recyclerview(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); final int version = build.version.sdk_int; mpostupdatesonanimation = version >= 16; final viewconfiguration vc = viewconfiguration.get(context); mtouchslop = vc.getscaledtouchslop(); mminflingvelocity = vc.getscaledminimumflingvelocity(); mmaxflingvelocity = vc.getscaledmaximumflingvelocity(); setwillnotdraw(viewcompat.getoverscrollmode(this) == viewcompat.over_scroll_never); mitemanimator.setlistener(mitemanimatorlistener); } /** * recyclerview can perform several optimizations if it can know in advance that changes in * adapter content cannot change the size of the recyclerview itself. * if your use of recyclerview falls into this category, set this to true. * * @param hasfixedsize true if adapter changes cannot affect the size of the recyclerview. */ public void sethasfixedsize(boolean hasfixedsize) { mhasfixedsize = hasfixedsize; } /** * @return true if the app has specified that changes in adapter content cannot change * the size of the recyclerview itself. */ public boolean hasfixedsize() { return mhasfixedsize; } /** * set a new adapter to provide child views on demand. * * @param adapter the new adapter to set, or null to set no adapter. */ public void setadapter(adapter adapter) { if (madapter != null) { madapter.unregisteradapterdataobserver(mobserver); } // end all running animations if (mitemanimator != null) { mitemanimator.endanimations(); } // since animations are ended, mlayout.children should be equal to recyclerview.children. // this may not be true if item animator's end does not work as expected. (e.g. not release // children instantly). it is safer to use mlayout's child count. if (mlayout != null) { mlayout.removeandrecycleallviews(mrecycler); mlayout.removeandrecyclescrapint(mrecycler, true); } final adapter oldadapter = madapter; madapter = adapter; if (adapter != null) { adapter.registeradapterdataobserver(mobserver); } if (mlayout != null) { mlayout.onadapterchanged(oldadapter, madapter); } mrecycler.onadapterchanged(oldadapter, madapter); mstate.mstructurechanged = true; markknownviewsinvalid(); requestlayout(); } /** * retrieves the previously set adapter or null if no adapter is set. * * @return the previously set adapter * @see #setadapter(adapter) */ public adapter getadapter() { return madapter; } /** * register a listener that will be notified whenever a child view is recycled. * * <p>this listener will be called when a layoutmanager or the recyclerview decides * that a child view is no longer needed. if an application associates expensive * or heavyweight data with item views, this may be a good place to release * or free those resources.</p> * * @param listener listener to register, or null to clear */ public void setrecyclerlistener(recyclerlistener listener) { mrecyclerlistener = listener; } /** * set the {@link layoutmanager} that this recyclerview will use. * * <p>in contrast to other adapter-backed views such as {@link android.widget.listview} * or {@link android.widget.gridview}, recyclerview allows client code to provide custom * layout arrangements for child views. these arrangements are controlled by the * {@link layoutmanager}. a layoutmanager must be provided for recyclerview to function.</p> * * <p>several default strategies are provided for common uses such as lists and grids.</p> * * @param layout layoutmanager to use */ public void setlayoutmanager(layoutmanager layout) { if (layout == mlayout) { return; } mrecycler.clear(); removeallviews(); if (mlayout != null) { if (misattached) { mlayout.ondetachedfromwindow(this); } mlayout.mrecyclerview = null; } mlayout = layout; if (layout != null) { if (layout.mrecyclerview != null) { throw new illegalargumentexception("layoutmanager " + layout + " is already attached to a recyclerview: " + layout.mrecyclerview); } layout.mrecyclerview = this; if (misattached) { mlayout.onattachedtowindow(this); } } requestlayout(); } @override protected parcelable onsaveinstancestate() { savedstate state = new savedstate(super.onsaveinstancestate()); if (mpendingsavedstate != null) { state.copyfrom(mpendingsavedstate); } else if (mlayout != null) { state.mlayoutstate = mlayout.onsaveinstancestate(); } else { state.mlayoutstate = null; } return state; } @override protected void onrestoreinstancestate(parcelable state) { mpendingsavedstate = (savedstate) state; super.onrestoreinstancestate(mpendingsavedstate.getsuperstate()); if (mlayout != null && mpendingsavedstate.mlayoutstate != null) { mlayout.onrestoreinstancestate(mpendingsavedstate.mlayoutstate); } } /** * adds a view to the animatingviews list. * manimatingviews holds the child views that are currently being kept around * purely for the purpose of being animated out of view. they are drawn as a regular * part of the child list of the recyclerview, but they are invisible to the layoutmanager * as they are managed separately from the regular child views. * @param view the view to be removed */ private void addanimatingview(view view) { boolean alreadyadded = false; if (mnumanimatingviews > 0) { for (int i = manimatingviewindex; i < getchildcount(); ++i) { if (getchildat(i) == view) { alreadyadded = true; break; } } } if (!alreadyadded) { if (mnumanimatingviews == 0) { manimatingviewindex = getchildcount(); } ++mnumanimatingviews; addview(view); } mrecycler.unscrapview(getchildviewholder(view)); } /** * removes a view from the animatingviews list. * @param view the view to be removed * @see #addanimatingview(view) */ private void removeanimatingview(view view) { if (mnumanimatingviews > 0) { for (int i = manimatingviewindex; i < getchildcount(); ++i) { if (getchildat(i) == view) { removeviewat(i); --mnumanimatingviews; if (mnumanimatingviews == 0) { manimatingviewindex = -1; } mrecycler.recycleview(view); return; } } } } private view getanimatingview(int position, int type) { if (mnumanimatingviews > 0) { for (int i = manimatingviewindex; i < getchildcount(); ++i) { final view view = getchildat(i); viewholder holder = getchildviewholder(view); if (holder.getposition() == position && ( type == invalid_type || holder.getitemviewtype() == type)) { return view; } } } return null; } /** * return the {@link layoutmanager} currently responsible for * layout policy for this recyclerview. * * @return the currently bound layoutmanager */ public layoutmanager getlayoutmanager() { return mlayout; } /** * retrieve this recyclerview's {@link recycledviewpool}. this method will never return null; * if no pool is set for this view a new one will be created. see * {@link #setrecycledviewpool(recycledviewpool) setrecycledviewpool} for more information. * * @return the pool used to store recycled item views for reuse. * @see #setrecycledviewpool(recycledviewpool) */ public recycledviewpool getrecycledviewpool() { return mrecycler.getrecycledviewpool(); } /** * recycled view pools allow multiple recyclerviews to share a common pool of scrap views. * this can be useful if you have multiple recyclerviews with adapters that use the same * view types, for example if you have several data sets with the same kinds of item views * displayed by a {@link android.support.v4.view.viewpager viewpager}. * * @param pool pool to set. if this parameter is null a new pool will be created and used. */ public void setrecycledviewpool(recycledviewpool pool) { mrecycler.setrecycledviewpool(pool); } /** * set the number of offscreen views to retain before adding them to the potentially shared * {@link #getrecycledviewpool() recycled view pool}. * * <p>the offscreen view cache stays aware of changes in the attached adapter, allowing * a layoutmanager to reuse those views unmodified without needing to return to the adapter * to rebind them.</p> * * @param size number of views to cache offscreen before returning them to the general * recycled view pool */ public void setitemviewcachesize(int size) { mrecycler.setviewcachesize(size); } /** * return the current scrolling state of the recyclerview. * * @return {@link #scroll_state_idle}, {@link #scroll_state_dragging} or * {@link #scroll_state_settling} */ public int getscrollstate() { return mscrollstate; } private void setscrollstate(int state) { if (state == mscrollstate) { return; } mscrollstate = state; if (state != scroll_state_settling) { stopscroll(); } if (mscrolllistener != null) { mscrolllistener.onscrollstatechanged(state); } } /** * add an {@link itemdecoration} to this recyclerview. item decorations can * affect both measurement and drawing of individual item views. * * <p>item decorations are ordered. decorations placed earlier in the list will * be run/queried/drawn first for their effects on item views. padding added to views * will be nested; a padding added by an earlier decoration will mean further * item decorations in the list will be asked to draw/pad within the previous decoration's * given area.</p> * * @param decor decoration to add * @param index position in the decoration chain to insert this decoration at. if this value * is negative the decoration will be added at the end. */ public void additemdecoration(itemdecoration decor, int index) { if (mitemdecorations.isempty()) { setwillnotdraw(false); } if (index < 0) { mitemdecorations.add(decor); } else { mitemdecorations.add(index, decor); } markitemdecorinsetsdirty(); requestlayout(); } /** * add an {@link itemdecoration} to this recyclerview. item decorations can * affect both measurement and drawing of individual item views. * * <p>item decorations are ordered. decorations placed earlier in the list will * be run/queried/drawn first for their effects on item views. padding added to views * will be nested; a padding added by an earlier decoration will mean further * item decorations in the list will be asked to draw/pad within the previous decoration's * given area.</p> * * @param decor decoration to add */ public void additemdecoration(itemdecoration decor) { additemdecoration(decor, -1); } /** * remove an {@link itemdecoration} from this recyclerview. * * <p>the given decoration will no longer impact the measurement and drawing of * item views.</p> * * @param decor decoration to remove * @see #additemdecoration(itemdecoration) */ public void removeitemdecoration(itemdecoration decor) { mitemdecorations.remove(decor); if (mitemdecorations.isempty()) { setwillnotdraw(viewcompat.getoverscrollmode(this) == viewcompat.over_scroll_never); } markitemdecorinsetsdirty(); requestlayout(); } /** * set a listener that will be notified of any changes in scroll state or position. * * @param listener listener to set or null to clear */ public void setonscrolllistener(onscrolllistener listener) { mscrolllistener = listener; } /** * convenience method to scroll to a certain position. * * recyclerview does not implement scrolling logic, rather forwards the call to * {@link android.support.v7.widget.recyclerview.layoutmanager#scrolltoposition(int)} * @param position scroll to this adapter position * @see android.support.v7.widget.recyclerview.layoutmanager#scrolltoposition(int) */ public void scrolltoposition(int position) { stopscroll(); mlayout.scrolltoposition(position); awakenscrollbars(); } /** * starts a smooth scroll to an adapter position. * <p> * to support smooth scrolling, you must override * {@link layoutmanager#smoothscrolltoposition(recyclerview, state, int)} and create a * {@link smoothscroller}. * <p> * {@link layoutmanager} is responsible for creating the actual scroll action. if you want to * provide a custom smooth scroll logic, override * {@link layoutmanager#smoothscrolltoposition(recyclerview, state, int)} in your * layoutmanager. * * @param position the adapter position to scroll to * @see layoutmanager#smoothscrolltoposition(recyclerview, state, int) */ public void smoothscrolltoposition(int position) { mlayout.smoothscrolltoposition(this, mstate, position); } @override public void scrollto(int x, int y) { throw new unsupportedoperationexception( "recyclerview does not support scrolling to an absolute position."); } @override public void scrollby(int x, int y) { if (mlayout == null) { throw new illegalstateexception("cannot scroll without a layoutmanager set. " + "call setlayoutmanager with a non-null argument."); } final boolean canscrollhorizontal = mlayout.canscrollhorizontally(); final boolean canscrollvertical = mlayout.canscrollvertically(); if (canscrollhorizontal || canscrollvertical) { scrollbyinternal(canscrollhorizontal ? x : 0, canscrollvertical ? y : 0); } } /** * helper method reflect data changes to the state. * <p> * adapter changes during a scroll may trigger a crash because scroll assumes no data change * but data actually changed. * <p> * this method consumes all deferred changes to avoid that case. * <p> * this also ends all pending animations. it will be changed once we can support * animations during scroll. */ private void consumependingupdateoperations() { if (mitemanimator != null) { mitemanimator.endanimations(); } if (mpendingupdates.size() > 0) { mupdatechildviewsrunnable.run(); } } /** * does not perform bounds checking. used by internal methods that have already validated input. */ void scrollbyinternal(int x, int y) { int overscrollx = 0, overscrolly = 0; consumependingupdateoperations(); if (madapter != null) { eatrequestlayout(); if (x != 0) { final int hresult = mlayout.scrollhorizontallyby(x, mrecycler, mstate); overscrollx = x - hresult; } if (y != 0) { final int vresult = mlayout.scrollverticallyby(y, mrecycler, mstate); overscrolly = y - vresult; } resumerequestlayout(false); } if (!mitemdecorations.isempty()) { invalidate(); } if (viewcompat.getoverscrollmode(this) != viewcompat.over_scroll_never) { pullglows(overscrollx, overscrolly); } if (mscrolllistener != null && (x != 0 || y != 0)) { mscrolllistener.onscrolled(x, y); } if (!awakenscrollbars()) { invalidate(); } } /** * <p>compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal * range. this value is used to compute the length of the thumb within the scrollbar's track. * </p> * * <p>the range is expressed in arbitrary units that must be the same as the units used by * {@link #computehorizontalscrollrange()} and {@link #computehorizontalscrollextent()}.</p> * * <p>default implementation returns 0.</p> * * <p>if you want to support scroll bars, override * {@link recyclerview.layoutmanager#computehorizontalscrolloffset(recyclerview.state)} in your * layoutmanager. </p> * * @return the horizontal offset of the scrollbar's thumb * @see android.support.v7.widget.recyclerview.layoutmanager#computehorizontalscrolloffset * (recyclerview.adapter) */ @override protected int computehorizontalscrolloffset() { return mlayout.canscrollhorizontally() ? mlayout.computehorizontalscrolloffset(mstate) : 0; } /** * <p>compute the horizontal extent of the horizontal scrollbar's thumb within the * horizontal range. this value is used to compute the length of the thumb within the * scrollbar's track.</p> * * <p>the range is expressed in arbitrary units that must be the same as the units used by * {@link #computehorizontalscrollrange()} and {@link #computehorizontalscrolloffset()}.</p> * * <p>default implementation returns 0.</p> * * <p>if you want to support scroll bars, override * {@link recyclerview.layoutmanager#computehorizontalscrollextent(recyclerview.state)} in your * layoutmanager.</p> * * @return the horizontal extent of the scrollbar's thumb * @see recyclerview.layoutmanager#computehorizontalscrollextent(recyclerview.state) */ @override protected int computehorizontalscrollextent() { return mlayout.canscrollhorizontally() ? mlayout.computehorizontalscrollextent(mstate) : 0; } /** * <p>compute the horizontal range that the horizontal scrollbar represents.</p> * * <p>the range is expressed in arbitrary units that must be the same as the units used by * {@link #computehorizontalscrollextent()} and {@link #computehorizontalscrolloffset()}.</p> * * <p>default implementation returns 0.</p> * * <p>if you want to support scroll bars, override * {@link recyclerview.layoutmanager#computehorizontalscrollrange(recyclerview.state)} in your * layoutmanager.</p> * * @return the total horizontal range represented by the vertical scrollbar * @see recyclerview.layoutmanager#computehorizontalscrollrange(recyclerview.state) */ @override protected int computehorizontalscrollrange() { return mlayout.canscrollhorizontally() ? mlayout.computehorizontalscrollrange(mstate) : 0; } /** * <p>compute the vertical offset of the vertical scrollbar's thumb within the vertical range. * this value is used to compute the length of the thumb within the scrollbar's track. </p> * * <p>the range is expressed in arbitrary units that must be the same as the units used by * {@link #computeverticalscrollrange()} and {@link #computeverticalscrollextent()}.</p> * * <p>default implementation returns 0.</p> * * <p>if you want to support scroll bars, override * {@link recyclerview.layoutmanager#computeverticalscrolloffset(recyclerview.state)} in your * layoutmanager.</p> * * @return the vertical offset of the scrollbar's thumb * @see android.support.v7.widget.recyclerview.layoutmanager#computeverticalscrolloffset * (recyclerview.adapter) */ @override protected int computeverticalscrolloffset() { return mlayout.canscrollvertically() ? mlayout.computeverticalscrolloffset(mstate) : 0; } /** * <p>compute the vertical extent of the vertical scrollbar's thumb within the vertical range. * this value is used to compute the length of the thumb within the scrollbar's track.</p> * * <p>the range is expressed in arbitrary units that must be the same as the units used by * {@link #computeverticalscrollrange()} and {@link #computeverticalscrolloffset()}.</p> * * <p>default implementation returns 0.</p> * * <p>if you want to support scroll bars, override * {@link recyclerview.layoutmanager#computeverticalscrollextent(recyclerview.state)} in your * layoutmanager.</p> * * @return the vertical extent of the scrollbar's thumb * @see recyclerview.layoutmanager#computeverticalscrollextent(recyclerview.state) */ @override protected int computeverticalscrollextent() { return mlayout.canscrollvertically() ? mlayout.computeverticalscrollextent(mstate) : 0; } /** * <p>compute the vertical range that the vertical scrollbar represents.</p> * * <p>the range is expressed in arbitrary units that must be the same as the units used by * {@link #computeverticalscrollextent()} and {@link #computeverticalscrolloffset()}.</p> * * <p>default implementation returns 0.</p> * * <p>if you want to support scroll bars, override * {@link recyclerview.layoutmanager#computeverticalscrollrange(recyclerview.state)} in your * layoutmanager.</p> * * @return the total vertical range represented by the vertical scrollbar * @see recyclerview.layoutmanager#computeverticalscrollrange(recyclerview.state) */ @override protected int computeverticalscrollrange() { return mlayout.canscrollvertically() ? mlayout.computeverticalscrollrange(mstate) : 0; } void eatrequestlayout() { if (!meatrequestlayout) { meatrequestlayout = true; mlayoutrequesteaten = false; } } void resumerequestlayout(boolean performlayoutchildren) { if (meatrequestlayout) { if (performlayoutchildren && mlayoutrequesteaten && mlayout != null && madapter != null) { dispatchlayout(); } meatrequestlayout = false; mlayoutrequesteaten = false; } } /** * animate a scroll by the given amount of pixels along either axis. * * @param dx pixels to scroll horizontally * @param dy pixels to scroll vertically */ public void smoothscrollby(int dx, int dy) { if (dx != 0 || dy != 0) { mviewflinger.smoothscrollby(dx, dy); } } /** * begin a standard fling with an initial velocity along each axis in pixels per second. * if the velocity given is below the system-defined minimum this method will return false * and no fling will occur. * * @param velocityx initial horizontal velocity in pixels per second * @param velocityy initial vertical velocity in pixels per second * @return true if the fling was started, false if the velocity was too low to fling */ public boolean fling(int velocityx, int velocityy) { if (math.abs(velocityx) < mminflingvelocity) { velocityx = 0; } if (math.abs(velocityy) < mminflingvelocity) { velocityy = 0; } velocityx = math.max(-mmaxflingvelocity, math.min(velocityx, mmaxflingvelocity)); velocityy = math.max(-mmaxflingvelocity, math.min(velocityy, mmaxflingvelocity)); if (velocityx != 0 || velocityy != 0) { mviewflinger.fling(velocityx, velocityy); return true; } return false; } /** * stop any current scroll in progress, such as one started by * {@link #smoothscrollby(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. */ public void stopscroll() { mviewflinger.stop(); mlayout.stopsmoothscroller(); } /** * apply a pull to relevant overscroll glow effects */ private void pullglows(int overscrollx, int overscrolly) { if (overscrollx < 0) { if (mleftglow == null) { mleftglow = new edgeeffectcompat(getcontext()); mleftglow.setsize(getmeasuredheight() - getpaddingtop() - getpaddingbottom(), getmeasuredwidth() - getpaddingleft() - getpaddingright()); } mleftglow.onpull(-overscrollx / (float) getwidth()); } else if (overscrollx > 0) { if (mrightglow == null) { mrightglow = new edgeeffectcompat(getcontext()); mrightglow.setsize(getmeasuredheight() - getpaddingtop() - getpaddingbottom(), getmeasuredwidth() - getpaddingleft() - getpaddingright()); } mrightglow.onpull(overscrollx / (float) getwidth()); } if (overscrolly < 0) { if (mtopglow == null) { mtopglow = new edgeeffectcompat(getcontext()); mtopglow.setsize(getmeasuredwidth() - getpaddingleft() - getpaddingright(), getmeasuredheight() - getpaddingtop() - getpaddingbottom()); } mtopglow.onpull(-overscrolly / (float) getheight()); } else if (overscrolly > 0) { if (mbottomglow == null) { mbottomglow = new edgeeffectcompat(getcontext()); mbottomglow.setsize(getmeasuredwidth() - getpaddingleft() - getpaddingright(), getmeasuredheight() - getpaddingtop() - getpaddingbottom()); } mbottomglow.onpull(overscrolly / (float) getheight()); } if (overscrollx != 0 || overscrolly != 0) { viewcompat.postinvalidateonanimation(this); } } private void releaseglows() { boolean needsinvalidate = false; if (mleftglow != null) needsinvalidate = mleftglow.onrelease(); if (mtopglow != null) needsinvalidate |= mtopglow.onrelease(); if (mrightglow != null) needsinvalidate |= mrightglow.onrelease(); if (mbottomglow != null) needsinvalidate |= mbottomglow.onrelease(); if (needsinvalidate) { viewcompat.postinvalidateonanimation(this); } } void absorbglows(int velocityx, int velocityy) { if (velocityx < 0) { if (mleftglow == null) { mleftglow = new edgeeffectcompat(getcontext()); mleftglow.setsize(getmeasuredheight() - getpaddingtop() - getpaddingbottom(), getmeasuredwidth() - getpaddingleft() - getpaddingright()); } mleftglow.onabsorb(-velocityx); } else if (velocityx > 0) { if (mrightglow == null) { mrightglow = new edgeeffectcompat(getcontext()); mrightglow.setsize(getmeasuredheight() - getpaddingtop() - getpaddingbottom(), getmeasuredwidth() - getpaddingleft() - getpaddingright()); } mrightglow.onabsorb(velocityx); } if (velocityy < 0) { if (mtopglow == null) { mtopglow = new edgeeffectcompat(getcontext()); mtopglow.setsize(getmeasuredwidth() - getpaddingleft() - getpaddingright(), getmeasuredheight() - getpaddingtop() - getpaddingbottom()); } mtopglow.onabsorb(-velocityy); } else if (velocityy > 0) { if (mbottomglow == null) { mbottomglow = new edgeeffectcompat(getcontext()); mbottomglow.setsize(getmeasuredwidth() - getpaddingleft() - getpaddingright(), getmeasuredheight() - getpaddingtop() - getpaddingbottom()); } mbottomglow.onabsorb(velocityy); } if (velocityx != 0 || velocityy != 0) { viewcompat.postinvalidateonanimation(this); } } // focus handling @override public view focussearch(view focused, int direction) { view result = mlayout.oninterceptfocussearch(focused, direction); if (result != null) { return result; } final focusfinder ff = focusfinder.getinstance(); result = ff.findnextfocus(this, focused, direction); if (result == null && madapter != null) { eatrequestlayout(); result = mlayout.onfocussearchfailed(focused, direction, mrecycler, mstate); resumerequestlayout(false); } return result != null ? result : super.focussearch(focused, direction); } @override public void requestchildfocus(view child, view focused) { if (!mlayout.onrequestchildfocus(this, child, focused)) { mtemprect.set(0, 0, focused.getwidth(), focused.getheight()); offsetdescendantrecttomycoords(focused, mtemprect); offsetrectintodescendantcoords(child, mtemprect); requestchildrectangleonscreen(child, mtemprect, !mfirstlayoutcomplete); } super.requestchildfocus(child, focused); } @override public boolean requestchildrectangleonscreen(view child, rect rect, boolean immediate) { return mlayout.requestchildrectangleonscreen(this, child, rect, immediate); } @override public void addfocusables(arraylist<view> views, int direction, int focusablemode) { if (!mlayout.onaddfocusables(this, views, direction, focusablemode)) { super.addfocusables(views, direction, focusablemode); } } @override protected void onattachedtowindow() { super.onattachedtowindow(); misattached = true; mfirstlayoutcomplete = false; if (mlayout != null) { mlayout.onattachedtowindow(this); } mpostedanimatorrunner = false; } @override protected void ondetachedfromwindow() { super.ondetachedfromwindow(); mfirstlayoutcomplete = false; stopscroll(); // todo mark what our target position was if relevant, then we can jump there // on reattach. misattached = false; if (mlayout != null) { mlayout.ondetachedfromwindow(this); } removecallbacks(mitemanimatorrunner); } /** * add an {@link onitemtouchlistener} to intercept touch events before they are dispatched * to child views or this view's standard scrolling behavior. * * <p>client code may use listeners to implement item manipulation behavior. once a listener * returns true from * {@link onitemtouchlistener#onintercepttouchevent(recyclerview, motionevent)} its * {@link onitemtouchlistener#ontouchevent(recyclerview, motionevent)} method will be called * for each incoming motionevent until the end of the gesture.</p> * * @param listener listener to add */ public void addonitemtouchlistener(onitemtouchlistener listener) { monitemtouchlisteners.add(listener); } /** * remove an {@link onitemtouchlistener}. it will no longer be able to intercept touch events. * * @param listener listener to remove */ public void removeonitemtouchlistener(onitemtouchlistener listener) { monitemtouchlisteners.remove(listener); if (mactiveonitemtouchlistener == listener) { mactiveonitemtouchlistener = null; } } private boolean dispatchonitemtouchintercept(motionevent e) { final int action = e.getaction(); if (action == motionevent.action_cancel || action == motionevent.action_down) { mactiveonitemtouchlistener = null; } final int listenercount = monitemtouchlisteners.size(); for (int i = 0; i < listenercount; i++) { final onitemtouchlistener listener = monitemtouchlisteners.get(i); if (listener.onintercepttouchevent(this, e) && action != motionevent.action_cancel) { mactiveonitemtouchlistener = listener; return true; } } return false; } private boolean dispatchonitemtouch(motionevent e) { final int action = e.getaction(); if (mactiveonitemtouchlistener != null) { if (action == motionevent.action_down) { // stale state from a previous gesture, we're starting a new one. clear it. mactiveonitemtouchlistener = null; } else { mactiveonitemtouchlistener.ontouchevent(this, e); if (action == motionevent.action_cancel || action == motionevent.action_up) { // clean up for the next gesture. mactiveonitemtouchlistener = null; } return true; } } // listeners will have already received the action_down via dispatchonitemtouchintercept // as called from onintercepttouchevent; skip it. if (action != motionevent.action_down) { final int listenercount = monitemtouchlisteners.size(); for (int i = 0; i < listenercount; i++) { final onitemtouchlistener listener = monitemtouchlisteners.get(i); if (listener.onintercepttouchevent(this, e)) { mactiveonitemtouchlistener = listener; return true; } } } return false; } @override public boolean onintercepttouchevent(motionevent e) { if (dispatchonitemtouchintercept(e)) { canceltouch(); return true; } final boolean canscrollhorizontally = mlayout.canscrollhorizontally(); final boolean canscrollvertically = mlayout.canscrollvertically(); if (mvelocitytracker == null) { mvelocitytracker = velocitytracker.obtain(); } mvelocitytracker.addmovement(e); final int action = motioneventcompat.getactionmasked(e); final int actionindex = motioneventcompat.getactionindex(e); switch (action) { case motionevent.action_down: mscrollpointerid = motioneventcompat.getpointerid(e, 0); minitialtouchx = mlasttouchx = (int) (e.getx() + 0.5f); minitialtouchy = mlasttouchy = (int) (e.gety() + 0.5f); if (mscrollstate == scroll_state_settling) { getparent().requestdisallowintercepttouchevent(true); setscrollstate(scroll_state_dragging); } break; case motioneventcompat.action_pointer_down: mscrollpointerid = motioneventcompat.getpointerid(e, actionindex); minitialtouchx = mlasttouchx = (int) (motioneventcompat.getx(e, actionindex) + 0.5f); minitialtouchy = mlasttouchy = (int) (motioneventcompat.gety(e, actionindex) + 0.5f); break; case motionevent.action_move: { final int index = motioneventcompat.findpointerindex(e, mscrollpointerid); if (index < 0) { log.e(tag, "error processing scroll; pointer index for id " + mscrollpointerid + " not found. did any motionevents get skipped?"); return false; } final int x = (int) (motioneventcompat.getx(e, index) + 0.5f); final int y = (int) (motioneventcompat.gety(e, index) + 0.5f); if (mscrollstate != scroll_state_dragging) { final int dx = x - minitialtouchx; final int dy = y - minitialtouchy; boolean startscroll = false; if (canscrollhorizontally && math.abs(dx) > mtouchslop) { mlasttouchx = minitialtouchx + mtouchslop * (dx < 0 ? -1 : 1); startscroll = true; } if (canscrollvertically && math.abs(dy) > mtouchslop) { mlasttouchy = minitialtouchy + mtouchslop * (dy < 0 ? -1 : 1); startscroll = true; } if (startscroll) { getparent().requestdisallowintercepttouchevent(true); setscrollstate(scroll_state_dragging); } } } break; case motioneventcompat.action_pointer_up: { onpointerup(e); } break; case motionevent.action_up: { mvelocitytracker.clear(); } break; case motionevent.action_cancel: { canceltouch(); } } return mscrollstate == scroll_state_dragging; } @override public boolean ontouchevent(motionevent e) { if (dispatchonitemtouch(e)) { canceltouch(); return true; } final boolean canscrollhorizontally = mlayout.canscrollhorizontally(); final boolean canscrollvertically = mlayout.canscrollvertically(); if (mvelocitytracker == null) { mvelocitytracker = velocitytracker.obtain(); } mvelocitytracker.addmovement(e); final int action = motioneventcompat.getactionmasked(e); final int actionindex = motioneventcompat.getactionindex(e); switch (action) { case motionevent.action_down: { mscrollpointerid = motioneventcompat.getpointerid(e, 0); minitialtouchx = mlasttouchx = (int) (e.getx() + 0.5f); minitialtouchy = mlasttouchy = (int) (e.gety() + 0.5f); } break; case motioneventcompat.action_pointer_down: { mscrollpointerid = motioneventcompat.getpointerid(e, actionindex); minitialtouchx = mlasttouchx = (int) (motioneventcompat.getx(e, actionindex) + 0.5f); minitialtouchy = mlasttouchy = (int) (motioneventcompat.gety(e, actionindex) + 0.5f); } break; case motionevent.action_move: { final int index = motioneventcompat.findpointerindex(e, mscrollpointerid); if (index < 0) { log.e(tag, "error processing scroll; pointer index for id " + mscrollpointerid + " not found. did any motionevents get skipped?"); return false; } final int x = (int) (motioneventcompat.getx(e, index) + 0.5f); final int y = (int) (motioneventcompat.gety(e, index) + 0.5f); if (mscrollstate != scroll_state_dragging) { final int dx = x - minitialtouchx; final int dy = y - minitialtouchy; boolean startscroll = false; if (canscrollhorizontally && math.abs(dx) > mtouchslop) { mlasttouchx = minitialtouchx + mtouchslop * (dx < 0 ? -1 : 1); startscroll = true; } if (canscrollvertically && math.abs(dy) > mtouchslop) { mlasttouchy = minitialtouchy + mtouchslop * (dy < 0 ? -1 : 1); startscroll = true; } if (startscroll) { getparent().requestdisallowintercepttouchevent(true); setscrollstate(scroll_state_dragging); } } if (mscrollstate == scroll_state_dragging) { final int dx = x - mlasttouchx; final int dy = y - mlasttouchy; scrollbyinternal(canscrollhorizontally ? -dx : 0, canscrollvertically ? -dy : 0); } mlasttouchx = x; mlasttouchy = y; } break; case motioneventcompat.action_pointer_up: { onpointerup(e); } break; case motionevent.action_up: { mvelocitytracker.computecurrentvelocity(1000, mmaxflingvelocity); final float xvel = canscrollhorizontally ? -velocitytrackercompat.getxvelocity(mvelocitytracker, mscrollpointerid) : 0; final float yvel = canscrollvertically ? -velocitytrackercompat.getyvelocity(mvelocitytracker, mscrollpointerid) : 0; if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { setscrollstate(scroll_state_idle); } mvelocitytracker.clear(); releaseglows(); } break; case motionevent.action_cancel: { canceltouch(); } break; } return true; } private void canceltouch() { mvelocitytracker.clear(); releaseglows(); setscrollstate(scroll_state_idle); } private void onpointerup(motionevent e) { final int actionindex = motioneventcompat.getactionindex(e); if (motioneventcompat.getpointerid(e, actionindex) == mscrollpointerid) { // pick a new pointer to pick up the slack. final int newindex = actionindex == 0 ? 1 : 0; mscrollpointerid = motioneventcompat.getpointerid(e, newindex); minitialtouchx = mlasttouchx = (int) (motioneventcompat.getx(e, newindex) + 0.5f); minitialtouchy = mlasttouchy = (int) (motioneventcompat.gety(e, newindex) + 0.5f); } } @override protected void onmeasure(int widthspec, int heightspec) { if (madapterupdateduringmeasure) { eatrequestlayout(); updatechildviews(); madapterupdateduringmeasure = false; resumerequestlayout(false); } if (madapter != null) { mstate.mitemcount = madapter.getitemcount(); } mlayout.onmeasure(mrecycler, mstate, widthspec, heightspec); final int widthsize = getmeasuredwidth(); final int heightsize = getmeasuredheight(); if (mleftglow != null) mleftglow.setsize(heightsize, widthsize); if (mtopglow != null) mtopglow.setsize(widthsize, heightsize); if (mrightglow != null) mrightglow.setsize(heightsize, widthsize); if (mbottomglow != null) mbottomglow.setsize(widthsize, heightsize); } /** * sets the {@link itemanimator} that will handle animations involving changes * to the items in this recyclerview. by default, recyclerview instantiates and * uses an instance of {@link defaultitemanimator}. whether item animations are * enabled for the recyclerview depends on the itemanimator and whether * the layoutmanager {@link layoutmanager#supportspredictiveitemanimations() * supports item animations}. * * @param animator the itemanimator being set. if null, no animations will occur * when changes occur to the items in this recyclerview. */ public void setitemanimator(itemanimator animator) { if (mitemanimator != null) { mitemanimator.setlistener(null); } mitemanimator = animator; if (mitemanimator != null) { mitemanimator.setlistener(mitemanimatorlistener); } } /** * gets the current itemanimator for this recyclerview. a null return value * indicates that there is no animator and that item changes will happen without * any animations. by default, recyclerview instantiates and * uses an instance of {@link defaultitemanimator}. * * @return itemanimator the current itemanimator. if null, no animations will occur * when changes occur to the items in this recyclerview. */ public itemanimator getitemanimator() { return mitemanimator; } /** * post a runnable to the next frame to run pending item animations. only the first such * request will be posted, governed by the mpostedanimatorrunner flag. */ private void postanimationrunner() { if (!mpostedanimatorrunner && misattached) { viewcompat.postonanimation(this, mitemanimatorrunner); mpostedanimatorrunner = true; } } private boolean predictiveitemanimationsenabled() { return (mitemanimator != null && mlayout.supportspredictiveitemanimations()); } /** * wrapper around layoutchildren() that handles animating changes caused by layout. * animations work on the assumption that there are five different kinds of items * in play: * persistent: items are visible before and after layout * removed: items were visible before layout and were removed by the app * added: items did not exist before layout and were added by the app * disappearing: items exist in the data set before/after, but changed from * visible to non-visible in the process of layout (they were moved off * screen as a side-effect of other changes) * appearing: items exist in the data set before/after, but changed from * non-visible to visible in the process of layout (they were moved on * screen as a side-effect of other changes) * the overall approach figures out what items exist before/after layout and * infers one of the five above states for each of the items. then the animations * are set up accordingly: * persistent views are moved ({@link itemanimator#animatemove(viewholder, int, int, int, int)}) * removed views are removed ({@link itemanimator#animateremove(viewholder)}) * added views are added ({@link itemanimator#animateadd(viewholder)}) * disappearing views are moved off screen * appearing views are moved on screen */ void dispatchlayout() { if (madapter == null) { log.e(tag, "no adapter attached; skipping layout"); return; } eatrequestlayout(); // simple animations are a subset of advanced animations (which will cause a // prelayout step) boolean animatechangessimple = mitemanimator != null && mitemsaddedorremoved && !mitemschanged; final boolean animatechangesadvanced = enable_predictive_animations && animatechangessimple && predictiveitemanimationsenabled(); mitemsaddedorremoved = mitemschanged = false; arraymap<view, rect> appearingviewinitialbounds = null; mstate.minprelayout = animatechangesadvanced; mstate.mitemcount = madapter.getitemcount(); if (animatechangessimple) { // step 0: find out where all non-removed items are, pre-layout mstate.mprelayoutholdermap.clear(); mstate.mpostlayoutholdermap.clear(); int count = getchildcount(); for (int i = 0; i < count; ++i) { final viewholder holder = getchildviewholderint(getchildat(i)); final view view = holder.itemview; mstate.mprelayoutholdermap.put(holder, new itemholderinfo(holder, view.getleft(), view.gettop(), view.getright(), view.getbottom(), holder.mposition)); } } if (animatechangesadvanced) { // step 1: run prelayout: this will use the old positions of items. the layout manager // is expected to layout everything, even removed items (though not to add removed // items back to the container). this gives the pre-layout position of appearing views // which come into existence as part of the real layout. minprelayout = true; final boolean didstructurechange = mstate.mstructurechanged; mstate.mstructurechanged = false; // temporarily disable flag because we are asking for previous layout mlayout.onlayoutchildren(mrecycler, mstate); mstate.mstructurechanged = didstructurechange; minprelayout = false; appearingviewinitialbounds = new arraymap<view, rect>(); for (int i = 0; i < getchildcount(); ++i) { boolean found = false; view child = getchildat(i); for (int j = 0; j < mstate.mprelayoutholdermap.size(); ++j) { viewholder holder = mstate.mprelayoutholdermap.keyat(j); if (holder.itemview == child) { found = true; continue; } } if (!found) { appearingviewinitialbounds.put(child, new rect(child.getleft(), child.gettop(), child.getright(), child.getbottom())); } } } clearoldpositions(); dispatchlayoutupdates(); mstate.mitemcount = madapter.getitemcount(); // step 2: run layout mstate.minprelayout = false; mlayout.onlayoutchildren(mrecycler, mstate); mstate.mstructurechanged = false; mpendingsavedstate = null; // onlayoutchildren may have caused client code to disable item animations; re-check animatechangessimple = animatechangessimple && mitemanimator != null; if (animatechangessimple) { // step 3: find out where things are now, post-layout int count = getchildcount(); for (int i = 0; i < count; ++i) { viewholder holder = getchildviewholderint(getchildat(i)); final view view = holder.itemview; mstate.mpostlayoutholdermap.put(holder, new itemholderinfo(holder, view.getleft(), view.gettop(), view.getright(), view.getbottom(), holder.mposition)); } // step 4: animate disappearing and removed items int prelayoutcount = mstate.mprelayoutholdermap.size(); for (int i = prelayoutcount - 1; i >= 0; i--) { viewholder itemholder = mstate.mprelayoutholdermap.keyat(i); if (!mstate.mpostlayoutholdermap.containskey(itemholder)) { itemholderinfo disappearingitem = mstate.mprelayoutholdermap.valueat(i); mstate.mprelayoutholdermap.removeat(i); view disappearingitemview = disappearingitem.holder.itemview; removedetachedview(disappearingitemview, false); mrecycler.unscrapview(disappearingitem.holder); animatedisappearance(disappearingitem); } } // step 5: animate appearing and added items int postlayoutcount = mstate.mpostlayoutholdermap.size(); if (postlayoutcount > 0) { for (int i = postlayoutcount - 1; i >= 0; i--) { viewholder itemholder = mstate.mpostlayoutholdermap.keyat(i); itemholderinfo info = mstate.mpostlayoutholdermap.valueat(i); if ((mstate.mprelayoutholdermap.isempty() || !mstate.mprelayoutholdermap.containskey(itemholder))) { mstate.mpostlayoutholdermap.removeat(i); rect initialbounds = (appearingviewinitialbounds != null) ? appearingviewinitialbounds.get(itemholder.itemview) : null; animateappearance(itemholder, initialbounds, info.left, info.top); } } } // step 6: animate persistent items count = mstate.mpostlayoutholdermap.size(); for (int i = 0; i < count; ++i) { viewholder postholder = mstate.mpostlayoutholdermap.keyat(i); itemholderinfo postinfo = mstate.mpostlayoutholdermap.valueat(i); itemholderinfo preinfo = mstate.mprelayoutholdermap.get(postholder); if (preinfo != null && postinfo != null) { if (preinfo.left != postinfo.left || preinfo.top != postinfo.top) { postholder.setisrecyclable(false); if (debug) { log.d(tag, "persistent: " + postholder + " with view " + postholder.itemview); } if (mitemanimator.animatemove(postholder, preinfo.left, preinfo.top, postinfo.left, postinfo.top)) { postanimationrunner(); } } } } } resumerequestlayout(false); mlayout.removeandrecyclescrapint(mrecycler, !animatechangesadvanced); mstate.mpreviouslayoutitemcount = mstate.mitemcount; mstate.mdeletedinvisibleitemcountsincepreviouslayout = 0; } private void animateappearance(viewholder itemholder, rect beforebounds, int afterleft, int aftertop) { view newitemview = itemholder.itemview; if (beforebounds != null && (beforebounds.left != afterleft || beforebounds.top != aftertop)) { // slide items in if before/after locations differ itemholder.setisrecyclable(false); if (debug) { log.d(tag, "appearing: " + itemholder + " with view " + newitemview); } if (mitemanimator.animatemove(itemholder, beforebounds.left, beforebounds.top, afterleft, aftertop)) { postanimationrunner(); } } else { if (debug) { log.d(tag, "added: " + itemholder + " with view " + newitemview); } itemholder.setisrecyclable(false); if (mitemanimator.animateadd(itemholder)) { postanimationrunner(); } } } private void animatedisappearance(itemholderinfo disappearingitem) { view disappearingitemview = disappearingitem.holder.itemview; addanimatingview(disappearingitemview); int oldleft = disappearingitem.left; int oldtop = disappearingitem.top; int newleft = disappearingitemview.getleft(); int newtop = disappearingitemview.gettop(); if (oldleft != newleft || oldtop != newtop) { disappearingitem.holder.setisrecyclable(false); disappearingitemview.layout(newleft, newtop, newleft + disappearingitemview.getwidth(), newtop + disappearingitemview.getheight()); if (debug) { log.d(tag, "disappearing: " + disappearingitem.holder + " with view " + disappearingitemview); } if (mitemanimator.animatemove(disappearingitem.holder, oldleft, oldtop, newleft, newtop)) { postanimationrunner(); } } else { if (debug) { log.d(tag, "removed: " + disappearingitem.holder + " with view " + disappearingitemview); } disappearingitem.holder.setisrecyclable(false); if (mitemanimator.animateremove(disappearingitem.holder)) { postanimationrunner(); } } } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { eatrequestlayout(); dispatchlayout(); resumerequestlayout(false); mfirstlayoutcomplete = true; } @override public void requestlayout() { if (!meatrequestlayout) { super.requestlayout(); } else { mlayoutrequesteaten = true; } } void markitemdecorinsetsdirty() { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { final view child = getchildat(i); ((layoutparams) child.getlayoutparams()).minsetsdirty = true; } } @override public void draw(canvas c) { super.draw(c); final int count = mitemdecorations.size(); for (int i = 0; i < count; i++) { mitemdecorations.get(i).ondrawover(c, this); } boolean needsinvalidate = false; if (mleftglow != null && !mleftglow.isfinished()) { final int restore = c.save(); c.rotate(270); c.translate(-getheight() + getpaddingtop(), 0); needsinvalidate = mleftglow != null && mleftglow.draw(c); c.restoretocount(restore); } if (mtopglow != null && !mtopglow.isfinished()) { c.translate(getpaddingleft(), getpaddingtop()); needsinvalidate |= mtopglow != null && mtopglow.draw(c); } if (mrightglow != null && !mrightglow.isfinished()) { final int restore = c.save(); final int width = getwidth(); c.rotate(90); c.translate(-getpaddingtop(), -width); needsinvalidate |= mrightglow != null && mrightglow.draw(c); c.restoretocount(restore); } if (mbottomglow != null && !mbottomglow.isfinished()) { final int restore = c.save(); c.rotate(180); c.translate(-getwidth() + getpaddingleft(), -getheight() + getpaddingtop()); needsinvalidate |= mbottomglow != null && mbottomglow.draw(c); c.restoretocount(restore); } if (needsinvalidate) { viewcompat.postinvalidateonanimation(this); } } @override public void ondraw(canvas c) { super.ondraw(c); final int count = mitemdecorations.size(); for (int i = 0; i < count; i++) { mitemdecorations.get(i).ondraw(c, this); } } @override protected boolean checklayoutparams(viewgroup.layoutparams p) { return p instanceof layoutparams && mlayout.checklayoutparams((layoutparams) p); } @override protected viewgroup.layoutparams generatedefaultlayoutparams() { if (mlayout == null) { throw new illegalstateexception("recyclerview has no layoutmanager"); } return mlayout.generatedefaultlayoutparams(); } @override public viewgroup.layoutparams generatelayoutparams(attributeset attrs) { if (mlayout == null) { throw new illegalstateexception("recyclerview has no layoutmanager"); } return mlayout.generatelayoutparams(getcontext(), attrs); } @override protected viewgroup.layoutparams generatelayoutparams(viewgroup.layoutparams p) { if (mlayout == null) { throw new illegalstateexception("recyclerview has no layoutmanager"); } return mlayout.generatelayoutparams(p); } private int findpositionoffset(int position) { int offset = 0; int count = mpendinglayoutupdates.size(); for (int i = 0; i < count; ++i) { updateop op = mpendinglayoutupdates.get(i); if (op.positionstart <= position) { if (op.cmd == updateop.remove) { offset -= op.itemcount; } else if (op.cmd == updateop.add) { offset += op.itemcount; } } } return position + offset; } void dispatchlayoutupdates() { final int opcount = mpendinglayoutupdates.size(); for (int i = 0; i < opcount; i++) { final updateop op = mpendinglayoutupdates.get(i); switch (op.cmd) { case updateop.add: mlayout.onitemsadded(this, op.positionstart, op.itemcount); break; case updateop.remove: mlayout.onitemsremoved(this, op.positionstart, op.itemcount); break; case updateop.update: // todo: tell the layout manager break; } recycleupdateop(op); } mpendinglayoutupdates.clear(); } void updatechildviews() { final int opcount = mpendingupdates.size(); for (int i = 0; i < opcount; i++) { final updateop op = mpendingupdates.get(i); switch (op.cmd) { case updateop.add: if (debug) { log.d(tag, "updateop.add start=" + op.positionstart + " count=" + op.itemcount); } offsetpositionrecordsforinsert(op.positionstart, op.itemcount); mitemsaddedorremoved = true; break; case updateop.remove: if (debug) { log.d(tag, "updateop.remove start=" + op.positionstart + " count=" + op.itemcount); } for (int j = 0; j < op.itemcount; ++j) { viewholder holder = findviewholderforposition(op.positionstart + j, true); if (holder != null) { holder.setisrecyclable(false); } else { mstate.mdeletedinvisibleitemcountsincepreviouslayout ++; } } offsetpositionrecordsforremove(op.positionstart, op.itemcount); mitemsaddedorremoved = true; break; case updateop.update: if (debug) { log.d(tag, "updateop.update start=" + op.positionstart + " count=" + op.itemcount); } viewrangeupdate(op.positionstart, op.itemcount); mitemschanged = true; break; } mpendinglayoutupdates.add(op); // todo: recycle the op if no animator (also don't bother stashing in pending layout updates?) } mpendingupdates.clear(); } void clearoldpositions() { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { final viewholder holder = getchildviewholderint(getchildat(i)); holder.clearoldposition(); } mrecycler.clearoldpositions(); } void offsetpositionrecordsforinsert(int positionstart, int itemcount) { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { final viewholder holder = getchildviewholderint(getchildat(i)); if (holder != null && holder.mposition >= positionstart) { if (debug) { log.d(tag, "offsetpositionrecordsforinsert attached child " + i + " holder " + holder + " now at position " + (holder.mposition + itemcount)); } holder.offsetposition(itemcount); mstate.mstructurechanged = true; } } mrecycler.offsetpositionrecordsforinsert(positionstart, itemcount); requestlayout(); } void offsetpositionrecordsforremove(int positionstart, int itemcount) { final int positionend = positionstart + itemcount; final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { final viewholder holder = getchildviewholderint(getchildat(i)); if (holder != null) { if (holder.mposition >= positionend) { if (debug) { log.d(tag, "offsetpositionrecordsforremove attached child " + i + " holder " + holder + " now at position " + (holder.mposition - itemcount)); } holder.offsetposition(-itemcount); mstate.mstructurechanged = true; } else if (holder.mposition >= positionstart) { if (debug) { log.d(tag, "offsetpositionrecordsforremove attached child " + i + " holder " + holder + " now removed"); } holder.addflags(viewholder.flag_removed); mstate.mstructurechanged = true; } } } mrecycler.offsetpositionrecordsforremove(positionstart, itemcount); requestlayout(); } /** * rebind existing views for the given range, or create as needed. * * @param positionstart adapter position to start at * @param itemcount number of views that must explicitly be rebound */ void viewrangeupdate(int positionstart, int itemcount) { final int childcount = getchildcount(); final int positionend = positionstart + itemcount; for (int i = 0; i < childcount; i++) { final viewholder holder = getchildviewholderint(getchildat(i)); if (holder == null) { continue; } final int position = holder.getposition(); if (position >= positionstart && position < positionend) { holder.addflags(viewholder.flag_update); // binding an attached view will request a layout if needed. madapter.bindviewholder(holder, holder.getposition()); } } mrecycler.viewrangeupdate(positionstart, itemcount); } /** * mark all known views as invalid. used in response to a, "the whole world might have changed" * data change event. */ void markknownviewsinvalid() { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { final viewholder holder = getchildviewholderint(getchildat(i)); if (holder != null) { holder.addflags(viewholder.flag_update | viewholder.flag_invalid); } } mrecycler.markknownviewsinvalid(); } /** * schedule an update of data from the adapter to occur on the next frame. * on newer platform versions this happens via the postonanimation mechanism and recyclerview * attempts to avoid relayouts if possible. * on older platform versions the recyclerview requests a layout the same way listview does. */ void postadapterupdate(updateop op) { mpendingupdates.add(op); if (mpendingupdates.size() == 1) { if (mpostupdatesonanimation && mhasfixedsize && misattached) { viewcompat.postonanimation(this, mupdatechildviewsrunnable); } else { madapterupdateduringmeasure = true; requestlayout(); } } } /** * retrieve the {@link viewholder} for the given child view. * * @param child child of this recyclerview to query for its viewholder * @return the child view's viewholder */ public viewholder getchildviewholder(view child) { final viewparent parent = child.getparent(); if (parent != null && parent != this) { throw new illegalargumentexception("view " + child + " is not a direct child of " + this); } return getchildviewholderint(child); } static viewholder getchildviewholderint(view child) { if (child == null) { return null; } return ((layoutparams) child.getlayoutparams()).mviewholder; } /** * return the adapter position that the given child view corresponds to. * * @param child child view to query * @return adapter position corresponding to the given view or {@link #no_position} */ public int getchildposition(view child) { final viewholder holder = getchildviewholderint(child); return holder != null ? holder.getposition() : no_position; } /** * return the stable item id that the given child view corresponds to. * * @param child child view to query * @return item id corresponding to the given view or {@link #no_id} */ public long getchilditemid(view child) { if (madapter == null || !madapter.hasstableids()) { return no_id; } final viewholder holder = getchildviewholderint(child); return holder != null ? holder.getitemid() : no_id; } /** * return the viewholder for the item in the given position of the data set. * * @param position the position of the item in the data set of the adapter * @return the viewholder at <code>position</code> */ public viewholder findviewholderforposition(int position) { return findviewholderforposition(position, false); } viewholder findviewholderforposition(int position, boolean checknewposition) { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { final viewholder holder = getchildviewholderint(getchildat(i)); if (holder != null) { if (checknewposition) { if (holder.mposition == position) { return holder; } } else if(holder.getposition() == position) { return holder; } } } return mrecycler.findviewholderforposition(position); } /** * return the viewholder for the item with the given id. the recyclerview must * use an adapter with {@link adapter#sethasstableids(boolean) stableids} to * return a non-null value. * * @param id the id for the requested item * @return the viewholder with the given <code>id</code>, of null if there * is no such item. */ public viewholder findviewholderforitemid(long id) { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { final viewholder holder = getchildviewholderint(getchildat(i)); if (holder != null && holder.getitemid() == id) { return holder; } } return mrecycler.findviewholderforitemid(id); } /** * find the topmost view under the given point. * * @param x horizontal position in pixels to search * @param y vertical position in pixels to search * @return the child view under (x, y) or null if no matching child is found */ public view findchildviewunder(float x, float y) { final int count = getchildcount(); for (int i = count - 1; i >= 0; i--) { final view child = getchildat(i); final float translationx = viewcompat.gettranslationx(child); final float translationy = viewcompat.gettranslationy(child); if (x >= child.getleft() + translationx && x <= child.getright() + translationx && y >= child.gettop() + translationy && y <= child.getbottom() + translationy) { return child; } } return null; } /** * offset the bounds of all child views by <code>dy</code> pixels. * useful for implementing simple scrolling in {@link layoutmanager layoutmanagers}. * * @param dy vertical pixel offset to apply to the bounds of all child views */ public void offsetchildrenvertical(int dy) { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { getchildat(i).offsettopandbottom(dy); } } /** * called when an item view is attached to this recyclerview. * * <p>subclasses of recyclerview may want to perform extra bookkeeping or modifications * of child views as they become attached. this will be called before a * {@link layoutmanager} measures or lays out the view and is a good time to perform these * changes.</p> * * @param child child view that is now attached to this recyclerview and its associated window */ public void onchildattachedtowindow(view child) { } /** * called when an item view is detached from this recyclerview. * * <p>subclasses of recyclerview may want to perform extra bookkeeping or modifications * of child views as they become detached. this will be called as a * {@link layoutmanager} fully detaches the child view from the parent and its window.</p> * * @param child child view that is now detached from this recyclerview and its associated window */ public void onchilddetachedfromwindow(view child) { } /** * offset the bounds of all child views by <code>dx</code> pixels. * useful for implementing simple scrolling in {@link layoutmanager layoutmanagers}. * * @param dx horizontal pixel offset to apply to the bounds of all child views */ public void offsetchildrenhorizontal(int dx) { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { getchildat(i).offsetleftandright(dx); } } rect getitemdecorinsetsforchild(view child) { final layoutparams lp = (layoutparams) child.getlayoutparams(); if (!lp.minsetsdirty) { return lp.mdecorinsets; } final rect insets = lp.mdecorinsets; insets.set(0, 0, 0, 0); final int decorcount = mitemdecorations.size(); for (int i = 0; i < decorcount; i++) { mtemprect.set(0, 0, 0, 0); mitemdecorations.get(i).getitemoffsets(mtemprect, lp.getviewposition(), this); insets.left += mtemprect.left; insets.top += mtemprect.top; insets.right += mtemprect.right; insets.bottom += mtemprect.bottom; } lp.minsetsdirty = false; return insets; } private class viewflinger implements runnable { private int mlastflingx; private int mlastflingy; private scrollercompat mscroller; private interpolator minterpolator = squinticinterpolator; // when set to true, postonanimation callbacks are delayed until the run method completes private boolean meatrunonanimationrequest = false; // tracks if postanimationcallback should be re-attached when it is done private boolean mreschedulepostanimationcallback = false; public viewflinger() { mscroller = scrollercompat.create(getcontext(), squinticinterpolator); } @override public void run() { disablerunonanimationrequests(); consumependingupdateoperations(); // keep a local reference so that if it is changed during onanimation method, it wont cause // unexpected behaviors final scrollercompat scroller = mscroller; final smoothscroller smoothscroller = mlayout.msmoothscroller; if (scroller.computescrolloffset()) { final int x = scroller.getcurrx(); final int y = scroller.getcurry(); final int dx = x - mlastflingx; final int dy = y - mlastflingy; mlastflingx = x; mlastflingy = y; int overscrollx = 0, overscrolly = 0; if (madapter != null) { eatrequestlayout(); if (dx != 0) { final int hresult = mlayout.scrollhorizontallyby(dx, mrecycler, mstate); overscrollx = dx - hresult; } if (dy != 0) { final int vresult = mlayout.scrollverticallyby(dy, mrecycler, mstate); overscrolly = dy - vresult; } if (smoothscroller != null && !smoothscroller.ispendinginitialrun() && smoothscroller.isrunning()) { smoothscroller.onanimation(dx - overscrollx, dy - overscrolly); } resumerequestlayout(false); } if (!mitemdecorations.isempty()) { invalidate(); } if (overscrollx != 0 || overscrolly != 0) { final int vel = (int) scroller.getcurrvelocity(); int velx = 0; if (overscrollx != x) { velx = overscrollx < 0 ? -vel : overscrollx > 0 ? vel : 0; } int vely = 0; if (overscrolly != y) { vely = overscrolly < 0 ? -vel : overscrolly > 0 ? vel : 0; } if (viewcompat.getoverscrollmode(recyclerview.this) != viewcompat.over_scroll_never) { absorbglows(velx, vely); } if ((velx != 0 || overscrollx == x || scroller.getfinalx() == 0) && (vely != 0 || overscrolly == y || scroller.getfinaly() == 0)) { scroller.abortanimation(); } } if (mscrolllistener != null && (x != 0 || y != 0)) { mscrolllistener.onscrolled(dx, dy); } if (!awakenscrollbars()) { invalidate(); } if (scroller.isfinished()) { setscrollstate(scroll_state_idle); } else { postonanimation(); } } // call this after the onanimation is complete not to have inconsistent callbacks etc. if (smoothscroller != null && smoothscroller.ispendinginitialrun()) { smoothscroller.onanimation(0, 0); } enablerunonanimationrequests(); } private void disablerunonanimationrequests() { mreschedulepostanimationcallback = false; meatrunonanimationrequest = true; } private void enablerunonanimationrequests() { meatrunonanimationrequest = false; if (mreschedulepostanimationcallback) { postonanimation(); } } void postonanimation() { if (meatrunonanimationrequest) { mreschedulepostanimationcallback = true; } else { viewcompat.postonanimation(recyclerview.this, this); } } public void fling(int velocityx, int velocityy) { setscrollstate(scroll_state_settling); mlastflingx = mlastflingy = 0; mscroller.fling(0, 0, velocityx, velocityy, integer.min_value, integer.max_value, integer.min_value, integer.max_value); postonanimation(); } public void smoothscrollby(int dx, int dy) { smoothscrollby(dx, dy, 0, 0); } public void smoothscrollby(int dx, int dy, int vx, int vy) { smoothscrollby(dx, dy, computescrollduration(dx, dy, vx, vy)); } private float distanceinfluenceforsnapduration(float f) { f -= 0.5f; // center the values about 0. f *= 0.3f * math.pi / 2.0f; return (float) math.sin(f); } private int computescrollduration(int dx, int dy, int vx, int vy) { final int absdx = math.abs(dx); final int absdy = math.abs(dy); final boolean horizontal = absdx > absdy; final int velocity = (int) math.sqrt(vx * vx + vy * vy); final int delta = (int) math.sqrt(dx * dx + dy * dy); final int containersize = horizontal ? getwidth() : getheight(); final int halfcontainersize = containersize / 2; final float distanceratio = math.min(1.f, 1.f * delta / containersize); final float distance = halfcontainersize + halfcontainersize * distanceinfluenceforsnapduration(distanceratio); final int duration; if (velocity > 0) { duration = 4 * math.round(1000 * math.abs(distance / velocity)); } else { float absdelta = (float) (horizontal ? absdx : absdy); duration = (int) (((absdelta / containersize) + 1) * 300); } return math.min(duration, max_scroll_duration); } public void smoothscrollby(int dx, int dy, int duration) { smoothscrollby(dx, dy, duration, squinticinterpolator); } public void smoothscrollby(int dx, int dy, int duration, interpolator interpolator) { if (minterpolator != interpolator) { minterpolator = interpolator; mscroller = scrollercompat.create(getcontext(), interpolator); } setscrollstate(scroll_state_settling); mlastflingx = mlastflingy = 0; mscroller.startscroll(0, 0, dx, dy, duration); postonanimation(); } public void stop() { removecallbacks(this); mscroller.abortanimation(); } } private class recyclerviewdataobserver extends adapterdataobserver { @override public void onchanged() { if (madapter.hasstableids()) { // todo determine what actually changed markknownviewsinvalid(); mstate.mstructurechanged = true; requestlayout(); } else { markknownviewsinvalid(); mstate.mstructurechanged = true; requestlayout(); } } @override public void onitemrangechanged(int positionstart, int itemcount) { postadapterupdate(obtainupdateop(updateop.update, positionstart, itemcount)); } @override public void onitemrangeinserted(int positionstart, int itemcount) { postadapterupdate(obtainupdateop(updateop.add, positionstart, itemcount)); } @override public void onitemrangeremoved(int positionstart, int itemcount) { postadapterupdate(obtainupdateop(updateop.remove, positionstart, itemcount)); } } public static class recycledviewpool { private sparsearray<arraylist<viewholder>> mscrap = new sparsearray<arraylist<viewholder>>(); private sparseintarray mmaxscrap = new sparseintarray(); private int mattachcount = 0; private static final int default_max_scrap = 5; public void clear() { mscrap.clear(); } public void setmaxrecycledviews(int viewtype, int max) { mmaxscrap.put(viewtype, max); final arraylist<viewholder> scrapheap = mscrap.get(viewtype); if (scrapheap != null) { while (scrapheap.size() > max) { scrapheap.remove(scrapheap.size() - 1); } } } public viewholder getrecycledview(int viewtype) { final arraylist<viewholder> scrapheap = mscrap.get(viewtype); if (scrapheap != null && !scrapheap.isempty()) { final int index = scrapheap.size() - 1; final viewholder scrap = scrapheap.get(index); scrapheap.remove(index); return scrap; } return null; } public void putrecycledview(viewholder scrap) { final int viewtype = scrap.getitemviewtype(); final arraylist scrapheap = getscrapheapfortype(viewtype); if (mmaxscrap.get(viewtype) <= scrapheap.size()) { return; } scrap.mposition = no_position; scrap.moldposition = no_position; scrap.mitemid = no_id; scrap.clearflagsforsharedpool(); scrapheap.add(scrap); } void attach(adapter adapter) { mattachcount++; } void detach() { mattachcount--; } void onadapterchanged(adapter oldadapter, adapter newadapter) { if (mattachcount == 1) { clear(); } } private arraylist<viewholder> getscrapheapfortype(int viewtype) { arraylist<viewholder> scrap = mscrap.get(viewtype); if (scrap == null) { scrap = new arraylist<viewholder>(); mscrap.put(viewtype, scrap); if (mmaxscrap.indexofkey(viewtype) < 0) { mmaxscrap.put(viewtype, default_max_scrap); } } return scrap; } } /** * a recycler is responsible for managing scrapped or detached item views for reuse. * * <p>a "scrapped" view is a view that is still attached to its parent recyclerview but * that has been marked for removal or reuse.</p> * * <p>typical use of a recycler by a {@link layoutmanager} will be to obtain views for * an adapter's data set representing the data at a given position or item id. * if the view to be reused is considered "dirty" the adapter will be asked to rebind it. * if not, the view can be quickly reused by the layoutmanager with no further work. * clean views that have not {@link android.view.view#islayoutrequested() requested layout} * may be repositioned by a layoutmanager without remeasurement.</p> */ public final class recycler { private final arraylist<viewholder> mattachedscrap = new arraylist<viewholder>(); private final arraylist<viewholder> mcachedviews = new arraylist<viewholder>(); private final list<viewholder> munmodifiableattachedscrap = collections.unmodifiablelist(mattachedscrap); private int mviewcachemax = default_cache_size; private recycledviewpool mrecyclerpool; private static final int default_cache_size = 2; /** * clear scrap views out of this recycler. detached views contained within a * recycled view pool will remain. */ public void clear() { mattachedscrap.clear(); recyclecachedviews(); } /** * set the maximum number of detached, valid views we should retain for later use. * * @param viewcount number of views to keep before sending views to the shared pool */ public void setviewcachesize(int viewcount) { mviewcachemax = viewcount; while (mcachedviews.size() > viewcount) { mcachedviews.remove(mcachedviews.size() - 1); } } /** * returns an unmodifiable list of viewholders that are currently in the scrap list. * * @return list of viewholders in the scrap list. */ public list<viewholder> getscraplist() { return munmodifiableattachedscrap; } /** * helper method for getviewforposition. * <p> * checks whether a given view holder can be used for the provided position. * * @param holder viewholder * @param offsetposition the position which is updated by update_op changes on the adapter * @return true if viewholder matches the provided position, false otherwise */ boolean validateviewholderforoffsetposition(viewholder holder, int offsetposition) { // if it is a removed holder, nothing to verify since we cannot ask adapter anymore // if it is not removed, verify the type and id. if (holder.isremoved()) { return true; } if (offsetposition < 0 || offsetposition >= madapter.getitemcount()) { if (debug) { log.d(tag, "validateviewholderforoffsetposition: invalid position, returning " + "false"); } return false; } final int type = madapter.getitemviewtype(offsetposition); if (type != holder.getitemviewtype()) { return false; } if (madapter.hasstableids()) { return holder.getitemid() == madapter.getitemid(offsetposition); } return true; } /** * obtain a view initialized for the given position. * * <p>this method should be used by {@link layoutmanager} implementations to obtain * views to represent data from an {@link adapter}.</p> * * <p>the recycler may reuse a scrap or detached view from a shared pool if one is * available for the correct view type. if the adapter has not indicated that the * data at the given position has changed, the recycler will attempt to hand back * a scrap view that was previously initialized for that data without rebinding.</p> * * @param position position to obtain a view for * @return a view representing the data at <code>position</code> from <code>adapter</code> */ public view getviewforposition(int position) { viewholder holder; holder = getscrapviewforposition(position, invalid_type); final int offsetposition = findpositionoffset(position); if (holder != null) { if (!validateviewholderforoffsetposition(holder, offsetposition)) { // recycle this scrap removedetachedview(holder.itemview, false); quickrecyclescrapview(holder.itemview); // if validate fails, we can query scrap again w/ type. that may return a // different view holder from cache. final int type = madapter.getitemviewtype(offsetposition); if (madapter.hasstableids()) { final long id = madapter.getitemid(offsetposition); holder = getscrapviewforid(id, type); } else { holder = getscrapviewforposition(offsetposition, type); } } } else { // try recycler. holder = getrecycledviewpool() .getrecycledview(madapter.getitemviewtype(offsetposition)); } if (holder == null) { if (offsetposition < 0 || offsetposition >= madapter.getitemcount()) { throw new indexoutofboundsexception("invalid item position " + position + "(" + offsetposition + ")"); } else { holder = madapter.createviewholder(recyclerview.this, madapter.getitemviewtype(offsetposition)); if (debug) { log.d(tag, "getviewforposition created new viewholder"); } } } if (!holder.isremoved() && (!holder.isbound() || holder.needsupdate())) { if (debug) { log.d(tag, "getviewforposition unbound holder or needs update; updating..."); } // todo: think through when getoffsetposition() is called. i use it here because // existing views have already been offset appropriately through the moldoffset // mechanism, but new views do not have this mechanism. madapter.bindviewholder(holder, offsetposition); } viewgroup.layoutparams lp = holder.itemview.getlayoutparams(); if (lp == null) { lp = generatedefaultlayoutparams(); holder.itemview.setlayoutparams(lp); } else if (!checklayoutparams(lp)) { lp = generatelayoutparams(lp); holder.itemview.setlayoutparams(lp); } ((layoutparams) lp).mviewholder = holder; return holder.itemview; } /** * recycle a detached view. the specified view will be added to a pool of views * for later rebinding and reuse. * * <p>a view must be fully detached before it may be recycled.</p> * * @param view removed view for recycling */ public void recycleview(view view) { recycleviewholder(getchildviewholderint(view)); } void recyclecachedviews() { final int count = mcachedviews.size(); for (int i = count - 1; i >= 0; i--) { final viewholder cachedview = mcachedviews.get(i); if (cachedview.isrecyclable()) { getrecycledviewpool().putrecycledview(cachedview); dispatchviewrecycled(cachedview); } mcachedviews.remove(i); } } void recycleviewholder(viewholder holder) { if (holder.isscrap() || holder.itemview.getparent() != null) { throw new illegalargumentexception( "scrapped or attached views may not be recycled."); } boolean cached = false; if (!holder.isinvalid() && (minprelayout || !holder.isremoved())) { // retire oldest cached views first if (mcachedviews.size() == mviewcachemax && !mcachedviews.isempty()) { for (int i = 0; i < mcachedviews.size(); i++) { final viewholder cachedview = mcachedviews.get(i); if (cachedview.isrecyclable()) { mcachedviews.remove(i); getrecycledviewpool().putrecycledview(cachedview); dispatchviewrecycled(cachedview); break; } } } if (mcachedviews.size() < mviewcachemax) { mcachedviews.add(holder); cached = true; } } if (!cached && holder.isrecyclable()) { getrecycledviewpool().putrecycledview(holder); dispatchviewrecycled(holder); } // remove from pre/post maps that are used to animate items; a recycled holder // should not be animated mstate.mprelayoutholdermap.remove(holder); mstate.mpostlayoutholdermap.remove(holder); } /** * used as a fast path for unscrapping and recycling a view during a bulk operation. * the caller must call {@link #clearscrap()} when it's done to update the recycler's * internal bookkeeping. */ void quickrecyclescrapview(view view) { final viewholder holder = getchildviewholderint(view); holder.mscrapcontainer = null; recycleviewholder(holder); } /** * mark an attached view as scrap. * * <p>"scrap" views are still attached to their parent recyclerview but are eligible * for rebinding and reuse. requests for a view for a given position may return a * reused or rebound scrap view instance.</p> * * @param view view to scrap */ void scrapview(view view) { final viewholder holder = getchildviewholderint(view); holder.setscrapcontainer(this); mattachedscrap.add(holder); } /** * remove a previously scrapped view from the pool of eligible scrap. * * <p>this view will no longer be eligible for reuse until re-scrapped or * until it is explicitly removed and recycled.</p> */ void unscrapview(viewholder holder) { mattachedscrap.remove(holder); holder.mscrapcontainer = null; } int getscrapcount() { return mattachedscrap.size(); } view getscrapviewat(int index) { return mattachedscrap.get(index).itemview; } void clearscrap() { mattachedscrap.clear(); } /** * returns a scrap view for the position. if type is not invalid_type, it also checks if * viewholder's type matches the provided type. * * @param position item position * @param type view type * @return a viewholder that can be re-used for this position. */ viewholder getscrapviewforposition(int position, int type) { final int scrapcount = mattachedscrap.size(); // try first for an exact, non-invalid match from scrap. for (int i = 0; i < scrapcount; i++) { final viewholder holder = mattachedscrap.get(i); if (holder.getposition() == position && !holder.isinvalid() && (minprelayout || !holder.isremoved())) { if (type != invalid_type && holder.getitemviewtype() != type) { log.e(tag, "scrap view for position " + position + " isn't dirty but has" + " wrong view type! (found " + holder.getitemviewtype() + " but expected " + type + ")"); break; } mattachedscrap.remove(i); holder.setscrapcontainer(null); if (debug) { log.d(tag, "getscrapviewforposition(" + position + ", " + type + ") found exact match in scrap: " + holder); } return holder; } } if (mnumanimatingviews != 0) { view view = getanimatingview(position, type); if (view != null) { // ending the animation should cause it to get recycled before we reuse it mitemanimator.endanimation(getchildviewholder(view)); } } // search in our first-level recycled view cache. final int cachesize = mcachedviews.size(); for (int i = 0; i < cachesize; i++) { final viewholder holder = mcachedviews.get(i); if (holder.getposition() == position) { mcachedviews.remove(i); if (holder.isinvalid() && (type != invalid_type && holder.getitemviewtype() != type)) { // can't use it. we don't know where it's been. if (debug) { log.d(tag, "getscrapviewforposition(" + position + ", " + type + ") found position match, but holder is invalid with type " + holder.getitemviewtype()); } if (holder.isrecyclable()) { getrecycledviewpool().putrecycledview(holder); } // even if the holder wasn't officially recycleable, dispatch that it // was recycled anyway in case there are resources to unbind. dispatchviewrecycled(holder); // drop out of the cache search and try something else instead, // we won't find another match here. break; } if (debug) { log.d(tag, "getscrapviewforposition(" + position + ", " + type + ") found match in cache: " + holder); } return holder; } } // give up. head to the shared pool. if (debug) { log.d(tag, "getscrapviewforposition(" + position + ", " + type + ") fetching from shared pool"); } return type == invalid_type ? null : getrecycledviewpool().getrecycledview(type); } viewholder getscrapviewforid(long id, int type) { // look in our attached views first final int count = mattachedscrap.size(); for (int i = 0; i < count; i++) { final viewholder holder = mattachedscrap.get(i); if (holder.getitemid() == id) { if (type == holder.getitemviewtype()) { mattachedscrap.remove(i); holder.setscrapcontainer(null); return holder; } else { break; } } } // search the first-level cache final int cachesize = mcachedviews.size(); for (int i = 0; i < cachesize; i++) { final viewholder holder = mcachedviews.get(i); if (holder.getitemid() == id) { mcachedviews.remove(i); return holder; } } // that didn't work, look for an unordered view of the right type instead. // the holder's position won't match so the calling code will need to have // the adapter rebind it. return getrecycledviewpool().getrecycledview(type); } void dispatchviewrecycled(viewholder holder) { if (mrecyclerlistener != null) { mrecyclerlistener.onviewrecycled(holder); } if (madapter != null) { madapter.onviewrecycled(holder); } if (debug) log.d(tag, "dispatchviewrecycled: " + holder); } void onadapterchanged(adapter oldadapter, adapter newadapter) { clear(); getrecycledviewpool().onadapterchanged(oldadapter, newadapter); } void offsetpositionrecordsforinsert(int insertedat, int count) { final int cachedcount = mcachedviews.size(); for (int i = 0; i < cachedcount; i++) { final viewholder holder = mcachedviews.get(i); if (holder != null && holder.getposition() >= insertedat) { if (debug) { log.d(tag, "offsetpositionrecordsforinsert cached " + i + " holder " + holder + " now at position " + (holder.mposition + count)); } holder.offsetposition(count); } } } void offsetpositionrecordsforremove(int removedfrom, int count) { final int removedend = removedfrom + count; final int cachedcount = mcachedviews.size(); for (int i = cachedcount - 1; i >= 0; i--) { final viewholder holder = mcachedviews.get(i); if (holder != null) { if (holder.getposition() >= removedend) { if (debug) { log.d(tag, "offsetpositionrecordsforremove cached " + i + " holder " + holder + " now at position " + (holder.mposition - count)); } holder.offsetposition(-count); } else if (holder.getposition() >= removedfrom) { // item for this view was removed. dump it from the cache. if (debug) { log.d(tag, "offsetpositionrecordsforremove cached " + i + " holder " + holder + " now placed in pool"); } mcachedviews.remove(i); getrecycledviewpool().putrecycledview(holder); dispatchviewrecycled(holder); } } } } void setrecycledviewpool(recycledviewpool pool) { if (mrecyclerpool != null) { mrecyclerpool.detach(); } mrecyclerpool = pool; if (pool != null) { mrecyclerpool.attach(getadapter()); } } recycledviewpool getrecycledviewpool() { if (mrecyclerpool == null) { mrecyclerpool = new recycledviewpool(); } return mrecyclerpool; } viewholder findviewholderforposition(int position) { final int cachedcount = mcachedviews.size(); for (int i = 0; i < cachedcount; i++) { final viewholder holder = mcachedviews.get(i); if (holder != null && holder.getposition() == position) { mcachedviews.remove(i); return holder; } } return null; } viewholder findviewholderforitemid(long id) { if (!madapter.hasstableids()) { return null; } final int cachedcount = mcachedviews.size(); for (int i = 0; i < cachedcount; i++) { final viewholder holder = mcachedviews.get(i); if (holder != null && holder.getitemid() == id) { mcachedviews.remove(i); return holder; } } return null; } void viewrangeupdate(int positionstart, int itemcount) { final int positionend = positionstart + itemcount; final int cachedcount = mcachedviews.size(); for (int i = 0; i < cachedcount; i++) { final viewholder holder = mcachedviews.get(i); if (holder == null) { continue; } final int pos = holder.getposition(); if (pos >= positionstart && pos < positionend) { holder.addflags(viewholder.flag_update); } } } void markknownviewsinvalid() { final int cachedcount = mcachedviews.size(); for (int i = 0; i < cachedcount; i++) { final viewholder holder = mcachedviews.get(i); if (holder != null) { holder.addflags(viewholder.flag_update | viewholder.flag_invalid); } } } void clearoldpositions() { final int cachedcount = mcachedviews.size(); for (int i = 0; i < cachedcount; i++) { final viewholder holder = mcachedviews.get(i); holder.clearoldposition(); } } } /** * base class for an adapter * * <p>adapters provide a binding from an app-specific data set to views that are displayed * within a {@link recyclerview}.</p> */ public static abstract class adapter<vh extends viewholder> { private final adapterdataobservable mobservable = new adapterdataobservable(); private boolean mhasstableids = false; public abstract vh oncreateviewholder(viewgroup parent, int viewtype); public abstract void onbindviewholder(vh holder, int position); public final vh createviewholder(viewgroup parent, int viewtype) { final vh holder = oncreateviewholder(parent, viewtype); holder.mitemviewtype = viewtype; return holder; } public final void bindviewholder(vh holder, int position) { holder.mposition = position; if (hasstableids()) { holder.mitemid = getitemid(position); } onbindviewholder(holder, position); holder.setflags(viewholder.flag_bound, viewholder.flag_bound | viewholder.flag_update | viewholder.flag_invalid); } /** * return the view type of the item at <code>position</code> for the purposes * of view recycling. * * <p>the default implementation of this method returns 0, making the assumption of * a single view type for the adapter. unlike listview adapters, types need not * be contiguous. consider using id resources to uniquely identify item view types. * * @param position position to query * @return integer value identifying the type of the view needed to represent the item at * <code>position</code>. type codes need not be contiguous. */ public int getitemviewtype(int position) { return 0; } public void sethasstableids(boolean hasstableids) { if (hasobservers()) { throw new illegalstateexception("cannot change whether this adapter has " + "stable ids while the adapter has registered observers."); } mhasstableids = hasstableids; } /** * return the stable id for the item at <code>position</code>. if {@link #hasstableids()} * would return false this method should return {@link #no_id}. the default implementation * of this method returns {@link #no_id}. * * @param position adapter position to query * @return the stable id of the item at position */ public long getitemid(int position) { return no_id; } public abstract int getitemcount(); /** * returns true if this adapter publishes a unique <code>long</code> value that can * act as a key for the item at a given position in the data set. if that item is relocated * in the data set, the id returned for that item should be the same. * * @return true if this adapter's items have stable ids */ public final boolean hasstableids() { return mhasstableids; } /** * called when a view created by this adapter has been recycled. * * <p>a view is recycled when a {@link layoutmanager} decides that it no longer * needs to be attached to its parent {@link recyclerview}. this can be because it has * fallen out of visibility or a set of cached views represented by views still * attached to the parent recyclerview. if an item view has large or expensive data * bound to it such as large bitmaps, this may be a good place to release those * resources.</p> * * @param holder the viewholder for the view being recycled */ public void onviewrecycled(vh holder) { } /** * called when a view created by this adapter has been attached to a window. * * <p>this can be used as a reasonable signal that the view is about to be seen * by the user. if the adapter previously freed any resources in * {@link #onviewdetachedfromwindow(recyclerview.viewholder) onviewdetachedfromwindow} * those resources should be restored here.</p> * * @param holder holder of the view being attached */ public void onviewattachedtowindow(vh holder) { } /** * called when a view created by this adapter has been detached from its window. * * <p>becoming detached from the window is not necessarily a permanent condition; * the consumer of an adapter's views may choose to cache views offscreen while they * are not visible, attaching an detaching them as appropriate.</p> * * @param holder holder of the view being detached */ public void onviewdetachedfromwindow(vh holder) { } /** * returns true if one or more observers are attached to this adapter. * @return true if this adapter has observers */ public final boolean hasobservers() { return mobservable.hasobservers(); } /** * register a new observer to listen for data changes. * * <p>the adapter may publish a variety of events describing specific changes. * not all adapters may support all change types and some may fall back to a generic * {@link android.support.v7.widget.recyclerview.adapterdataobserver#onchanged() * "something changed"} event if more specific data is not available.</p> * * <p>components registering observers with an adapter are responsible for * {@link #unregisteradapterdataobserver(android.support.v7.widget.recyclerview.adapterdataobserver) * unregistering} those observers when finished.</p> * * @param observer observer to register * * @see #unregisteradapterdataobserver(android.support.v7.widget.recyclerview.adapterdataobserver) */ public void registeradapterdataobserver(adapterdataobserver observer) { mobservable.registerobserver(observer); } /** * unregister an observer currently listening for data changes. * * <p>the unregistered observer will no longer receive events about changes * to the adapter.</p> * * @param observer observer to unregister * * @see #registeradapterdataobserver(android.support.v7.widget.recyclerview.adapterdataobserver) */ public void unregisteradapterdataobserver(adapterdataobserver observer) { mobservable.unregisterobserver(observer); } /** * notify any registered observers that the data set has changed. * * <p>there are two different classes of data change events, item changes and structural * changes. item changes are when a single item has its data updated but no positional * changes have occurred. structural changes are when items are inserted, removed or moved * within the data set.</p> * * <p>this event does not specify what about the data set has changed, forcing * any observers to assume that all existing items and structure may no longer be valid. * layoutmanagers will be forced to fully rebind and relayout all visible views.</p> * * <p><code>recyclerview</code> will attempt to synthesize visible structural change events * for adapters that report that they have {@link #hasstableids() stable ids} when * this method is used. this can help for the purposes of animation and visual * object persistence but individual item views will still need to be rebound * and relaid out.</p> * * <p>if you are writing an adapter it will always be more efficient to use the more * specific change events if you can. rely on <code>notifydatasetchanged()</code> * as a last resort.</p> * * @see #notifyitemchanged(int) * @see #notifyiteminserted(int) * @see #notifyitemremoved(int) * @see #notifyitemrangechanged(int, int) * @see #notifyitemrangeinserted(int, int) * @see #notifyitemrangeremoved(int, int) */ public final void notifydatasetchanged() { mobservable.notifychanged(); } /** * notify any registered observers that the item at <code>position</code> has changed. * * <p>this is an item change event, not a structural change event. it indicates that any * reflection of the data at <code>position</code> is out of date and should be updated. * the item at <code>position</code> retains the same identity.</p> * * @param position position of the item that has changed * * @see #notifyitemrangechanged(int, int) */ public final void notifyitemchanged(int position) { mobservable.notifyitemrangechanged(position, 1); } /** * notify any registered observers that the <code>itemcount</code> items starting at * position <code>positionstart</code> have changed. * * <p>this is an item change event, not a structural change event. it indicates that * any reflection of the data in the given position range is out of date and should * be updated. the items in the given range retain the same identity.</p> * * @param positionstart position of the first item that has changed * @param itemcount number of items that have changed * * @see #notifyitemchanged(int) */ public final void notifyitemrangechanged(int positionstart, int itemcount) { mobservable.notifyitemrangechanged(positionstart, itemcount); } /** * notify any registered observers that the item reflected at <code>position</code> * has been newly inserted. the item previously at <code>position</code> is now at * position <code>position + 1</code>. * * <p>this is a structural change event. representations of other existing items in the * data set are still considered up to date and will not be rebound, though their * positions may be altered.</p> * * @param position position of the newly inserted item in the data set * * @see #notifyitemrangeinserted(int, int) */ public final void notifyiteminserted(int position) { mobservable.notifyitemrangeinserted(position, 1); } /** * notify any registered observers that the currently reflected <code>itemcount</code> * items starting at <code>positionstart</code> have been newly inserted. the items * previously located at <code>positionstart</code> and beyond can now be found starting * at position <code>positionstart + itemcount</code>. * * <p>this is a structural change event. representations of other existing items in the * data set are still considered up to date and will not be rebound, though their positions * may be altered.</p> * * @param positionstart position of the first item that was inserted * @param itemcount number of items inserted * * @see #notifyiteminserted(int) */ public final void notifyitemrangeinserted(int positionstart, int itemcount) { mobservable.notifyitemrangeinserted(positionstart, itemcount); } /** * notify any registered observers that the item previously located at <code>position</code> * has been removed from the data set. the items previously located at and after * <code>position</code> may now be found at <code>oldposition - 1</code>. * * <p>this is a structural change event. representations of other existing items in the * data set are still considered up to date and will not be rebound, though their positions * may be altered.</p> * * @param position position of the item that has now been removed * * @see #notifyitemrangeremoved(int, int) */ public final void notifyitemremoved(int position) { mobservable.notifyitemrangeremoved(position, 1); } /** * notify any registered observers that the <code>itemcount</code> items previously * located at <code>positionstart</code> have been removed from the data set. the items * previously located at and after <code>positionstart + itemcount</code> may now be found * at <code>oldposition - itemcount</code>. * * <p>this is a structural change event. representations of other existing items in the data * set are still considered up to date and will not be rebound, though their positions * may be altered.</p> * * @param positionstart previous position of the first item that was removed * @param itemcount number of items removed from the data set */ public final void notifyitemrangeremoved(int positionstart, int itemcount) { mobservable.notifyitemrangeremoved(positionstart, itemcount); } } /** * a <code>layoutmanager</code> is responsible for measuring and positioning item views * within a <code>recyclerview</code> as well as determining the policy for when to recycle * item views that are no longer visible to the user. by changing the <code>layoutmanager</code> * a <code>recyclerview</code> can be used to implement a standard vertically scrolling list, * a uniform grid, staggered grids, horizontally scrolling collections and more. several stock * layout managers are provided for general use. */ public static abstract class layoutmanager { recyclerview mrecyclerview; @nullable smoothscroller msmoothscroller; /** * calls {@code recyclerview#requestlayout} on the underlying recyclerview */ public void requestlayout() { if(mrecyclerview != null) { mrecyclerview.requestlayout(); } } /** * returns whether this layoutmanager supports automatic item animations. * a layoutmanager wishing to support item animations should obey certain * rules as outlined in {@link #onlayoutchildren(recycler, state)}. * the default return value is <code>false</code>, so subclasses of layoutmanager * will not get predictive item animations by default. * * <p>whether item animations are enabled in a recyclerview is determined both * by the return value from this method and the * {@link recyclerview#setitemanimator(itemanimator) itemanimator} set on the * recyclerview itself. if the recyclerview has a non-null itemanimator but this * method returns false, then simple item animations will be enabled, in which * views that are moving onto or off of the screen are simply faded in/out. if * the recyclerview has a non-null itemanimator and this method returns true, * then there will be two calls to {@link #onlayoutchildren(recycler, state)} to * setup up the information needed to more intelligently predict where appearing * and disappearing views should be animated from/to.</p> * * @return true if predictive item animations should be enabled, false otherwise */ public boolean supportspredictiveitemanimations() { return false; } /** * called when this layoutmanager is both attached to a recyclerview and that recyclerview * is attached to a window. * * <p>subclass implementations should always call through to the superclass implementation. * </p> * * @param view the recyclerview this layoutmanager is bound to */ public void onattachedtowindow(recyclerview view) { } /** * called when this layoutmanager is detached from its parent recyclerview or when * its parent recyclerview is detached from its window. * * <p>subclass implementations should always call through to the superclass implementation. * </p> * * @param view the recyclerview this layoutmanager is bound to */ public void ondetachedfromwindow(recyclerview view) { } /** * lay out all relevant child views from the given adapter. * * the layoutmanager is in charge of the behavior of item animations. by default, * recyclerview has a non-null {@link #getitemanimator() itemanimator}, and simple * item animations are enabled. this means that add/remove operations on the * adapter will result in animations to add new or appearing items, removed or * disappearing items, and moved items. if a layoutmanager returns false from * {@link #supportspredictiveitemanimations()}, which is the default, and runs a * normal layout operation during {@link #onlayoutchildren(recycler, state)}, the * recyclerview will have enough information to run those animations in a simple * way. for example, the default itemanimator, {@link defaultitemanimator}, will * simple fade views in and out, whether they are actuall added/removed or whether * they are moved on or off the screen due to other add/remove operations. * * <p>a layoutmanager wanting a better item animation experience, where items can be * animated onto and off of the screen according to where the items exist when they * are not on screen, then the layoutmanager should return true from * {@link #supportspredictiveitemanimations()} and add additional logic to * {@link #onlayoutchildren(recycler, state)}. supporting predictive animations * means that {@link #onlayoutchildren(recycler, state)} will be called twice; * once as a "pre" layout step to determine where items would have been prior to * a real layout, and again to do the "real" layout. in the pre-layout phase, * items will remember their pre-layout positions to allow them to be laid out * appropriately. also, {@link layoutparams#isitemremoved() removed} items will * be returned from the scrap to help determine correct placement of other items. * these removed items should not be added to the child list, but should be used * to help calculate correct positioning of other views, including views that * were not previously onscreen (referred to as appearing views), but whose * pre-layout offscreen position can be determined given the extra * information about the pre-layout removed views.</p> * * <p>the second layout pass is the real layout in which only non-removed views * will be used. the only additional requirement during this pass is, if * {@link #supportspredictiveitemanimations()} returns true, to note which * views exist in the child list prior to layout and which are not there after * layout (referred to as disappearing views), and to position/layout those views * appropriately, without regard to the actual bounds of the recyclerview. this allows * the animation system to know the location to which to animate these disappearing * views.</p> * * <p>the default layoutmanager implementations for recyclerview handle all of these * requirements for animations already. clients of recyclerview can either use one * of these layout managers directly or look at their implementations of * onlayoutchildren() to see how they account for the appearing and * disappearing views.</p> * * @param recycler recycler to use for fetching potentially cached views for a * position * @param state transient state of recyclerview */ public void onlayoutchildren(recycler recycler, state state) { log.e(tag, "you must override onlayoutchildren(recycler recycler, state state) "); } /** * create a default <code>layoutparams</code> object for a child of the recyclerview. * * <p>layoutmanagers will often want to use a custom <code>layoutparams</code> type * to store extra information specific to the layout. client code should subclass * {@link recyclerview.layoutparams} for this purpose.</p> * * <p><em>important:</em> if you use your own custom <code>layoutparams</code> type * you must also override * {@link #checklayoutparams(layoutparams)}, * {@link #generatelayoutparams(android.view.viewgroup.layoutparams)} and * {@link #generatelayoutparams(android.content.context, android.util.attributeset)}.</p> * * @return a new layoutparams for a child view */ public abstract layoutparams generatedefaultlayoutparams(); /** * determines the validity of the supplied layoutparams object. * * <p>this should check to make sure that the object is of the correct type * and all values are within acceptable ranges. the default implementation * returns <code>true</code> for non-null params.</p> * * @param lp layoutparams object to check * @return true if this layoutparams object is valid, false otherwise */ public boolean checklayoutparams(layoutparams lp) { return lp != null; } /** * create a layoutparams object suitable for this layoutmanager, copying relevant * values from the supplied layoutparams object if possible. * * <p><em>important:</em> if you use your own custom <code>layoutparams</code> type * you must also override * {@link #checklayoutparams(layoutparams)}, * {@link #generatelayoutparams(android.view.viewgroup.layoutparams)} and * {@link #generatelayoutparams(android.content.context, android.util.attributeset)}.</p> * * @param lp source layoutparams object to copy values from * @return a new layoutparams object */ public layoutparams generatelayoutparams(viewgroup.layoutparams lp) { if (lp instanceof layoutparams) { return new layoutparams((layoutparams) lp); } else if (lp instanceof marginlayoutparams) { return new layoutparams((marginlayoutparams) lp); } else { return new layoutparams(lp); } } /** * create a layoutparams object suitable for this layoutmanager from * an inflated layout resource. * * <p><em>important:</em> if you use your own custom <code>layoutparams</code> type * you must also override * {@link #checklayoutparams(layoutparams)}, * {@link #generatelayoutparams(android.view.viewgroup.layoutparams)} and * {@link #generatelayoutparams(android.content.context, android.util.attributeset)}.</p> * * @param c context for obtaining styled attributes * @param attrs attributeset describing the supplied arguments * @return a new layoutparams object */ public layoutparams generatelayoutparams(context c, attributeset attrs) { return new layoutparams(c, attrs); } /** * scroll horizontally by dx pixels in screen coordinates and return the distance traveled. * the default implementation does nothing and returns 0. * * @param dx distance to scroll by in pixels. x increases as scroll position * approaches the right. * @param recycler recycler to use for fetching potentially cached views for a * position * @param state transient state of recyclerview * @return the actual distance scrolled. the return value will be negative if dx was * negative and scrolling proceeeded in that direction. * <code>math.abs(result)</code> may be less than dx if a boundary was reached. */ public int scrollhorizontallyby(int dx, recycler recycler, state state) { return 0; } /** * scroll vertically by dy pixels in screen coordinates and return the distance traveled. * the default implementation does nothing and returns 0. * * @param dy distance to scroll in pixels. y increases as scroll position * approaches the bottom. * @param recycler recycler to use for fetching potentially cached views for a * position * @param state transient state of recyclerview * @return the actual distance scrolled. the return value will be negative if dy was * negative and scrolling proceeeded in that direction. * <code>math.abs(result)</code> may be less than dy if a boundary was reached. */ public int scrollverticallyby(int dy, recycler recycler, state state) { return 0; } /** * query if horizontal scrolling is currently supported. the default implementation * returns false. * * @return true if this layoutmanager can scroll the current contents horizontally */ public boolean canscrollhorizontally() { return false; } /** * query if vertical scrolling is currently supported. the default implementation * returns false. * * @return true if this layoutmanager can scroll the current contents vertically */ public boolean canscrollvertically() { return false; } /** * scroll to the specified adapter position. * * actual position of the item on the screen depends on the layoutmanager implementation. * @param position scroll to this adapter position. */ public void scrolltoposition(int position) { if (debug) { log.e(tag, "you must implement scrolltoposition. it will soon become abstract"); } } /** * <p>smooth scroll to the specified adapter position.</p> * <p>to support smooth scrolling, override this method, create your {@link smoothscroller} * instance and call {@link #startsmoothscroll(smoothscroller)}. * </p> * @param recyclerview the recyclerview to which this layout manager is attached * @param state current state of recyclerview * @param position scroll to this adapter position. */ public void smoothscrolltoposition(recyclerview recyclerview, state state, int position) { log.e(tag, "you must override smoothscrolltoposition to support smooth scrolling"); } /** * <p>starts a smooth scroll using the provided smoothscroller.</p> * <p>calling this method will cancel any previous smooth scroll request.</p> * @param smoothscroller unstance which defines how smooth scroll should be animated */ public void startsmoothscroll(smoothscroller smoothscroller) { if (msmoothscroller != null && smoothscroller != msmoothscroller && msmoothscroller.isrunning()) { msmoothscroller.stop(); } msmoothscroller = smoothscroller; msmoothscroller.start(mrecyclerview, this); } /** * @return true if recycylerview is currently in the state of smooth scrolling. */ public boolean issmoothscrolling() { return msmoothscroller != null && msmoothscroller.isrunning(); } /** * returns the resolved layout direction for this recyclerview. * * @return {@link android.support.v4.view.viewcompat#layout_direction_rtl} if the layout * direction is rtl or returns * {@link android.support.v4.view.viewcompat#layout_direction_ltr} if the layout direction * is not rtl. */ public int getlayoutdirection() { return viewcompat.getlayoutdirection(mrecyclerview); } /** * add a view to the currently attached recyclerview if needed. layoutmanagers should * use this method to add views obtained from a {@link recycler} using * {@link recycler#getviewforposition(int)}. * * @param child view to add * @param index index to add child at */ public void addview(view child, int index) { if (mrecyclerview.manimatingviewindex >= 0) { if (index > mrecyclerview.manimatingviewindex) { throw new indexoutofboundsexception("index=" + index + " count=" + mrecyclerview.manimatingviewindex); } mrecyclerview.manimatingviewindex++; } final viewholder holder = getchildviewholderint(child); if (holder.isscrap()) { holder.unscrap(); mrecyclerview.attachviewtoparent(child, index, child.getlayoutparams()); if (dispatch_temp_detach) { viewcompat.dispatchfinishtemporarydetach(child); } } else { mrecyclerview.addview(child, index); final layoutparams lp = (layoutparams) child.getlayoutparams(); lp.minsetsdirty = true; final adapter adapter = mrecyclerview.getadapter(); if (adapter != null) { adapter.onviewattachedtowindow(getchildviewholderint(child)); } mrecyclerview.onchildattachedtowindow(child); if (msmoothscroller != null && msmoothscroller.isrunning()) { msmoothscroller.onchildattachedtowindow(child); } } } /** * add a view to the currently attached recyclerview if needed. layoutmanagers should * use this method to add views obtained from a {@link recycler} using * {@link recycler#getviewforposition(int)}. * * @param child view to add */ public void addview(view child) { if (mrecyclerview.manimatingviewindex >= 0) { addview(child, mrecyclerview.manimatingviewindex); } else { addview(child, -1); } } /** * remove a view from the currently attached recyclerview if needed. layoutmanagers should * use this method to completely remove a child view that is no longer needed. * layoutmanagers should strongly consider recycling removed views using * {@link recycler#recycleview(android.view.view)}. * * @param child view to remove */ public void removeview(view child) { final adapter adapter = mrecyclerview.getadapter(); if (adapter != null) { adapter.onviewdetachedfromwindow(getchildviewholderint(child)); } mrecyclerview.onchilddetachedfromwindow(child); mrecyclerview.removeview(child); if (mrecyclerview.manimatingviewindex >= 0) { mrecyclerview.manimatingviewindex--; } } /** * remove a view from the currently attached recyclerview if needed. layoutmanagers should * use this method to completely remove a child view that is no longer needed. * layoutmanagers should strongly consider recycling removed views using * {@link recycler#recycleview(android.view.view)}. * * @param index index of the child view to remove */ public void removeviewat(int index) { final view child = mrecyclerview.getchildat(index); if (child != null) { final adapter adapter = mrecyclerview.getadapter(); if (adapter != null) { adapter.onviewdetachedfromwindow(getchildviewholderint(child)); } mrecyclerview.onchilddetachedfromwindow(child); mrecyclerview.removeviewat(index); if (mrecyclerview.manimatingviewindex >= 0) { mrecyclerview.manimatingviewindex--; } } } /** * remove all views from the currently attached recyclerview. this will not recycle * any of the affected views; the layoutmanager is responsible for doing so if desired. */ public void removeallviews() { final adapter adapter = mrecyclerview.getadapter(); // only remove non-animating views final int childcount = mrecyclerview.getchildcount() - mrecyclerview.mnumanimatingviews; for (int i = 0; i < childcount; i++) { final view child = mrecyclerview.getchildat(i); if (adapter != null) { adapter.onviewdetachedfromwindow(getchildviewholderint(child)); } mrecyclerview.onchilddetachedfromwindow(child); } for (int i = childcount - 1; i >= 0; i--) { mrecyclerview.removeviewat(i); if (mrecyclerview.manimatingviewindex >= 0) { mrecyclerview.manimatingviewindex--; } } } /** * returns the adapter position of the item represented by the given view. * * @param view the view to query * @return the adapter position of the item which is rendered by this view. */ public int getposition(view view) { return ((recyclerview.layoutparams) view.getlayoutparams()).getviewposition(); } /** * <p>finds the view which represents the given adapter position.</p> * <p>this method traverses each child since it has no information about child order. * override this method to improve performance if your layoutmanager keeps data about * child views.</p> * * @param position position of the item in adapter * @return the child view that represents the given position or null if the position is not * visible */ public view findviewbyposition(int position) { final int childcount = getchildcount(); for (int i = 0; i < childcount; i++) { view child = getchildat(i); if (getposition(child) == position) { return child; } } return null; } /** * temporarily detach a child view. * * <p>layoutmanagers may want to perform a lightweight detach operation to rearrange * views currently attached to the recyclerview. generally layoutmanager implementations * will want to use {@link #detachandscrapview(android.view.view, recyclerview.recycler)} * so that the detached view may be rebound and reused.</p> * * <p>if a layoutmanager uses this method to detach a view, it <em>must</em> * {@link #attachview(android.view.view, int, recyclerview.layoutparams) reattach} * or {@link #removedetachedview(android.view.view) fully remove} the detached view * before the layoutmanager entry point method called by recyclerview returns.</p> * * @param child child to detach */ public void detachview(view child) { if (dispatch_temp_detach) { viewcompat.dispatchstarttemporarydetach(child); } mrecyclerview.detachviewfromparent(child); } /** * temporarily detach a child view. * * <p>layoutmanagers may want to perform a lightweight detach operation to rearrange * views currently attached to the recyclerview. generally layoutmanager implementations * will want to use {@link #detachandscrapview(android.view.view, recyclerview.recycler)} * so that the detached view may be rebound and reused.</p> * * <p>if a layoutmanager uses this method to detach a view, it <em>must</em> * {@link #attachview(android.view.view, int, recyclerview.layoutparams) reattach} * or {@link #removedetachedview(android.view.view) fully remove} the detached view * before the layoutmanager entry point method called by recyclerview returns.</p> * * @param index index of the child to detach */ public void detachviewat(int index) { if (dispatch_temp_detach) { viewcompat.dispatchstarttemporarydetach(mrecyclerview.getchildat(index)); } mrecyclerview.detachviewfromparent(index); if (mrecyclerview.manimatingviewindex >= 0) { --mrecyclerview.manimatingviewindex; } } /** * reattach a previously {@link #detachview(android.view.view) detached} view. * this method should not be used to reattach views that were previously * {@link #detachandscrapview(android.view.view, recyclerview.recycler)} scrapped}. * * @param child child to reattach * @param index intended child index for child * @param lp layoutparams for child */ public void attachview(view child, int index, layoutparams lp) { mrecyclerview.attachviewtoparent(child, index, lp); if (mrecyclerview.manimatingviewindex >= 0) { ++mrecyclerview.manimatingviewindex; } if (dispatch_temp_detach) { viewcompat.dispatchfinishtemporarydetach(child); } } /** * reattach a previously {@link #detachview(android.view.view) detached} view. * this method should not be used to reattach views that were previously * {@link #detachandscrapview(android.view.view, recyclerview.recycler)} scrapped}. * * @param child child to reattach * @param index intended child index for child */ public void attachview(view child, int index) { attachview(child, index, (layoutparams) child.getlayoutparams()); } /** * reattach a previously {@link #detachview(android.view.view) detached} view. * this method should not be used to reattach views that were previously * {@link #detachandscrapview(android.view.view, recyclerview.recycler)} scrapped}. * * @param child child to reattach */ public void attachview(view child) { attachview(child, -1); } /** * finish removing a view that was previously temporarily * {@link #detachview(android.view.view) detached}. * * @param child detached child to remove */ public void removedetachedview(view child) { mrecyclerview.removedetachedview(child, false); } /** * detach a child view and add it to a {@link recycler recycler's} scrap heap. * * <p>scrapping a view allows it to be rebound and reused to show updated or * different data.</p> * * @param child child to detach and scrap * @param recycler recycler to deposit the new scrap view into */ public void detachandscrapview(view child, recycler recycler) { detachview(child); recycler.scrapview(child); } /** * detach a child view and add it to a {@link recycler recycler's} scrap heap. * * <p>scrapping a view allows it to be rebound and reused to show updated or * different data.</p> * * @param index index of child to detach and scrap * @param recycler recycler to deposit the new scrap view into */ public void detachandscrapviewat(int index, recycler recycler) { final view child = getchildat(index); detachviewat(index); recycler.scrapview(child); } /** * remove a child view and recycle it using the given recycler. * * @param child child to remove and recycle * @param recycler recycler to use to recycle child */ public void removeandrecycleview(view child, recycler recycler) { removeview(child); recycler.recycleview(child); } /** * remove a child view and recycle it using the given recycler. * * @param index index of child to remove and recycle * @param recycler recycler to use to recycle child */ public void removeandrecycleviewat(int index, recycler recycler) { final view view = getchildat(index); removeviewat(index); recycler.recycleview(view); } /** * return the current number of child views attached to the parent recyclerview. * this does not include child views that were temporarily detached and/or scrapped. * * @return number of attached children */ public int getchildcount() { return mrecyclerview != null ? mrecyclerview.getchildcount() - mrecyclerview.mnumanimatingviews : 0; } /** * return the child view at the given index * @param index index of child to return * @return child view at index */ public view getchildat(int index) { return mrecyclerview != null ? mrecyclerview.getchildat(index) : null; } /** * return the width of the parent recyclerview * * @return width in pixels */ public int getwidth() { return mrecyclerview != null ? mrecyclerview.getwidth() : 0; } /** * return the height of the parent recyclerview * * @return height in pixels */ public int getheight() { return mrecyclerview != null ? mrecyclerview.getheight() : 0; } /** * return the left padding of the parent recyclerview * * @return padding in pixels */ public int getpaddingleft() { return mrecyclerview != null ? mrecyclerview.getpaddingleft() : 0; } /** * return the top padding of the parent recyclerview * * @return padding in pixels */ public int getpaddingtop() { return mrecyclerview != null ? mrecyclerview.getpaddingtop() : 0; } /** * return the right padding of the parent recyclerview * * @return padding in pixels */ public int getpaddingright() { return mrecyclerview != null ? mrecyclerview.getpaddingright() : 0; } /** * return the bottom padding of the parent recyclerview * * @return padding in pixels */ public int getpaddingbottom() { return mrecyclerview != null ? mrecyclerview.getpaddingbottom() : 0; } /** * return the start padding of the parent recyclerview * * @return padding in pixels */ public int getpaddingstart() { return mrecyclerview != null ? viewcompat.getpaddingstart(mrecyclerview) : 0; } /** * return the end padding of the parent recyclerview * * @return padding in pixels */ public int getpaddingend() { return mrecyclerview != null ? viewcompat.getpaddingend(mrecyclerview) : 0; } /** * returns true if the recyclerview this layoutmanager is bound to has focus. * * @return true if the recyclerview has focus, false otherwise. * @see view#isfocused() */ public boolean isfocused() { return mrecyclerview != null && mrecyclerview.isfocused(); } /** * returns true if the recyclerview this layoutmanager is bound to has or contains focus. * * @return true if the recyclerview has or contains focus * @see view#hasfocus() */ public boolean hasfocus() { return mrecyclerview != null && mrecyclerview.hasfocus(); } /** * return the number of items in the adapter bound to the parent recyclerview * * @return items in the bound adapter */ public int getitemcount() { final adapter a = mrecyclerview != null ? mrecyclerview.getadapter() : null; return a != null ? a.getitemcount() : 0; } /** * offset all child views attached to the parent recyclerview by dx pixels along * the horizontal axis. * * @param dx pixels to offset by */ public void offsetchildrenhorizontal(int dx) { if (mrecyclerview != null) { mrecyclerview.offsetchildrenhorizontal(dx); } } /** * offset all child views attached to the parent recyclerview by dy pixels along * the vertical axis. * * @param dy pixels to offset by */ public void offsetchildrenvertical(int dy) { if (mrecyclerview != null) { mrecyclerview.offsetchildrenvertical(dy); } } /** * temporarily detach and scrap all currently attached child views. views will be scrapped * into the given recycler. the recycler may prefer to reuse scrap views before * other views that were previously recycled. * * @param recycler recycler to scrap views into */ public void detachandscrapattachedviews(recycler recycler) { final int childcount = getchildcount(); for (int i = childcount - 1; i >= 0; i--) { final view v = getchildat(i); detachviewat(i); recycler.scrapview(v); } } /** * recycles the scrapped views. * <p> * when a view is detached and removed, it does not trigger a viewgroup invalidate. this is * the expected behavior if scrapped views are used for animations. otherwise, we need to * call remove and invalidate recyclerview to ensure ui update. * * @param recycler recycler * @param remove whether scrapped views should be removed from viewgroup or not. this * method will invalidate recyclerview if it removes any scrapped child. */ void removeandrecyclescrapint(recycler recycler, boolean remove) { final int scrapcount = recycler.getscrapcount(); for (int i = 0; i < scrapcount; i++) { final view scrap = recycler.getscrapviewat(i); if (remove) { mrecyclerview.removedetachedview(scrap, false); } recycler.quickrecyclescrapview(scrap); } recycler.clearscrap(); if (remove && scrapcount > 0) { mrecyclerview.invalidate(); } } /** * measure a child view using standard measurement policy, taking the padding * of the parent recyclerview and any added item decorations into account. * * <p>if the recyclerview can be scrolled in either dimension the caller may * pass 0 as the widthused or heightused parameters as they will be irrelevant.</p> * * @param child child view to measure * @param widthused width in pixels currently consumed by other views, if relevant * @param heightused height in pixels currently consumed by other views, if relevant */ public void measurechild(view child, int widthused, int heightused) { final layoutparams lp = (layoutparams) child.getlayoutparams(); final rect insets = mrecyclerview.getitemdecorinsetsforchild(child); widthused += insets.left + insets.right; heightused += insets.top + insets.bottom; final int widthspec = getchildmeasurespec(getwidth(), getpaddingleft() + getpaddingright() + widthused, lp.width, canscrollhorizontally()); final int heightspec = getchildmeasurespec(getheight(), getpaddingtop() + getpaddingbottom() + heightused, lp.height, canscrollvertically()); child.measure(widthspec, heightspec); } /** * measure a child view using standard measurement policy, taking the padding * of the parent recyclerview, any added item decorations and the child margins * into account. * * <p>if the recyclerview can be scrolled in either dimension the caller may * pass 0 as the widthused or heightused parameters as they will be irrelevant.</p> * * @param child child view to measure * @param widthused width in pixels currently consumed by other views, if relevant * @param heightused height in pixels currently consumed by other views, if relevant */ public void measurechildwithmargins(view child, int widthused, int heightused) { final layoutparams lp = (layoutparams) child.getlayoutparams(); final rect insets = mrecyclerview.getitemdecorinsetsforchild(child); widthused += insets.left + insets.right; heightused += insets.top + insets.bottom; final int widthspec = getchildmeasurespec(getwidth(), getpaddingleft() + getpaddingright() + lp.leftmargin + lp.rightmargin + widthused, lp.width, canscrollhorizontally()); final int heightspec = getchildmeasurespec(getheight(), getpaddingtop() + getpaddingbottom() + lp.topmargin + lp.bottommargin + heightused, lp.height, canscrollvertically()); child.measure(widthspec, heightspec); } /** * calculate a measurespec value for measuring a child view in one dimension. * * @param parentsize size of the parent view where the child will be placed * @param padding total space currently consumed by other elements of parent * @param childdimension desired size of the child view, or match_parent/wrap_content. * generally obtained from the child view's layoutparams * @param canscroll true if the parent recyclerview can scroll in this dimension * * @return a measurespec value for the child view */ public static int getchildmeasurespec(int parentsize, int padding, int childdimension, boolean canscroll) { int size = math.max(0, parentsize - padding); int resultsize = 0; int resultmode = 0; if (canscroll) { if (childdimension >= 0) { resultsize = childdimension; resultmode = measurespec.exactly; } else { // match_parent can't be applied since we can scroll in this dimension, wrap // instead using unspecified. resultsize = 0; resultmode = measurespec.unspecified; } } else { if (childdimension >= 0) { resultsize = childdimension; resultmode = measurespec.exactly; } else if (childdimension == layoutparams.fill_parent) { resultsize = size; resultmode = measurespec.exactly; } else if (childdimension == layoutparams.wrap_content) { resultsize = size; resultmode = measurespec.at_most; } } return measurespec.makemeasurespec(resultsize, resultmode); } /** * returns the measured width of the given child, plus the additional size of * any insets applied by {@link itemdecoration itemdecorations}. * * @param child child view to query * @return child's measured width plus <code>itemdecoration</code> insets * * @see view#getmeasuredwidth() */ public int getdecoratedmeasuredwidth(view child) { final rect insets = ((layoutparams) child.getlayoutparams()).mdecorinsets; return child.getmeasuredwidth() + insets.left + insets.right; } /** * returns the measured height of the given child, plus the additional size of * any insets applied by {@link itemdecoration itemdecorations}. * * @param child child view to query * @return child's measured height plus <code>itemdecoration</code> insets * * @see view#getmeasuredheight() */ public int getdecoratedmeasuredheight(view child) { final rect insets = ((layoutparams) child.getlayoutparams()).mdecorinsets; return child.getmeasuredheight() + insets.top + insets.bottom; } /** * lay out the given child view within the recyclerview using coordinates that * include any current {@link itemdecoration itemdecorations}. * * <p>layoutmanagers should prefer working in sizes and coordinates that include * item decoration insets whenever possible. this allows the layoutmanager to effectively * ignore decoration insets within measurement and layout code. see the following * methods:</p> * <ul> * <li>{@link #measurechild(view, int, int)}</li> * <li>{@link #measurechildwithmargins(view, int, int)}</li> * <li>{@link #getdecoratedleft(view)}</li> * <li>{@link #getdecoratedtop(view)}</li> * <li>{@link #getdecoratedright(view)}</li> * <li>{@link #getdecoratedbottom(view)}</li> * <li>{@link #getdecoratedmeasuredwidth(view)}</li> * <li>{@link #getdecoratedmeasuredheight(view)}</li> * </ul> * * @param child child to lay out * @param left left edge, with item decoration insets included * @param top top edge, with item decoration insets included * @param right right edge, with item decoration insets included * @param bottom bottom edge, with item decoration insets included * * @see view#layout(int, int, int, int) */ public void layoutdecorated(view child, int left, int top, int right, int bottom) { final rect insets = ((layoutparams) child.getlayoutparams()).mdecorinsets; child.layout(left + insets.left, top + insets.top, right - insets.right, bottom - insets.bottom); } /** * returns the left edge of the given child view within its parent, offset by any applied * {@link itemdecoration itemdecorations}. * * @param child child to query * @return child left edge with offsets applied */ public int getdecoratedleft(view child) { final rect insets = ((layoutparams) child.getlayoutparams()).mdecorinsets; return child.getleft() - insets.left; } /** * returns the top edge of the given child view within its parent, offset by any applied * {@link itemdecoration itemdecorations}. * * @param child child to query * @return child top edge with offsets applied */ public int getdecoratedtop(view child) { final rect insets = ((layoutparams) child.getlayoutparams()).mdecorinsets; return child.gettop() - insets.top; } /** * returns the right edge of the given child view within its parent, offset by any applied * {@link itemdecoration itemdecorations}. * * @param child child to query * @return child right edge with offsets applied */ public int getdecoratedright(view child) { final rect insets = ((layoutparams) child.getlayoutparams()).mdecorinsets; return child.getright() + insets.right; } /** * returns the bottom edge of the given child view within its parent, offset by any applied * {@link itemdecoration itemdecorations}. * * @param child child to query * @return child bottom edge with offsets applied */ public int getdecoratedbottom(view child) { final rect insets = ((layoutparams) child.getlayoutparams()).mdecorinsets; return child.getbottom() + insets.bottom; } /** * called when searching for a focusable view in the given direction has failed * for the current content of the recyclerview. * * <p>this is the layoutmanager's opportunity to populate views in the given direction * to fulfill the request if it can. the layoutmanager should attach and return * the view to be focused. the default implementation returns null.</p> * * @param focused the currently focused view * @param direction one of {@link view#focus_up}, {@link view#focus_down}, * {@link view#focus_left}, {@link view#focus_right}, * {@link view#focus_backward}, {@link view#focus_forward} * or 0 for not applicable * @param recycler the recycler to use for obtaining views for currently offscreen items * @param state transient state of recyclerview * @return the chosen view to be focused */ public view onfocussearchfailed(view focused, int direction, recycler recycler, state state) { return null; } /** * this method gives a layoutmanager an opportunity to intercept the initial focus search * before the default behavior of {@link focusfinder} is used. if this method returns * null focusfinder will attempt to find a focusable child view. if it fails * then {@link #onfocussearchfailed(view, int, recyclerview.recycler, recyclerview.state)} * will be called to give the layoutmanager an opportunity to add new views for items * that did not have attached views representing them. the layoutmanager should not add * or remove views from this method. * * @param focused the currently focused view * @param direction one of {@link view#focus_up}, {@link view#focus_down}, * {@link view#focus_left}, {@link view#focus_right}, * {@link view#focus_backward}, {@link view#focus_forward} * @return a descendant view to focus or null to fall back to default behavior. * the default implementation returns null. */ public view oninterceptfocussearch(view focused, int direction) { return null; } /** * called when a child of the recyclerview wants a particular rectangle to be positioned * onto the screen. see {@link viewparent#requestchildrectangleonscreen(android.view.view, * android.graphics.rect, boolean)} for more details. * * <p>the base implementation will attempt to perform a standard programmatic scroll * to bring the given rect into view, within the padded area of the recyclerview.</p> * * @param child the direct child making the request. * @param rect the rectangle in the child's coordinates the child * wishes to be on the screen. * @param immediate true to forbid animated or delayed scrolling, * false otherwise * @return whether the group scrolled to handle the operation */ public boolean requestchildrectangleonscreen(recyclerview parent, view child, rect rect, boolean immediate) { final int parentleft = getpaddingleft(); final int parenttop = getpaddingtop(); final int parentright = getwidth() - getpaddingright(); final int parentbottom = getheight() - getpaddingbottom(); final int childleft = child.getleft() + rect.left; final int childtop = child.gettop() + rect.top; final int childright = childleft + rect.right; final int childbottom = childtop + rect.bottom; final int offscreenleft = math.min(0, childleft - parentleft); final int offscreentop = math.min(0, childtop - parenttop); final int offscreenright = math.max(0, childright - parentright); final int offscreenbottom = math.max(0, childbottom - parentbottom); // favor the "start" layout direction over the end when bringing one side or the other // of a large rect into view. final int dx; if (viewcompat.getlayoutdirection(parent) == viewcompat.layout_direction_rtl) { dx = offscreenright != 0 ? offscreenright : offscreenleft; } else { dx = offscreenleft != 0 ? offscreenleft : offscreenright; } // favor bringing the top into view over the bottom final int dy = offscreentop != 0 ? offscreentop : offscreenbottom; if (dx != 0 || dy != 0) { if (immediate) { parent.scrollby(dx, dy); } else { parent.smoothscrollby(dx, dy); } return true; } return false; } /** * called when a descendant view of the recyclerview requests focus. * * <p>a layoutmanager wishing to keep focused views aligned in a specific * portion of the view may implement that behavior in an override of this method.</p> * * <p>if the layoutmanager executes different behavior that should override the default * behavior of scrolling the focused child on screen instead of running alongside it, * this method should return true.</p> * * @param parent the recyclerview hosting this layoutmanager * @param child direct child of the recyclerview containing the newly focused view * @param focused the newly focused view. this may be the same view as child * @return true if the default scroll behavior should be suppressed */ public boolean onrequestchildfocus(recyclerview parent, view child, view focused) { return false; } /** * called if the recyclerview this layoutmanager is bound to has a different adapter set. * the layoutmanager may use this opportunity to clear caches and configure state such * that it can relayout appropriately with the new data and potentially new view types. * * <p>the default implementation removes all currently attached views.</p> * * @param oldadapter the previous adapter instance. will be null if there was previously no * adapter. * @param newadapter the new adapter instance. might be null if * {@link #setadapter(recyclerview.adapter)} is called with {@code null}. */ public void onadapterchanged(adapter oldadapter, adapter newadapter) { } /** * called to populate focusable views within the recyclerview. * * <p>the layoutmanager implementation should return <code>true</code> if the default * behavior of {@link viewgroup#addfocusables(java.util.arraylist, int)} should be * suppressed.</p> * * <p>the default implementation returns <code>false</code> to trigger recyclerview * to fall back to the default viewgroup behavior.</p> * * @param recyclerview the recyclerview hosting this layoutmanager * @param views list of output views. this method should add valid focusable views * to this list. * @param direction one of {@link view#focus_up}, {@link view#focus_down}, * {@link view#focus_left}, {@link view#focus_right}, * {@link view#focus_backward}, {@link view#focus_forward} * @param focusablemode the type of focusables to be added. * * @return true to suppress the default behavior, false to add default focusables after * this method returns. * * @see #focusables_all * @see #focusables_touch_mode */ public boolean onaddfocusables(recyclerview recyclerview, arraylist<view> views, int direction, int focusablemode) { return false; } /** * called when items have been added to the adapter. the layoutmanager may choose to * requestlayout if the inserted items would require refreshing the currently visible set * of child views. (e.g. currently empty space would be filled by appended items, etc.) * * @param recyclerview * @param positionstart * @param itemcount */ public void onitemsadded(recyclerview recyclerview, int positionstart, int itemcount) { } /** * called when items have been removed from the adapter. * * @param recyclerview * @param positionstart * @param itemcount */ public void onitemsremoved(recyclerview recyclerview, int positionstart, int itemcount) { } /** * <p>override this method if you want to support scroll bars.</p> * * <p>read {@link recyclerview#computehorizontalscrollextent()} for details.</p> * * <p>default implementation returns 0.</p> * * @param state current state of recyclerview * @return the horizontal extent of the scrollbar's thumb * @see recyclerview#computehorizontalscrollextent() */ public int computehorizontalscrollextent(state state) { return 0; } /** * <p>override this method if you want to support scroll bars.</p> * * <p>read {@link recyclerview#computehorizontalscrolloffset()} for details.</p> * * <p>default implementation returns 0.</p> * * @param state current state of recyclerview where you can find total item count * @return the horizontal offset of the scrollbar's thumb * @see recyclerview#computehorizontalscrolloffset() */ public int computehorizontalscrolloffset(state state) { return 0; } /** * <p>override this method if you want to support scroll bars.</p> * * <p>read {@link recyclerview#computehorizontalscrollrange()} for details.</p> * * <p>default implementation returns 0.</p> * * @param state current state of recyclerview where you can find total item count * @return the total horizontal range represented by the vertical scrollbar * @see recyclerview#computehorizontalscrollrange() */ public int computehorizontalscrollrange(state state) { return 0; } /** * <p>override this method if you want to support scroll bars.</p> * * <p>read {@link recyclerview#computeverticalscrollextent()} for details.</p> * * <p>default implementation returns 0.</p> * * @param state current state of recyclerview * @return the vertical extent of the scrollbar's thumb * @see recyclerview#computeverticalscrollextent() */ public int computeverticalscrollextent(state state) { return 0; } /** * <p>override this method if you want to support scroll bars.</p> * * <p>read {@link recyclerview#computeverticalscrolloffset()} for details.</p> * * <p>default implementation returns 0.</p> * * @param state current state of recyclerview where you can find total item count * @return the vertical offset of the scrollbar's thumb * @see recyclerview#computeverticalscrolloffset() */ public int computeverticalscrolloffset(state state) { return 0; } /** * <p>override this method if you want to support scroll bars.</p> * * <p>read {@link recyclerview#computeverticalscrollrange()} for details.</p> * * <p>default implementation returns 0.</p> * * @param state current state of recyclerview where you can find total item count * @return the total vertical range represented by the vertical scrollbar * @see recyclerview#computeverticalscrollrange() */ public int computeverticalscrollrange(state state) { return 0; } /** * measure the attached recyclerview. implementations must call * {@link #setmeasureddimension(int, int)} before returning. * * <p>the default implementation will handle exactly measurements and respect * the minimum width and height properties of the host recyclerview if measured * as unspecified. at_most measurements will be treated as exactly and the recyclerview * will consume all available space.</p> * * @param recycler recycler * @param state transient state of recyclerview * @param widthspec width {@link android.view.view.measurespec} * @param heightspec height {@link android.view.view.measurespec} */ public void onmeasure(recycler recycler, state state, int widthspec, int heightspec) { final int widthmode = measurespec.getmode(widthspec); final int heightmode = measurespec.getmode(heightspec); final int widthsize = measurespec.getsize(widthspec); final int heightsize = measurespec.getsize(heightspec); int width = 0; int height = 0; switch (widthmode) { case measurespec.exactly: case measurespec.at_most: width = widthsize; break; case measurespec.unspecified: default: width = getminimumwidth(); break; } switch (heightmode) { case measurespec.exactly: case measurespec.at_most: height = heightsize; break; case measurespec.unspecified: default: height = getminimumheight(); break; } setmeasureddimension(width, height); } /** * {@link view#setmeasureddimension(int, int) set the measured dimensions} of the * host recyclerview. * * @param widthsize measured width * @param heightsize measured height */ public void setmeasureddimension(int widthsize, int heightsize) { mrecyclerview.setmeasureddimension(widthsize, heightsize); } /** * @return the host recyclerview's {@link view#getminimumwidth()} */ public int getminimumwidth() { return viewcompat.getminimumwidth(mrecyclerview); } /** * @return the host recyclerview's {@link view#getminimumheight()} */ public int getminimumheight() { return viewcompat.getminimumheight(mrecyclerview); } /** * <p>called when the layoutmanager should save its state. this is a good time to save your * scroll position, configuration and anything else that may be required to restore the same * layout state if the layoutmanager is recreated.</p> * <p>recyclerview does not verify if the layoutmanager has changed between state save and * restore. this will let you share information between your layoutmanagers but it is also * your responsibility to make sure they use the same parcelable class.</p> * * @return necessary information for layoutmanager to be able to restore its state */ public parcelable onsaveinstancestate() { return null; } public void onrestoreinstancestate(parcelable state) { } void stopsmoothscroller() { if (msmoothscroller != null) { msmoothscroller.stop(); } } private void onsmoothscrollerstopped(smoothscroller smoothscroller) { if (msmoothscroller == smoothscroller) { msmoothscroller = null; } } void removeandrecycleallviews(recycler recycler) { for (int i = getchildcount() - 1; i >= 0; i--) { removeandrecycleviewat(i, recycler); } } } /** * an itemdecoration allows the application to add a special drawing and layout offset * to specific item views from the adapter's data set. this can be useful for drawing dividers * between items, highlights, visual grouping boundaries and more. * * <p>all itemdecorations are drawn in the order they were added, before the item * views (in {@link itemdecoration#ondraw(canvas, recyclerview) ondraw()} and after the items * (in {@link itemdecoration#ondrawover(canvas, recyclerview)}.</p> */ public static abstract class itemdecoration { /** * draw any appropriate decorations into the canvas supplied to the recyclerview. * any content drawn by this method will be drawn before the item views are drawn, * and will thus appear underneath the views. * * @param c canvas to draw into * @param parent recyclerview this itemdecoration is drawing into */ public void ondraw(canvas c, recyclerview parent) { } /** * draw any appropriate decorations into the canvas supplied to the recyclerview. * any content drawn by this method will be drawn after the item views are drawn * and will thus appear over the views. * * @param c canvas to draw into * @param parent recyclerview this itemdecoration is drawing into */ public void ondrawover(canvas c, recyclerview parent) { } /** * retrieve any offsets for the given item. each field of <code>outrect</code> specifies * the number of pixels that the item view should be inset by, similar to padding or margin. * the default implementation sets the bounds of outrect to 0 and returns. * * <p>if this itemdecoration does not affect the positioning of item views it should set * all four fields of <code>outrect</code> (left, top, right, bottom) to zero * before returning.</p> * * @param outrect rect to receive the output. * @param itemposition adapter position of the item to offset * @param parent recyclerview this itemdecoration is decorating */ public void getitemoffsets(rect outrect, int itemposition, recyclerview parent) { outrect.set(0, 0, 0, 0); } } /** * an onitemtouchlistener allows the application to intercept touch events in progress at the * view hierarchy level of the recyclerview before those touch events are considered for * recyclerview's own scrolling behavior. * * <p>this can be useful for applications that wish to implement various forms of gestural * manipulation of item views within the recyclerview. onitemtouchlisteners may intercept * a touch interaction already in progress even if the recyclerview is already handling that * gesture stream itself for the purposes of scrolling.</p> */ public interface onitemtouchlistener { /** * silently observe and/or take over touch events sent to the recyclerview * before they are handled by either the recyclerview itself or its child views. * * <p>the onintercepttouchevent methods of each attached onitemtouchlistener will be run * in the order in which each listener was added, before any other touch processing * by the recyclerview itself or child views occurs.</p> * * @param e motionevent describing the touch event. all coordinates are in * the recyclerview's coordinate system. * @return true if this onitemtouchlistener wishes to begin intercepting touch events, false * to continue with the current behavior and continue observing future events in * the gesture. */ public boolean onintercepttouchevent(recyclerview rv, motionevent e); /** * process a touch event as part of a gesture that was claimed by returning true from * a previous call to {@link #onintercepttouchevent}. * * @param e motionevent describing the touch event. all coordinates are in * the recyclerview's coordinate system. */ public void ontouchevent(recyclerview rv, motionevent e); } /** * an onscrolllistener can be set on a recyclerview to receive messages * when a scrolling event has occurred on that recyclerview. * * @see recyclerview#setonscrolllistener(onscrolllistener) */ public interface onscrolllistener { public void onscrollstatechanged(int newstate); public void onscrolled(int dx, int dy); } /** * a recyclerlistener can be set on a recyclerview to receive messages whenever * a view is recycled. * * @see recyclerview#setrecyclerlistener(recyclerlistener) */ public interface recyclerlistener { /** * this method is called whenever the view in the viewholder is recycled. * * @param holder the viewholder containing the view that was recycled */ public void onviewrecycled(viewholder holder); } /** * interface definition for a callback to be invoked when an item in this * recyclerview.adapter has been clicked. */ public interface onitemclicklistener { /** * callback method to be invoked when an item in this recyclerview.adapter has * been clicked. * <p> * implementers can call getposition(position) if they need * to access the data associated with the selected item. * * @param view the view within the recyclerview.adapter that was clicked (this * will be a view provided by the adapter) * @param position the position of the view in the adapter. */ void onitemclick(view view, int position); } public static onitemclicklistener monitemclicklistener = null; /** * register a callback to be invoked when an item in this adapterview has * been clicked. * * @param listener the callback that will be invoked. */ public void setonitemclicklistener(onitemclicklistener listener) { monitemclicklistener = listener; } /** * @return the callback to be invoked with an item in this adapterview has * been clicked, or null id no callback has been set. */ public final onitemclicklistener getonitemclicklistener() { return monitemclicklistener; } /** * a viewholder describes an item view and metadata about its place within the recyclerview. * * <p>{@link adapter} implementations should subclass viewholder and add fields for caching * potentially expensive {@link view#findviewbyid(int)} results.</p> * * <p>while {@link layoutparams} belong to the {@link layoutmanager}, * {@link viewholder viewholders} belong to the adapter. adapters should feel free to use * their own custom viewholder implementations to store data that makes binding view contents * easier. implementations should assume that individual item views will hold strong references * to <code>viewholder</code> objects and that <code>recyclerview</code> instances may hold * strong references to extra off-screen item views for caching purposes</p> */ public static abstract class viewholder implements onclicklistener{ public final view itemview; int mposition = no_position; int moldposition = no_position; long mitemid = no_id; int mitemviewtype = invalid_type; /** * this viewholder has been bound to a position; mposition, mitemid and mitemviewtype * are all valid. */ static final int flag_bound = 1 << 0; /** * the data this viewholder's view reflects is stale and needs to be rebound * by the adapter. mposition and mitemid are consistent. */ static final int flag_update = 1 << 1; /** * this viewholder's data is invalid. the identity implied by mposition and mitemid * are not to be trusted and may no longer match the item view type. * this viewholder must be fully rebound to different data. */ static final int flag_invalid = 1 << 2; /** * this viewholder points at data that represents an item previously removed from the * data set. its view may still be used for things like outgoing animations. */ static final int flag_removed = 1 << 3; /** * this viewholder should not be recycled. this flag is set via setisrecyclable() * and is intended to keep views around during animations. */ static final int flag_not_recyclable = 1 << 4; private int mflags; private int misrecyclablecount = 0; // if non-null, view is currently considered scrap and may be reused for other data by the // scrap container. private recycler mscrapcontainer = null; @override public void onclick(view v) { if (monitemclicklistener != null) { monitemclicklistener.onitemclick(itemview, getposition()); } } public viewholder(view itemview) { if (itemview == null) { throw new illegalargumentexception("itemview may not be null"); } this.itemview = itemview; this.itemview.setonclicklistener(this); } void offsetposition(int offset) { if (moldposition == no_position) { moldposition = mposition; } mposition += offset; } void clearoldposition() { moldposition = no_position; } public final int getposition() { return moldposition == no_position ? mposition : moldposition; } public final long getitemid() { return mitemid; } public final int getitemviewtype() { return mitemviewtype; } boolean isscrap() { return mscrapcontainer != null; } void unscrap() { mscrapcontainer.unscrapview(this); mscrapcontainer = null; } void setscrapcontainer(recycler recycler) { mscrapcontainer = recycler; } boolean isinvalid() { return (mflags & flag_invalid) != 0; } boolean needsupdate() { return (mflags & flag_update) != 0; } boolean isbound() { return (mflags & flag_bound) != 0; } boolean isremoved() { return (mflags & flag_removed) != 0; } void setflags(int flags, int mask) { mflags = (mflags & ~mask) | (flags & mask); } void addflags(int flags) { mflags |= flags; } void clearflagsforsharedpool() { mflags = 0; } @override public string tostring() { final stringbuilder sb = new stringbuilder("viewholder{" + integer.tohexstring(hashcode()) + " position=" + mposition + " id=" + mitemid); if (isscrap()) sb.append(" scrap"); if (isinvalid()) sb.append(" invalid"); if (!isbound()) sb.append(" unbound"); if (needsupdate()) sb.append(" update"); if (isremoved()) sb.append(" removed"); sb.append("}"); return sb.tostring(); } /** * informs the recycler whether this item can be recycled. views which are not * recyclable will not be reused for other items until setisrecyclable() is * later set to true. calls to setisrecyclable() should always be paired (one * call to setisrecyclabe(false) should always be matched with a later call to * setisrecyclable(true)). pairs of calls may be nested, as the state is internally * reference-counted. * * @param recyclable whether this item is available to be recycled. default value * is true. */ public final void setisrecyclable(boolean recyclable) { misrecyclablecount = recyclable ? misrecyclablecount - 1 : misrecyclablecount + 1; if (misrecyclablecount < 0) { misrecyclablecount = 0; log.e(view_log_tag, "isrecyclable decremented below 0: " + "unmatched pair of setisrecyable() calls"); } else if (!recyclable && misrecyclablecount == 1) { mflags |= flag_not_recyclable; } else if (recyclable && misrecyclablecount == 0) { mflags &= ~flag_not_recyclable; } } /** * @see {@link #setisrecyclable(boolean)} * * @return true if this item is available to be recycled, false otherwise. */ public final boolean isrecyclable() { return (mflags & flag_not_recyclable) == 0 && !viewcompat.hastransientstate(itemview); } } /** * queued operation to happen when child views are updated. */ private static class updateop { public static final int add = 0; public static final int remove = 1; public static final int update = 2; static final int pool_size = 30; public int cmd; public int positionstart; public int itemcount; public updateop(int cmd, int positionstart, int itemcount) { this.cmd = cmd; this.positionstart = positionstart; this.itemcount = itemcount; } } updateop obtainupdateop(int cmd, int positionstart, int itemcount) { updateop op = mupdateoppool.acquire(); if (op == null) { op = new updateop(cmd, positionstart, itemcount); } else { op.cmd = cmd; op.positionstart = positionstart; op.itemcount = itemcount; } return op; } void recycleupdateop(updateop op) { mupdateoppool.release(op); } /** * {@link android.view.viewgroup.marginlayoutparams layoutparams} subclass for children of * {@link recyclerview}. custom {@link layoutmanager layout managers} are encouraged * to create their own subclass of this <code>layoutparams</code> class * to store any additional required per-child view metadata about the layout. */ public static class layoutparams extends marginlayoutparams { viewholder mviewholder; final rect mdecorinsets = new rect(); boolean minsetsdirty = true; public layoutparams(context c, attributeset attrs) { super(c, attrs); } public layoutparams(int width, int height) { super(width, height); } public layoutparams(marginlayoutparams source) { super(source); } public layoutparams(viewgroup.layoutparams source) { super(source); } public layoutparams(layoutparams source) { super((viewgroup.layoutparams) source); } /** * returns true if the view this layoutparams is attached to needs to have its content * updated from the corresponding adapter. * * @return true if the view should have its content updated */ public boolean viewneedsupdate() { return mviewholder.needsupdate(); } /** * returns true if the view this layoutparams is attached to is now representing * potentially invalid data. a layoutmanager should scrap/recycle it. * * @return true if the view is invalid */ public boolean isviewinvalid() { return mviewholder.isinvalid(); } /** * returns true if the adapter data item corresponding to the view this layoutparams * is attached to has been removed from the data set. a layoutmanager may choose to * treat it differently in order to animate its outgoing or disappearing state. * * @return true if the item the view corresponds to was removed from the data set */ public boolean isitemremoved() { return mviewholder.isremoved(); } /** * returns the position that the view this layoutparams is attached to corresponds to. * * @return the adapter position this view was bound from */ public int getviewposition() { return mviewholder.getposition(); } } /** * observer base class for watching changes to an {@link adapter}. * see {@link adapter#registeradapterdataobserver(adapterdataobserver)}. */ public static abstract class adapterdataobserver { public void onchanged() { // do nothing } public void onitemrangechanged(int positionstart, int itemcount) { // do nothing } public void onitemrangeinserted(int positionstart, int itemcount) { // do nothing } public void onitemrangeremoved(int positionstart, int itemcount) { // do nothing } } /** * <p>base class for smooth scrolling. handles basic tracking of the target view position and * provides methods to trigger a programmatic scroll.</p> * * @see linearsmoothscroller */ public static abstract class smoothscroller { private int mtargetposition = recyclerview.no_position; private recyclerview mrecyclerview; private layoutmanager mlayoutmanager; private boolean mpendinginitialrun; private boolean mrunning; private view mtargetview; private final action mrecyclingaction; public smoothscroller() { mrecyclingaction = new action(0, 0); } /** * starts a smooth scroll for the given target position. * <p>in each animation step, {@link recyclerview} will check * for the target view and call either * {@link #ontargetfound(android.view.view, recyclerview.state, smoothscroller.action)} or * {@link #onseektargetstep(int, int, recyclerview.state, smoothscroller.action)} until * smoothscroller is stopped.</p> * * <p>note that if recyclerview finds the target view, it will automatically stop the * smoothscroller. this <b>does not</b> mean that scroll will stop, it only means it will * stop calling smoothscroller in each animation step.</p> */ void start(recyclerview recyclerview, layoutmanager layoutmanager) { mrecyclerview = recyclerview; mlayoutmanager = layoutmanager; if (mtargetposition == recyclerview.no_position) { throw new illegalargumentexception("invalid target position"); } mrecyclerview.mstate.mtargetposition = mtargetposition; mrunning = true; mpendinginitialrun = true; mtargetview = findviewbyposition(gettargetposition()); onstart(); mrecyclerview.mviewflinger.postonanimation(); } public void settargetposition(int targetposition) { mtargetposition = targetposition; } /** * @return the layoutmanager to which this smoothscroller is attached */ public layoutmanager getlayoutmanager() { return mlayoutmanager; } /** * stops running the smoothscroller in each animation callback. note that this does not * cancel any existing {@link action} updated by * {@link #ontargetfound(android.view.view, recyclerview.state, smoothscroller.action)} or * {@link #onseektargetstep(int, int, recyclerview.state, smoothscroller.action)}. */ final protected void stop() { if (!mrunning) { return; } onstop(); mrecyclerview.mstate.mtargetposition = recyclerview.no_position; mtargetview = null; mtargetposition = recyclerview.no_position; mpendinginitialrun = false; mrunning = false; // trigger a cleanup mlayoutmanager.onsmoothscrollerstopped(this); // clear references to avoid any potential leak by a custom smooth scroller mlayoutmanager = null; mrecyclerview = null; } /** * returns true if smoothscroller has beens started but has not received the first * animation * callback yet. * * @return true if this smoothscroller is waiting to start */ public boolean ispendinginitialrun() { return mpendinginitialrun; } /** * @return true if smoothscroller is currently active */ public boolean isrunning() { return mrunning; } /** * returns the adapter position of the target item * * @return adapter position of the target item or * {@link recyclerview#no_position} if no target view is set. */ public int gettargetposition() { return mtargetposition; } private void onanimation(int dx, int dy) { if (!mrunning || mtargetposition == recyclerview.no_position) { stop(); } mpendinginitialrun = false; if (mtargetview != null) { // verify target position if (getchildposition(mtargetview) == mtargetposition) { ontargetfound(mtargetview, mrecyclerview.mstate, mrecyclingaction); mrecyclingaction.runinnecessary(mrecyclerview); stop(); } else { log.e(tag, "passed over target position while smooth scrolling."); mtargetview = null; } } if (mrunning) { onseektargetstep(dx, dy, mrecyclerview.mstate, mrecyclingaction); mrecyclingaction.runinnecessary(mrecyclerview); } } /** * @see recyclerview#getchildposition(android.view.view) */ public int getchildposition(view view) { return mrecyclerview.getchildposition(view); } /** * @see recyclerview#getchildcount() */ public int getchildcount() { return mrecyclerview.getchildcount(); } /** * @see recyclerview.layoutmanager#findviewbyposition(int) */ public view findviewbyposition(int position) { return mrecyclerview.mlayout.findviewbyposition(position); } /** * @see recyclerview#scrolltoposition(int) */ public void instantscrolltoposition(int position) { mrecyclerview.scrolltoposition(position); } protected void onchildattachedtowindow(view child) { if (getchildposition(child) == gettargetposition()) { mtargetview = child; if (debug) { log.d(tag, "smooth scroll target view has been attached"); } } } /** * normalizes the vector. * @param scrollvector the vector that points to the target scroll position */ protected void normalize(pointf scrollvector) { final double magnitute = math.sqrt(scrollvector.x * scrollvector.x + scrollvector.y * scrollvector.y); scrollvector.x /= magnitute; scrollvector.y /= magnitute; } /** * called when smooth scroll is started. this might be a good time to do setup. */ abstract protected void onstart(); /** * called when smooth scroller is stopped. this is a good place to cleanup your state etc. * @see #stop() */ abstract protected void onstop(); /** * <p>recyclerview will call this method each time it scrolls until it can find the target * position in the layout.</p> * <p>smoothscroller should check dx, dy and if scroll should be changed, update the * provided {@link action} to define the next scroll.</p> * * @param dx last scroll amount horizontally * @param dy last scroll amount verticaully * @param state transient state of recyclerview * @param action if you want to trigger a new smooth scroll and cancel the previous one, * update this object. */ abstract protected void onseektargetstep(int dx, int dy, state state, action action); /** * called when the target position is laid out. this is the last callback smoothscroller * will receive and it should update the provided {@link action} to define the scroll * details towards the target view. * @param targetview the view element which render the target position. * @param state transient state of recyclerview * @param action action instance that you should update to define final scroll action * towards the targetview * @return an {@link action} to finalize the smooth scrolling */ abstract protected void ontargetfound(view targetview, state state, action action); /** * holds information about a smooth scroll request by a {@link smoothscroller}. */ public static class action { public static final int undefined_duration = integer.min_value; private int mdx; private int mdy; private int mduration; private interpolator minterpolator; private boolean changed = false; // we track this variable to inform custom implementer if they are updating the action // in every animation callback private int consecutiveupdates = 0; /** * @param dx pixels to scroll horizontally * @param dy pixels to scroll vertically */ public action(int dx, int dy) { this(dx, dy, undefined_duration, null); } /** * @param dx pixels to scroll horizontally * @param dy pixels to scroll vertically * @param duration duration of the animation in milliseconds */ public action(int dx, int dy, int duration) { this(dx, dy, duration, null); } /** * @param dx pixels to scroll horizontally * @param dy pixels to scroll vertically * @param duration duration of the animation in milliseconds * @param interpolator interpolator to be used when calculating scroll position in each * animation step */ public action(int dx, int dy, int duration, interpolator interpolator) { mdx = dx; mdy = dy; mduration = duration; minterpolator = interpolator; } private void runinnecessary(recyclerview recyclerview) { if (changed) { validate(); if (minterpolator == null) { if (mduration == undefined_duration) { recyclerview.mviewflinger.smoothscrollby(mdx, mdy); } else { recyclerview.mviewflinger.smoothscrollby(mdx, mdy, mduration); } } else { recyclerview.mviewflinger.smoothscrollby(mdx, mdy, mduration, minterpolator); } consecutiveupdates ++; if (consecutiveupdates > 10) { // a new action is being set in every animation step. this looks like a bad // implementation. inform developer. log.e(tag, "smooth scroll action is being updated too frequently. make sure" + " you are not changing it unless necessary"); } changed = false; } else { consecutiveupdates = 0; } } private void validate() { if (minterpolator != null && mduration < 1) { throw new illegalstateexception("if you provide an interpolator, you must" + " set a positive duration"); } else if (mduration < 1) { throw new illegalstateexception("scroll duration must be a positive number"); } } public int getdx() { return mdx; } public void setdx(int dx) { changed = true; mdx = dx; } public int getdy() { return mdy; } public void setdy(int dy) { changed = true; mdy = dy; } public int getduration() { return mduration; } public void setduration(int duration) { changed = true; mduration = duration; } public interpolator getinterpolator() { return minterpolator; } /** * sets the interpolator to calculate scroll steps * @param interpolator the interpolator to use. if you specify an interpolator, you must * also set the duration. * @see #setduration(int) */ public void setinterpolator(interpolator interpolator) { changed = true; minterpolator = interpolator; } /** * updates the action with given parameters. * @param dx pixels to scroll horizontally * @param dy pixels to scroll vertically * @param duration duration of the animation in milliseconds * @param interpolator interpolator to be used when calculating scroll position in each * animation step */ public void update(int dx, int dy, int duration, interpolator interpolator) { mdx = dx; mdy = dy; mduration = duration; minterpolator = interpolator; changed = true; } } } static class adapterdataobservable extends observable<adapterdataobserver> { public boolean hasobservers() { return !mobservers.isempty(); } public void notifychanged() { // since onchanged() is implemented by the app, it could do anything, including // removing itself from {@link mobservers} - and that could cause problems if // an iterator is used on the arraylist {@link mobservers}. // to avoid such problems, just march thru the list in the reverse order. for (int i = mobservers.size() - 1; i >= 0; i--) { mobservers.get(i).onchanged(); } } public void notifyitemrangechanged(int positionstart, int itemcount) { // since onitemrangechanged() is implemented by the app, it could do anything, including // removing itself from {@link mobservers} - and that could cause problems if // an iterator is used on the arraylist {@link mobservers}. // to avoid such problems, just march thru the list in the reverse order. for (int i = mobservers.size() - 1; i >= 0; i--) { mobservers.get(i).onitemrangechanged(positionstart, itemcount); } } public void notifyitemrangeinserted(int positionstart, int itemcount) { // since onitemrangeinserted() is implemented by the app, it could do anything, // including removing itself from {@link mobservers} - and that could cause problems if // an iterator is used on the arraylist {@link mobservers}. // to avoid such problems, just march thru the list in the reverse order. for (int i = mobservers.size() - 1; i >= 0; i--) { mobservers.get(i).onitemrangeinserted(positionstart, itemcount); } } public void notifyitemrangeremoved(int positionstart, int itemcount) { // since onitemrangeremoved() is implemented by the app, it could do anything, including // removing itself from {@link mobservers} - and that could cause problems if // an iterator is used on the arraylist {@link mobservers}. // to avoid such problems, just march thru the list in the reverse order. for (int i = mobservers.size() - 1; i >= 0; i--) { mobservers.get(i).onitemrangeremoved(positionstart, itemcount); } } } static class savedstate extends basesavedstate { parcelable mlayoutstate; /** * called by creator */ savedstate(parcel in) { super(in); mlayoutstate = in.readparcelable(layoutmanager.class.getclassloader()); } /** * called by onsaveinstancestate */ savedstate(parcelable superstate) { super(superstate); } @override public void writetoparcel(parcel dest, int flags) { super.writetoparcel(dest, flags); dest.writeparcelable(mlayoutstate, 0); } private void copyfrom(savedstate other) { mlayoutstate = other.mlayoutstate; } public static final parcelable.creator<savedstate> creator = new parcelable.creator<savedstate>() { @override public savedstate createfromparcel(parcel in) { return new savedstate(in); } @override public savedstate[] newarray(int size) { return new savedstate[size]; } }; } /** * <p>contains useful information about the current recyclerview state like target scroll * position or view focus. state object can also keep arbitrary data, identified by resource * ids.</p> * <p>often times, recyclerview components will need to pass information between each other. * to provide a well defined data bus between components, recyclerview passes the same state * object to component callbacks and these components can use it to exchange data.</p> * <p>if you implement custom components, you can use state's put/get/remove methods to pass * data between your components without needing to manage their lifecycles.</p> */ public static class state { private int mtargetposition = recyclerview.no_position; private arraymap<viewholder, itemholderinfo> mprelayoutholdermap = new arraymap<viewholder, itemholderinfo>(); private arraymap<viewholder, itemholderinfo> mpostlayoutholdermap = new arraymap<viewholder, itemholderinfo>(); private sparsearray<object> mdata; /** * number of items adapter has. */ private int mitemcount = 0; /** * number of items adapter had in the previous layout. */ private int mpreviouslayoutitemcount = 0; /** * number of items that were not laid out but has been deleted from the adapter after the * previous layout. */ private int mdeletedinvisibleitemcountsincepreviouslayout = 0; private boolean mstructurechanged = false; private boolean minprelayout = false; state reset() { mtargetposition = recyclerview.no_position; if (mdata != null) { mdata.clear(); } mitemcount = 0; mstructurechanged = false; return this; } public boolean isprelayout() { return minprelayout; } /** * removes the mapping from the specified id, if there was any. * @param resourceid id of the resource you want to remove. it is suggested to use r.id.* to * preserve cross functionality and avoid conflicts. */ public void remove(int resourceid) { if (mdata == null) { return; } mdata.remove(resourceid); } /** * gets the object mapped from the specified id, or <code>null</code> * if no such data exists. * * @param resourceid id of the resource you want to remove. it is suggested to use r.id.* * to * preserve cross functionality and avoid conflicts. */ public <t> t get(int resourceid) { if (mdata == null) { return null; } return (t) mdata.get(resourceid); } /** * adds a mapping from the specified id to the specified value, replacing the previous * mapping from the specified key if there was one. * * @param resourceid id of the resource you want to add. it is suggested to use r.id.* to * preserve cross functionality and avoid conflicts. * @param data the data you want to associate with the resourceid. */ public void put(int resourceid, object data) { if (mdata == null) { mdata = new sparsearray<object>(); } mdata.put(resourceid, data); } /** * if scroll is triggered to make a certain item visible, this value will return the * adapter index of that item. * @return adapter index of the target item or * {@link recyclerview#no_position} if there is no target * position. */ public int gettargetscrollposition() { return mtargetposition; } /** * returns if current scroll has a target position. * @return true if scroll is being triggered to make a certain position visible * @see #gettargetscrollposition() */ public boolean hastargetscrollposition() { return mtargetposition != recyclerview.no_position; } /** * @return true if the structure of the data set has changed since the last call to * onlayoutchildren, false otherwise */ public boolean didstructurechange() { return mstructurechanged; } /** * @return total number of items to be laid out. note that, this number is not necessarily * equal to the number of items in the adapter, so you should always use this number for * your position calculations and never call adapter directly. */ public int getitemcount() { return minprelayout ? (mpreviouslayoutitemcount - mdeletedinvisibleitemcountsincepreviouslayout) : mitemcount; } } /** * internal listener that manages items after animations finish. this is how items are * retained (not recycled) during animations, but allowed to be recycled afterwards. * it depends on the contract with the itemanimator to call the appropriate dispatch*finished() * method on the animator's listener when it is done animating any item. */ private class itemanimatorrestorelistener implements itemanimator.itemanimatorlistener { @override public void onremovefinished(viewholder item) { item.setisrecyclable(true); removeanimatingview(item.itemview); removedetachedview(item.itemview, false); } @override public void onaddfinished(viewholder item) { item.setisrecyclable(true); removeanimatingview(item.itemview); } @override public void onmovefinished(viewholder item) { item.setisrecyclable(true); removeanimatingview(item.itemview); } }; /** * this class defines the animations that take place on items as changes are made * to the adapter. * * subclasses of itemanimator can be used to implement custom animations for actions on * viewholder items. the recyclerview will manage retaining these items while they * are being animated, but implementors must call the appropriate "finished" * method when each item animation is done ({@link #dispatchremovefinished(viewholder)}, * {@link #dispatchmovefinished(viewholder)}, or {@link #dispatchaddfinished(viewholder)}). * * <p>by default, recyclerview uses {@link defaultitemanimator}</p> * * @see #setitemanimator(itemanimator) */ public static abstract class itemanimator { private itemanimatorlistener mlistener = null; private arraylist<itemanimatorfinishedlistener> mfinishedlisteners = new arraylist<itemanimatorfinishedlistener>(); private long maddduration = 120; private long mremoveduration = 120; private long mmoveduration = 250; /** * gets the current duration for which all move animations will run. * * @return the current move duration */ public long getmoveduration() { return mmoveduration; } /** * sets the current duration for which all move animations will run. * * @param moveduration the current move duration */ public void setmoveduration(long moveduration) { mmoveduration = moveduration; } /** * gets the current duration for which all add animations will run. * * @return the current add duration */ public long getaddduration() { return maddduration; } /** * sets the current duration for which all add animations will run. * * @param addduration the current add duration */ public void setaddduration(long addduration) { maddduration = addduration; } /** * gets the current duration for which all remove animations will run. * * @return the current remove duration */ public long getremoveduration() { return mremoveduration; } /** * sets the current duration for which all remove animations will run. * * @param removeduration the current remove duration */ public void setremoveduration(long removeduration) { mremoveduration = removeduration; } /** * internal only: * sets the listener that must be called when the animator is finished * animating the item (or immediately if no animation happens). this is set * internally and is not intended to be set by external code. * * @param listener the listener that must be called. */ void setlistener(itemanimatorlistener listener) { mlistener = listener; } /** * called when there are pending animations waiting to be started. this state * is governed by the return values from {@link #animateadd(viewholder) animateadd()}, * {@link #animatemove(viewholder, int, int, int, int) animatemove()}, and * {@link #animateremove(viewholder) animateremove()}, which inform the * recyclerview that the itemanimator wants to be called later to start the * associated animations. runpendinganimations() will be scheduled to be run * on the next frame. */ abstract public void runpendinganimations(); /** * called when an item is removed from the recyclerview. implementors can choose * whether and how to animate that change, but must always call * {@link #dispatchremovefinished(viewholder)} when done, either * immediately (if no animation will occur) or after the animation actually finishes. * the return value indicates whether an animation has been set up and whether the * itemanimators {@link #runpendinganimations()} method should be called at the * next opportunity. this mechanism allows itemanimator to set up individual animations * as separate calls to {@link #animateadd(viewholder) animateadd()}, * {@link #animatemove(viewholder, int, int, int, int) animatemove()}, and * {@link #animateremove(viewholder) animateremove()} come in one by one, then * start the animations together in the later call to {@link #runpendinganimations()}. * * <p>this method may also be called for disappearing items which continue to exist in the * recyclerview, but for which the system does not have enough information to animate * them out of view. in that case, the default animation for removing items is run * on those items as well.</p> * * @param holder the item that is being removed. * @return true if a later call to {@link #runpendinganimations()} is requested, * false otherwise. */ abstract public boolean animateremove(viewholder holder); /** * called when an item is added to the recyclerview. implementors can choose * whether and how to animate that change, but must always call * {@link #dispatchaddfinished(viewholder)} when done, either * immediately (if no animation will occur) or after the animation actually finishes. * the return value indicates whether an animation has been set up and whether the * itemanimators {@link #runpendinganimations()} method should be called at the * next opportunity. this mechanism allows itemanimator to set up individual animations * as separate calls to {@link #animateadd(viewholder) animateadd()}, * {@link #animatemove(viewholder, int, int, int, int) animatemove()}, and * {@link #animateremove(viewholder) animateremove()} come in one by one, then * start the animations together in the later call to {@link #runpendinganimations()}. * * <p>this method may also be called for appearing items which were already in the * recyclerview, but for which the system does not have enough information to animate * them into view. in that case, the default animation for adding items is run * on those items as well.</p> * * @param holder the item that is being added. * @return true if a later call to {@link #runpendinganimations()} is requested, * false otherwise. */ abstract public boolean animateadd(viewholder holder); /** * called when an item is moved in the recyclerview. implementors can choose * whether and how to animate that change, but must always call * {@link #dispatchmovefinished(viewholder)} when done, either * immediately (if no animation will occur) or after the animation actually finishes. * the return value indicates whether an animation has been set up and whether the * itemanimators {@link #runpendinganimations()} method should be called at the * next opportunity. this mechanism allows itemanimator to set up individual animations * as separate calls to {@link #animateadd(viewholder) animateadd()}, * {@link #animatemove(viewholder, int, int, int, int) animatemove()}, and * {@link #animateremove(viewholder) animateremove()} come in one by one, then * start the animations together in the later call to {@link #runpendinganimations()}. * * @param holder the item that is being moved. * @return true if a later call to {@link #runpendinganimations()} is requested, * false otherwise. */ abstract public boolean animatemove(viewholder holder, int fromx, int fromy, int tox, int toy); /** * method to be called by subclasses when a remove animation is done. * * @param item the item which has been removed */ public final void dispatchremovefinished(viewholder item) { if (mlistener != null) { mlistener.onremovefinished(item); } } /** * method to be called by subclasses when a move animation is done. * * @param item the item which has been moved */ public final void dispatchmovefinished(viewholder item) { if (mlistener != null) { mlistener.onmovefinished(item); } } /** * method to be called by subclasses when an add animation is done. * * @param item the item which has been added */ public final void dispatchaddfinished(viewholder item) { if (mlistener != null) { mlistener.onaddfinished(item); } } /** * method called when an animation on a view should be ended immediately. * this could happen when other events, like scrolling, occur, so that * animating views can be quickly put into their proper end locations. * implementations should ensure that any animations running on the item * are canceled and affected properties are set to their end values. * also, appropriate dispatch methods (e.g., {@link #dispatchaddfinished(viewholder)} * should be called since the animations are effectively done when this * method is called. * * @param item the item for which an animation should be stopped. */ abstract public void endanimation(viewholder item); /** * method called when all item animations should be ended immediately. * this could happen when other events, like scrolling, occur, so that * animating views can be quickly put into their proper end locations. * implementations should ensure that any animations running on any items * are canceled and affected properties are set to their end values. * also, appropriate dispatch methods (e.g., {@link #dispatchaddfinished(viewholder)} * should be called since the animations are effectively done when this * method is called. */ abstract public void endanimations(); /** * method which returns whether there are any item animations currently running. * this method can be used to determine whether to delay other actions until * animations end. * * @return true if there are any item animations currently running, false otherwise. */ abstract public boolean isrunning(); /** * like {@link #isrunning()}, this method returns whether there are any item * animations currently running. addtionally, the listener passed in will be called * when there are no item animations running, either immediately (before the method * returns) if no animations are currently running, or when the currently running * animations are {@link #dispatchanimationsfinished() finished}. * * <p>note that the listener is transient - it is either called immediately and not * stored at all, or stored only until it is called when running animations * are finished sometime later.</p> * * @param listener a listener to be called immediately if no animations are running * or later when currently-running animations have finished. a null listener is * equivalent to calling {@link #isrunning()}. * @return true if there are any item animations currently running, false otherwise. */ public final boolean isrunning(itemanimatorfinishedlistener listener) { boolean running = isrunning(); if (listener != null) { if (!running) { listener.onanimationsfinished(); } else { mfinishedlisteners.add(listener); } } return running; } /** * the interface to be implemented by listeners to animation events from this * itemanimator. this is used internally and is not intended for developers to * create directly. */ private interface itemanimatorlistener { void onremovefinished(viewholder item); void onaddfinished(viewholder item); void onmovefinished(viewholder item); } /** * this method should be called by itemanimator implementations to notify * any listeners that all pending and active item animations are finished. */ public final void dispatchanimationsfinished() { final int count = mfinishedlisteners.size(); for (int i = 0; i < count; ++i) { mfinishedlisteners.get(i).onanimationsfinished(); } mfinishedlisteners.clear(); } /** * this interface is used to inform listeners when all pending or running animations * in an itemanimator are finished. this can be used, for example, to delay an action * in a data set until currently-running animations are complete. * * @see #isrunning(itemanimatorfinishedlistener) */ public interface itemanimatorfinishedlistener { void onanimationsfinished(); } } /** * internal data structure that holds information about an item's bounds. * this information is used in calculating item animations. */ private static class itemholderinfo { viewholder holder; int left, top, right, bottom; int position; itemholderinfo(viewholder holder, int left, int top, int right, int bottom, int position) { this.holder = holder; this.left = left; this.top = top; this.right = right; this.bottom = bottom; this.position = position; } } }
以上所述是小编给大家介绍的解决recyclerview无法onitemclick问题的两种方法,希望对大家有所帮助
上一篇: Java实现颜色渐变效果