-
概述
在一開始使用RecyclerView的過程中,可能會遇到這么一種情況,就是我們的item View已經(jīng)設(shè)置成match_parent了,RecyclerView也設(shè)置成match_parent,但是在顯示的時候卻只顯示內(nèi)容wrap_content的大小,布局文件沒有問題,那么么問題出在哪呢?
分析一下,可以猜測,只有在item添加到RecyclerView時的中間部分才有可能修改LayoutParams,也就是問題最有可能出現(xiàn)在onCreateViewHolder中的inflate的時候。
-
View.inflate
通常為了簡單,我們經(jīng)常調(diào)用View.inflate來加載布局,而我們出現(xiàn)上面問題的時候也是使用的這個,這個方法定義如下:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) { LayoutInflater factory = LayoutInflater.from(context); return factory.inflate(resource, root); }因為我們是要加到RecyclerView中去,在RecyclerView的布局工作中會調(diào)用addView添加,所以我們這里的root必須傳null,否則就會拋出“不允許有多個parent”的異常。
可以看到,這里其實就是對于LayoutInflater.from(context).inflate(@LayoutRes int resource, @Nullable ViewGroup root)方法的封裝:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }這個方法又是調(diào)用了三個參數(shù)的inflate重載方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); } View view = tryInflatePrecompiled(resource, res, root, attachToRoot); if (view != null) { return view; } XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }因為我們這里傳入的root是null,所以attachToRoot為false,這里調(diào)用了tryInflatePrecompiled方法創(chuàng)建View:
private @Nullable View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root, boolean attachToRoot) { if (!mUseCompiledView) { return null; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)"); // Try to inflate using a precompiled layout. String pkg = res.getResourcePackageName(resource); String layout = res.getResourceEntryName(resource); try { Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader); Method inflater = clazz.getMethod(layout, Context.class, int.class); View view = (View) inflater.invoke(null, mContext, resource); if (view != null && root != null) { // We were able to use the precompiled inflater, but now we need to do some work to // attach the view to the root correctly. XmlResourceParser parser = res.getLayout(resource); try { AttributeSet attrs = Xml.asAttributeSet(parser); advanceToRootNode(parser); ViewGroup.LayoutParams params = root.generateLayoutParams(attrs); if (attachToRoot) { root.addView(view, params); } else { view.setLayoutParams(params); } } finally { parser.close(); } } return view; } catch (Throwable e) { if (DEBUG) { Log.e(TAG, "Failed to use precompiled view", e); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } return null; }可以看到,這里利用反射,找到View類中View(Context.class, int.class)這個構(gòu)造函數(shù)來生成View實例,而View的構(gòu)造函數(shù)中并沒有任何關(guān)于LayoutParams的操作,往下看,if語句判斷中null不為空這個條件不符合,所以不會執(zhí)行if語句內(nèi)的代碼,if語句內(nèi)恰好是把我們xml布局中定義的LayoutParams屬性設(shè)置到View的操作,所以,因為root為null的關(guān)系,我們在布局中設(shè)置的layout_xxx相關(guān)的屬性并沒有被使用,這里只是返回一個基本構(gòu)造函數(shù)返回的View實例。
從這我們也能看出來,LayoutParams是有關(guān)子View在父容器中存在的效果的,所以這里的父容器為空時也不需要處理LayoutParams。但有人會說,子View本身也有需要顯示的內(nèi)容啊,如果不設(shè)置LayoutParams的話那內(nèi)容也顯示不出來了啊。是的,LayoutParams是必須要設(shè)置的,只是..不是現(xiàn)在,接著往下看。
-
RecyclerView的處理
創(chuàng)建了View實例之后,返回onCreateViewHolder方法,這個方法是在RecyclerView的layout流程中的tryGetViewHolderForPositionByDeadline方法中調(diào)用的,它里面有這樣一段代碼:
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; }可以看到,如果View沒有設(shè)置LayoutParams的話會調(diào)用generateDefaultLayoutParams方法設(shè)置,這個方法內(nèi)部是調(diào)用了對應(yīng)LayoutManager的同名方法,以LinearLayoutManager為例:
@Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); }可見,這里默認(rèn)會使用WRAP_CONTENT作為寬高的LayoutParams屬性,這也就能解釋了為什么會出現(xiàn)Item沒有充滿RecyclerView寬(高)的情況。
-
其他
假使我們添加了DividerItemDecoration(ColorDrawable(Color.GRAY)),并且錯誤地使用了View.inflate方法加載,那么會出現(xiàn)這么一種情況:
image-20211206144221245灰色的是我們添加的分割線,卡其色是Item的背景色,也就是內(nèi)容區(qū)域,可以看到,分割線被內(nèi)容擋住了一部分,這是為什么呢?而分割線為什么又是這么高呢?
DividerItemDecoration的getItemOffsets方法如下:
@Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (mDivider == null) { outRect.set(0, 0, 0, 0); return; } if (mOrientation == VERTICAL) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } }mDivider是設(shè)置的Drawable,也就是ColorDrawable,它沒有重寫Drawable的getIntrinsicHeight方法:
public int getIntrinsicHeight() { return -1; }所以這就是高度為什么是1像素的原因。
我們知道在RecyclerView的measure流程中會調(diào)用getItemOffsets方法把分割線的高度作為child的一部分提前預(yù)留出來,這里是-1,所以取最大范圍的值,也就是child本身設(shè)置的大小,這里在onDraw中又通過bottom-mDivider.getIntrinsicHeight()賦值給分割線的top:
for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getDecoratedBoundsWithMargins(child, mBounds); final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); final int top = bottom - mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); }所以會出現(xiàn)內(nèi)容覆蓋分割線一部分的效果。
-
總結(jié)
根據(jù)以上分析,我們得出以下結(jié)論:
對于RecyclerView,在加載Item布局時,我們要使用LayoutInflater.from(context).inflate(R.layout.xxxxx, parent,false)來加載,這個方法可以保證root不為null同時attchToRoot為false,也就能保證既可以應(yīng)用了我們布局中設(shè)置的layout屬性,又不會產(chǎn)生“不允許存在多個parent”的異常。
RecyclerView的Item沒有充滿整個寬度
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- 失效表現(xiàn):在item的xml文件中,設(shè)置了“match_parent”,但是在顯示的時候,展現(xiàn)形式確實“warp_...
- 1. LayoutInflater是做什么的 在Android開發(fā)中LayoutInflater經(jīng)常要用到,F(xiàn)ra...
- 我的CSDN: ListerCi我的簡書: 東方未曦 RecyclerView是項目中使用最為頻繁的控件之一,相關(guān)...
- 學(xué)習(xí)資料: 鴻洋大神為RecyclerView打造通用Adapter讓RecyclerView更加好用 鴻洋大神A...
