実現したいこと
- スクロールビュー内のRecyclerViewがスクロールなしで全アイテムを3列でにグリッド上に表示できること
(RecyclerView)にこだわりはないので別のアプローチでも大丈夫です!
- リスト可変アイテムの高さを持つRecyclerViewで、正確に全体の高さを計算する方法はありますか?
- リストあるいは、他にアプローチとして考えられる解決策があれば教えてください。
前提
現在、AndroidのRecyclerViewを使ってスクロール無しの横3列で
グリッド上にアイテムを表示しようと思っています。
表示する各アイテムはアイテム内の要素をGONEにしたり、VISIBLEにしているので高さが異なるため、
行ごとに一番高いアイテムの高さに合わせてRecyclerViewの高さを動的に計算しようとしています。
しかし、RecyclerViewの高さを正確に計算できず、特定の行が見切れる、
または高さが合わない問題が発生しています。
発生している問題
- リストスクロールをしないようにしたRecyclerViewの高さが正確に計算されず、一部の行が見切れる。
- 1行ごとのアイテムの高さが異なるため、行ごとの最大高さを正しく反映させたいが、うまくいかない。
該当のソースコード
- レイアウトファイル (ScrollView + RecyclerView)
xml
1<ScrollView 2 android:id="@+id/main_scroll_view" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent"> 5 6 <LinearLayout 7 android:layout_width="match_parent" 8 android:layout_height="wrap_content" 9 android:orientation="vertical"> 10 11 <!-- グリッド表示のRecyclerView --> 12 <androidx.recyclerview.widget.RecyclerView 13 android:id="@+id/articleRecyclerView" 14 android:layout_width="match_parent" 15 android:layout_height="wrap_content" 16 android:visibility="visible" 17 android:nestedScrollingEnabled="false" /> 18 <!-- 以下にその他項目(省略) --> 19 </LinearLayout> 20</ScrollView> 21
- アイテムのレイアウト (item_article.xml)
xml
1<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="wrap_content" 3 android:layout_height="wrap_content" 4 android:paddingBottom="8dp" 5 android:paddingLeft="8dp" 6 android:paddingRight="8dp"> 7 8 <!-- 固定表示される記事のサムネイル --> 9 <ImageView 10 android:id="@+id/articleImage" 11 android:layout_width="104dp" 12 android:layout_height="104dp" 13 android:layout_alignParentTop="true" 14 android:layout_centerHorizontal="true" /> 15 16 <!-- Newアイコン(表示/非表示) --> 17 <TextView 18 android:id="@+id/articleNewIcon" 19 android:layout_width="wrap_content" 20 android:layout_height="wrap_content" 21 android:layout_below="@id/articleImage" 22 android:layout_centerHorizontal="true" 23 android:layout_marginTop="4dp" 24 android:text="NEW!" 25 android:visibility="gone" /> 26 27 <!-- 投稿日(nullの場合非表示) --> 28 <TextView 29 android:id="@+id/publishDate" 30 android:layout_width="wrap_content" 31 android:layout_height="wrap_content" 32 android:layout_below="@id/articleNewIcon" 33 android:layout_centerHorizontal="true" 34 android:visibility="gone" /> 35 36 <!-- 常に表示されるタイトル --> 37 <TextView 38 android:id="@+id/articleTitle" 39 android:layout_width="wrap_content" 40 android:layout_height="wrap_content" 41 android:layout_below="@id/publishDate" 42 android:layout_centerHorizontal="true" 43 android:text="Article Title" /> 44 45 <!-- ブログ名(nullの場合非表示) --> 46 <TextView 47 android:id="@+id/blogName" 48 android:layout_width="wrap_content" 49 android:layout_height="wrap_content" 50 android:layout_below="@id/articleTitle" 51 android:layout_centerHorizontal="true" 52 android:visibility="gone" /> 53</RelativeLayout> 54
- アダプタ (ArticleListAdapter.java)
java
1public class ArticleListAdapter extends RecyclerView.Adapter<ArticleListAdapter.ArticleViewHolder> { 2 private List<Article> dataList; 3 4 public ArticleListAdapter(List<Article> dataList) { 5 this.dataList = dataList; 6 } 7 8 @NonNull 9 @Override 10 public ArticleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 11 View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_article, parent, false); 12 return new ArticleViewHolder(itemView); 13 } 14 15 @Override 16 public void onBindViewHolder(@NonNull ArticleViewHolder holder, int position) { 17 Article data = dataList.get(position); 18 // サムネイル 19 holder.articleImage.setImageResource(data.getImageResourceId()); 20 // タイトル(常に表示) 21 holder.titleView.setText(data.getTitle()); 22 // Newアイコン 23 holder.newIcon.setVisibility(data.isNew() ? View.VISIBLE : View.GONE); 24 // 投稿日(nullの場合非表示) 25 if (data.getPublishDate() != null) { 26 holder.publishDate.setText(data.getPublishDate()); 27 holder.publishDate.setVisibility(View.VISIBLE); 28 } else { 29 holder.publishDate.setVisibility(View.GONE); 30 } 31 // ブログ名(nullの場合非表示) 32 if (data.getBlogName() != null) { 33 holder.blogName.setText(data.getBlogName()); 34 holder.blogName.setVisibility(View.VISIBLE); 35 } else { 36 holder.blogName.setVisibility(View.GONE); 37 } 38 } 39 40 @Override 41 public int getItemCount() { 42 return dataList.size(); 43 } 44 45 public static class ArticleViewHolder extends RecyclerView.ViewHolder { 46 TextView titleView, newIcon, publishDate, blogName; 47 ImageView articleImage; 48 public ArticleViewHolder(@NonNull View itemView) { 49 super(itemView); 50 titleView = itemView.findViewById(R.id.articleTitle); 51 newIcon = itemView.findViewById(R.id.articleNewIcon); 52 publishDate = itemView.findViewById(R.id.publishDate); 53 blogName = itemView.findViewById(R.id.blogName); 54 articleImage = itemView.findViewById(R.id.articleImage); 55 } 56 } 57} 58
- フラグメント (MyFragment.java)
java
1public class MyFragment extends Fragment { 2 3 @Override 4 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 5 View view = inflater.inflate(R.layout.fragment_layout, container, false); 6 7 RecyclerView recyclerView = view.findViewById(R.id.articleRecyclerView); 8 recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 9 recyclerView.setAdapter(new ArticleListAdapter(getDummyData())); 10 11 // 高さを動的に設定 12 adjustRecyclerViewHeight(recyclerView, 3); 13 return view; 14 } 15 16 // RecyclerViewの高さを動的に調整する 17 private void adjustRecyclerViewHeight(RecyclerView recyclerView, int columns) { 18 RecyclerView.Adapter adapter = recyclerView.getAdapter(); 19 if (adapter == null) { 20 return; 21 } 22 23 int totalHeight = 0; // 最終的な RecyclerView の高さ 24 int itemCount = adapter.getItemCount(); // アイテム数 25 26 // 1行の高さを記録するための変数 27 List<Integer> rowHeights = new ArrayList<>(); // 各行の高さを格納するリスト 28 29 // 各行の高さを計算 30 for (int rowIndex = 0; rowIndex < Math.ceil((double) itemCount / columns); rowIndex++) { 31 int maxHeightInRow = 0; 32 33 // 各行内の各アイテムの高さを計測し、最大値を取る 34 for (int columnIndex = 0; columnIndex < columns; columnIndex++) { 35 int itemIndex = rowIndex * columns + columnIndex; 36 37 // アイテムが存在する場合のみ計測 38 if (itemIndex < itemCount) { 39 // アイテムの ViewHolder を取得 40 RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(itemIndex)); 41 View itemView = holder.itemView; 42 43 // アイテムの高さを測定 44 itemView.measure( 45 View.MeasureSpec.makeMeasureSpec(recyclerView.getWidth() / columns, View.MeasureSpec.EXACTLY), 46 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) 47 ); 48 49 // アイテムの高さを取得 50 int itemHeight = itemView.getMeasuredHeight(); 51 52 // アイテムのマージンを考慮 53 ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams(); 54 int itemVerticalMargin = layoutParams.topMargin + layoutParams.bottomMargin; 55 itemHeight += itemVerticalMargin; 56 57 // この行の最大高さを更新 58 if (itemHeight > maxHeightInRow) { 59 maxHeightInRow = itemHeight; 60 } 61 } 62 } 63 64 // 行の最大高さをリストに追加 65 rowHeights.add(maxHeightInRow); 66 } 67 68 // 各行の高さを合計して最終的な RecyclerView の高さを計算 69 for (int rowHeight : rowHeights) { 70 totalHeight += rowHeight; 71 } 72 73 // RecyclerView のパディングを考慮 74 totalHeight += recyclerView.getPaddingTop() + recyclerView.getPaddingBottom(); 75 76 // 高さを設定 77 ViewGroup.LayoutParams params = recyclerView.getLayoutParams(); 78 params.height = totalHeight; 79 recyclerView.setLayoutParams(params); 80 } 81}
試したこと
- GridViewを使用したGrid表示(各行のアイテムの高さが揃わず断念しRecyclerViewに移行)
- ViewHolderを使ってアイテムの高さを取得し、行ごとの最大高さを計算
- ViewTreeObserver.OnGlobalLayoutListener でRecyclerViewが描画された後に高さを計算
- スクロールを無効にして全アイテムが表示されるように設定(nestedScrollingEnabled=false)
補足情報(FW/ツールのバージョンなど)
- Java
- minSdkVersion 21
- targetSdkVersion 33
- Android Studio
回答1件
あなたの回答
tips
プレビュー