diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/AutofitGridLayoutManager.kt b/app/src/main/java/com/owncloud/android/ui/fragment/AutofitGridLayoutManager.kt
new file mode 100644
index 000000000000..653807aa8c3a
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/AutofitGridLayoutManager.kt
@@ -0,0 +1,55 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+package com.owncloud.android.ui.fragment
+
+import android.content.Context
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+/**
+ * A [GridLayoutManager] that works out its own column count from the width it is actually given.
+ *
+ * The width is read during layout rather than from the display metrics, because the metrics are
+ * not a reliable stand in: they describe the display rather than the space this list occupies,
+ * they are not yet meaningful while the view is being created, and they can still describe the
+ * previous orientation while a rotation is being delivered.
+ *
+ * @param columnWidthProvider target width of a single column in pixels, read on every layout so
+ * that a changed preference is picked up without recreating the layout manager.
+ */
+class AutofitGridLayoutManager(context: Context, private val columnWidthProvider: () -> Int) :
+ GridLayoutManager(context, 1) {
+
+ private var lastWidth = 0
+ private var lastColumnWidth = 0
+
+ override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
+ updateSpanCount()
+ super.onLayoutChildren(recycler, state)
+ }
+
+ private fun updateSpanCount() {
+ val columnWidth = columnWidthProvider()
+ if (width <= 0 || columnWidth <= 0) {
+ return
+ }
+
+ if (width == lastWidth && columnWidth == lastColumnWidth) {
+ return
+ }
+
+ lastWidth = width
+ lastColumnWidth = columnWidth
+ spanCount = max(MIN_COLUMN_COUNT, (width.toFloat() / columnWidth).roundToInt())
+ }
+
+ companion object {
+ private const val MIN_COLUMN_COUNT = 2
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt
index 45f9063369b3..49f54eb736b8 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt
@@ -86,8 +86,6 @@ open class ExtendedListFragment :
SearchView.OnQueryTextListener,
SearchView.OnCloseListener,
Injectable {
- private var maxColumnSize = 5
-
@Inject
lateinit var preferences: AppPreferences
@@ -146,7 +144,7 @@ open class ExtendedListFragment :
open fun switchToGridView() {
if (!isGridEnabled) {
- recyclerView?.layoutManager = GridLayoutManager(context, columnsCount)
+ recyclerView?.layoutManager = AutofitGridLayoutManager(requireContext()) { targetColumnWidth }
}
}
@@ -375,9 +373,8 @@ open class ExtendedListFragment :
mScale = gridLayoutManager.spanCount.toFloat()
}
mScale *= 2f - scaleFactor
- mScale = max(MIN_COLUMN_SIZE, min(mScale, maxColumnSize.toFloat()))
- val scaleInt = mScale.roundToInt()
- gridLayoutManager.setSpanCount(scaleInt)
+ mScale = max(MIN_COLUMN_SIZE, min(mScale, MAX_COLUMN_SCALE))
+ gridLayoutManager.requestLayout()
mRecyclerView?.adapter?.notifyDataSetChanged()
}
}
@@ -437,14 +434,24 @@ open class ExtendedListFragment :
preferences.setGridColumns(mScale)
}
- open val columnsCount: Int
+ /**
+ * Target width of one grid column in pixels.
+ *
+ * [mScale] is a zoom level: asking for more columns than the default means asking for
+ * smaller cells. The column count itself is worked out by [AutofitGridLayoutManager] from
+ * the width the list is actually given.
+ */
+ internal val targetColumnWidth: Int
get() {
- if (mScale == -1f) {
- return AppPreferencesImpl.DEFAULT_GRID_COLUMN.roundToInt()
- }
- return mScale.roundToInt()
+ val zoom = (if (mScale > 0f) mScale else AppPreferencesImpl.DEFAULT_GRID_COLUMN) /
+ AppPreferencesImpl.DEFAULT_GRID_COLUMN
+ return (resources.getDimension(R.dimen.grid_item_default_width) / zoom).roundToInt()
}
+ open val columnsCount: Int
+ get() = (recyclerView?.layoutManager as? GridLayoutManager)?.spanCount
+ ?: AppPreferencesImpl.DEFAULT_GRID_COLUMN.roundToInt()
+
/*
* Restore index and position
*/
@@ -778,15 +785,7 @@ open class ExtendedListFragment :
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
- if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- maxColumnSize = 10
- } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
- maxColumnSize = 5
- }
-
- if (isGridEnabled && columnsCount > maxColumnSize) {
- (recyclerView?.layoutManager as GridLayoutManager).spanCount = maxColumnSize
- }
+ // The column count follows the width the list is given, so nothing to do here.
}
protected fun setLayoutSwitchButton() {
@@ -821,5 +820,10 @@ open class ExtendedListFragment :
private const val KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE"
private const val KEY_IS_GRID_VISIBLE = "IS_GRID_VISIBLE"
private const val MIN_COLUMN_SIZE: Float = 2.0f
+
+ /**
+ * Highest number of columns the pinch gesture can select on a reference-width screen.
+ */
+ private const val MAX_COLUMN_SCALE: Float = 5.0f
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileListLayoutManager.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileListLayoutManager.kt
index 1f4375466bc7..39b7f146ee23 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileListLayoutManager.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileListLayoutManager.kt
@@ -87,7 +87,7 @@ class FileListLayoutManager(private val fragment: OCFileListFragment, private va
val layoutManager: RecyclerView.LayoutManager?
if (grid) {
- layoutManager = GridLayoutManager(context, fragment.columnsCount)
+ layoutManager = AutofitGridLayoutManager(context) { fragment.targetColumnWidth }
layoutManager.spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int = if (position == fragment.adapter.itemCount - 1 ||
(position == 0 && fragment.adapter.shouldShowHeader())
diff --git a/app/src/main/res/values/dims.xml b/app/src/main/res/values/dims.xml
index ffc32ec8b7a8..fc54132e8982 100644
--- a/app/src/main/res/values/dims.xml
+++ b/app/src/main/res/values/dims.xml
@@ -46,6 +46,9 @@
12sp
20dp
10dp
+
+ 140dp
2dp
22sp
2dp