Android ScrollVew套RecyclerView發現的問題以及解決辦法
n 前言
一般我們在使用RecyclerView的時候,因為它本身就帶有可以scroll的功能,所以我們並不需要再另外用ScrollView將其置於其中,但有時候因為客戶的設計需求,就有可能會不得不需要將RecyclerView套在ScrollView之中。例如下方顯示的結構,如果我們想要手機呈現可以上下滑動的動作,我們可以直接將UI元件至於ScrollView(藍色部分)中即可,但如果所包含的其中一個元件是RecyclerView(紅色部份)就會遇到一些問題。
n 問題一: ScrollView 與 RecyclerView scroll功能各自作動的問題
如前言所述,如果我們照著常用的方式去進行設計,直覺地將RecyclerView直接放入ScrollView中,就有會產生ScrollView與RecyclerView兩個元件各自滑動的結果,而達不到一開始就想要的整頁可直覺上下滑動的效果。
無法達到效果的Layout如下:
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/activity_main4_ll_image"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_margin="8dp"
android:background="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<TextView
android:id="@+id/activity_main4_tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is the Title"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@+id/activity_main4_ll_image"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/activity_main4_rcv"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/activity_main4_tv_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
</ScrollView>
為了要解決這個問題,就必須先限制RecyclerView的scroll的功能,禁止其可以滑動才能夠達到只有外層的ScrollView能夠滑動的效果。而要實作限制RecyclerView能否scroll功能,我們必須要改寫RecyclerView的LayoutManager的功能。我們建立一個class並繼承特定的LayoutManager,並且改寫canScrollVerticallya這個function。
public class MyLinearLayoutManager extends LinearLayoutManager {
private static final String TAG = MyLinearLayoutManager.class.getSimpleName();
private boolean isScrollEnabled = true;
public MyLinearLayoutManager(Context context, boolean isScrollEnabled) {
super(context);
this.isScrollEnabled = isScrollEnabled;
}
public MyLinearLayoutManager(Context context,int orientation,boolean reverseLayout){
super(context, orientation, reverseLayout);
}
@Override
public boolean canScrollVertically() {
//設定是否禁止滑動
return isScrollEnabled && super.canScrollVertically();
}
}
接著讓RecyclerView直接使用我們自定義的LayoutManager就可以先解決兩個元件各自滑動的問題。
mRecyclerView = findViewById(R.id.activity_main4_rcv);
mRecyclerView.setLayoutManager(new MyLinearLayoutManager(this,false));
n 問題二: RecyclerView顯示不全的問題
上面我們解決了scroll的問題,但是馬上就會發現為什麼RecyclerView卻沒有如我們預期的將資料顯示完全,假設我們預期RecyclerView因該要顯示20 筆資料,但卻沒有全部都顯示出來。網路上對於這方面的問題有多種的解決辦法,在這裏推薦我個人覺得最佳的解決方案。
將ScrollView直接替換成NestedScrollView
如下方Layout:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/activity_main4_ll_image"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_margin="8dp"
android:background="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<TextView
android:id="@+id/activity_main4_tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is the Title"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@+id/activity_main4_ll_image"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/activity_main4_rcv"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/activity_main4_tv_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
</android.support.v4.widget.NestedScrollView>
n 問題三: 畫面一開啟並非直接置頂
使用NestedScrollView則會有另外一個問題,就是畫面開啟後,畫面一開始的呈現並沒有顯示畫面的置頂,如下所示:
解決辦法,在NestedScrollView內的子Layout(通常只有一個)內新增下方屬性:
android:descendantFocusability="blocksDescendants"
該屬性是在定義ViewGroup與其子控件取得焦點的關係,設定blocksDescendants則代表ViewGroup會覆蓋子控件而直接取得焦點。
Layout 如下:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<FrameLayout
android:id="@+id/activity_main4_ll_image"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_margin="8dp"
android:background="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<TextView
android:id="@+id/activity_main4_tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is the Title"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@+id/activity_main4_ll_image"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/activity_main4_rcv"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/activity_main4_tv_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
</android.support.v4.widget.NestedScrollView>
n 問題四: RecyclerView的高度設定wrap_content失效問題
以上如果RecyclerView的版本23.2.0以上,都可以正常運作,但是如果因為某些特定原因(例如專案需求等等)被迫需要使用低於23.2.0以下的版本,就會面臨高度設定wrap_content卻失效的問題,這問題是被確認為一個bug,但google於23.2.0的版本已經修復,所以如果無法使用較新版本的RecyclerView,就必須改寫LayoutManager的onMeasure方法,根據item的內容高度重新量測RecyclerView的高度。
可以根據以下個code來進行RecyclerView量測高度的動作。
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,int heightSpec, int[] measuredDimension) {
View view = recycler.getViewForPosition(position);
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams)view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
n 結論
首先我們透過限制RecyclerView的scroll功能,來避免RecyclerView與ScrollView兩個元件scroll衝突的問題。接著我們將ScrollView換成NestedScrollView來解決RecyclerView在顯示上會遇到的問題。最後,分享了如果是使用RecyclerView 23.2.0以下版的時wrap_content屬性失效的解決方案,但面對warp_content失效的問題,對好的解決方案還是升級RecyclerView版本。以上這些相關的問題,在網路上也可能會有其他方法,而在這邊提供的是本人認為最適當的解決方案供大家參考。
參考資料:
https://www.itread01.com/content/1548244837.html
https://www.jianshu.com/p/9d83e5c76923
https://www.jianshu.com/p/d16ec64181f2
https://www.jianshu.com/p/5dfc90656665
https://www.jianshu.com/p/035fb8baf466
https://codeday.me/bug/20170430/14143.html