mirror of
https://github.com/nextcloud/android.git
synced 2024-11-29 10:49:04 +03:00
Merge pull request #10174 from nextcloud/new-fastscroll-lib
Fast scrolling fixes
This commit is contained in:
commit
db85ebba94
10 changed files with 347 additions and 33 deletions
|
@ -272,7 +272,7 @@ dependencies {
|
||||||
implementation "com.google.android.exoplayer:exoplayer:$exoplayerVersion"
|
implementation "com.google.android.exoplayer:exoplayer:$exoplayerVersion"
|
||||||
implementation "com.google.android.exoplayer:extension-okhttp:$exoplayerVersion"
|
implementation "com.google.android.exoplayer:extension-okhttp:$exoplayerVersion"
|
||||||
|
|
||||||
implementation 'com.simplecityapps:recyclerview-fastscroll:2.0.1'
|
implementation 'me.zhanghai.android.fastscroll:library:1.1.8'
|
||||||
|
|
||||||
// Shimmer animation
|
// Shimmer animation
|
||||||
implementation 'io.github.elye:loaderviewlibrary:3.0.0'
|
implementation 'io.github.elye:loaderviewlibrary:3.0.0'
|
||||||
|
|
61
app/src/main/java/com/nextcloud/utils/view/FastScroll.kt
Normal file
61
app/src/main/java/com/nextcloud/utils/view/FastScroll.kt
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Android Library is available under MIT license
|
||||||
|
*
|
||||||
|
* @author Álvaro Brey Vilas
|
||||||
|
* Copyright (C) 2022 Álvaro Brey Vilas
|
||||||
|
* Copyright (C) 2022 Nextcloud GmbH
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.utils.view
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import me.zhanghai.android.fastscroll.FastScroller
|
||||||
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
|
|
||||||
|
object FastScroll {
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun applyFastScroll(recyclerView: RecyclerView, viewHelper: FastScroller.ViewHelper? = null) {
|
||||||
|
val builder = FastScrollerBuilder(recyclerView).useMd2Style()
|
||||||
|
if (viewHelper != null) {
|
||||||
|
builder.setViewHelper(viewHelper)
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun fixAppBarForFastScroll(appBarLayout: AppBarLayout, content: ViewGroup) {
|
||||||
|
val contentLayoutInitialPaddingBottom = content.paddingBottom
|
||||||
|
appBarLayout.addOnOffsetChangedListener(
|
||||||
|
AppBarLayout.OnOffsetChangedListener { _, offset ->
|
||||||
|
content.setPadding(
|
||||||
|
content.paddingLeft,
|
||||||
|
content.paddingTop,
|
||||||
|
content.paddingRight,
|
||||||
|
contentLayoutInitialPaddingBottom + appBarLayout.totalScrollRange + offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,14 +25,13 @@ import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends RecyclerView to show a custom view if no data is available Inspired by http://alexzh.com/tutorials/how-to-setemptyview-to-recyclerview
|
* Extends RecyclerView to show a custom view if no data is available Inspired by http://alexzh.com/tutorials/how-to-setemptyview-to-recyclerview
|
||||||
*/
|
*/
|
||||||
public class EmptyRecyclerView extends FastScrollRecyclerView {
|
public class EmptyRecyclerView extends RecyclerView {
|
||||||
private View mEmptyView;
|
private View mEmptyView;
|
||||||
private boolean hasFooter = false;
|
private boolean hasFooter = false;
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ import com.nextcloud.client.network.ConnectivityService;
|
||||||
import com.nextcloud.client.preferences.AppPreferences;
|
import com.nextcloud.client.preferences.AppPreferences;
|
||||||
import com.nextcloud.client.utils.IntentUtil;
|
import com.nextcloud.client.utils.IntentUtil;
|
||||||
import com.nextcloud.java.util.Optional;
|
import com.nextcloud.java.util.Optional;
|
||||||
|
import com.nextcloud.utils.view.FastScroll;
|
||||||
import com.owncloud.android.MainApp;
|
import com.owncloud.android.MainApp;
|
||||||
import com.owncloud.android.R;
|
import com.owncloud.android.R;
|
||||||
import com.owncloud.android.databinding.FilesBinding;
|
import com.owncloud.android.databinding.FilesBinding;
|
||||||
|
@ -137,7 +138,6 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
||||||
import androidx.core.view.MenuItemCompat;
|
import androidx.core.view.MenuItemCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
@ -266,6 +266,9 @@ public class FileDisplayActivity extends FileActivity
|
||||||
mSwitchAccountButton.setOnClickListener(v -> showManageAccountsDialog());
|
mSwitchAccountButton.setOnClickListener(v -> showManageAccountsDialog());
|
||||||
|
|
||||||
|
|
||||||
|
FastScroll.fixAppBarForFastScroll(binding.appbar.appbar, binding.rootLayout);
|
||||||
|
|
||||||
|
|
||||||
// Init Fragment without UI to retain AsyncTask across configuration changes
|
// Init Fragment without UI to retain AsyncTask across configuration changes
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
TaskRetainerFragment taskRetainerFragment =
|
TaskRetainerFragment taskRetainerFragment =
|
||||||
|
|
|
@ -45,7 +45,7 @@ import com.owncloud.android.utils.FileSortOrder
|
||||||
import com.owncloud.android.utils.FileStorageUtils
|
import com.owncloud.android.utils.FileStorageUtils
|
||||||
import com.owncloud.android.utils.theme.ThemeColorUtils
|
import com.owncloud.android.utils.theme.ThemeColorUtils
|
||||||
import com.owncloud.android.utils.theme.ThemeDrawableUtils
|
import com.owncloud.android.utils.theme.ThemeDrawableUtils
|
||||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView.SectionedAdapter
|
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ class GalleryAdapter(
|
||||||
transferServiceGetter: ComponentsGetter,
|
transferServiceGetter: ComponentsGetter,
|
||||||
themeColorUtils: ThemeColorUtils,
|
themeColorUtils: ThemeColorUtils,
|
||||||
themeDrawableUtils: ThemeDrawableUtils
|
themeDrawableUtils: ThemeDrawableUtils
|
||||||
) : SectionedRecyclerViewAdapter<SectionedViewHolder>(), CommonOCFileListAdapterInterface, SectionedAdapter {
|
) : SectionedRecyclerViewAdapter<SectionedViewHolder>(), CommonOCFileListAdapterInterface, PopupTextProvider {
|
||||||
private var files: List<GalleryItems> = mutableListOf()
|
var files: List<GalleryItems> = mutableListOf()
|
||||||
private val ocFileListDelegate: OCFileListDelegate
|
private val ocFileListDelegate: OCFileListDelegate
|
||||||
private var storageManager: FileDataStorageManager
|
private var storageManager: FileDataStorageManager
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ class GalleryAdapter(
|
||||||
return files.size
|
return files.size
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSectionName(position: Int): String {
|
override fun getPopupText(position: Int): String {
|
||||||
return DisplayUtils.getDateByPattern(
|
return DisplayUtils.getDateByPattern(
|
||||||
files[getRelativePosition(position).section()].date,
|
files[getRelativePosition(position).section()].date,
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -74,7 +74,6 @@ import com.owncloud.android.utils.theme.CapabilityUtils;
|
||||||
import com.owncloud.android.utils.theme.ThemeAvatarUtils;
|
import com.owncloud.android.utils.theme.ThemeAvatarUtils;
|
||||||
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
||||||
import com.owncloud.android.utils.theme.ThemeDrawableUtils;
|
import com.owncloud.android.utils.theme.ThemeDrawableUtils;
|
||||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
@ -90,14 +89,14 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
import me.zhanghai.android.fastscroll.PopupTextProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This Adapter populates a RecyclerView with all files and folders in a Nextcloud instance.
|
* This Adapter populates a RecyclerView with all files and folders in a Nextcloud instance.
|
||||||
*/
|
*/
|
||||||
public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
||||||
implements DisplayUtils.AvatarGenerationListener,
|
implements DisplayUtils.AvatarGenerationListener,
|
||||||
CommonOCFileListAdapterInterface,
|
CommonOCFileListAdapterInterface, PopupTextProvider {
|
||||||
FastScrollRecyclerView.SectionedAdapter {
|
|
||||||
|
|
||||||
private static final int showFilenameColumnThreshold = 4;
|
private static final int showFilenameColumnThreshold = 4;
|
||||||
private final String userId;
|
private final String userId;
|
||||||
|
@ -909,7 +908,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getSectionName(int position) {
|
public String getPopupText(int position) {
|
||||||
OCFile file = getItem(position);
|
OCFile file = getItem(position);
|
||||||
|
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.nextcloud.utils.view.FastScroll;
|
||||||
import com.owncloud.android.R;
|
import com.owncloud.android.R;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
|
@ -35,6 +36,7 @@ import com.owncloud.android.ui.adapter.CommonOCFileListAdapterInterface;
|
||||||
import com.owncloud.android.ui.adapter.GalleryAdapter;
|
import com.owncloud.android.ui.adapter.GalleryAdapter;
|
||||||
import com.owncloud.android.ui.asynctasks.GallerySearchTask;
|
import com.owncloud.android.ui.asynctasks.GallerySearchTask;
|
||||||
import com.owncloud.android.ui.events.ChangeMenuEvent;
|
import com.owncloud.android.ui.events.ChangeMenuEvent;
|
||||||
|
import com.owncloud.android.ui.fragment.util.GalleryFastScrollViewHelper;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
@ -109,26 +111,14 @@ public class GalleryFragment extends OCFileListFragment {
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils);
|
themeDrawableUtils);
|
||||||
|
|
||||||
// val spacing = resources.getDimensionPixelSize(R.dimen.media_grid_spacing)
|
|
||||||
// binding.list.addItemDecoration(MediaGridItemDecoration(spacing))
|
|
||||||
setRecyclerViewAdapter(mAdapter);
|
setRecyclerViewAdapter(mAdapter);
|
||||||
|
|
||||||
|
|
||||||
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), getColumnsCount());
|
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), getColumnsCount());
|
||||||
// ((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
|
||||||
// @Override
|
|
||||||
// public int getSpanSize(int position) {
|
|
||||||
// if (position == getAdapter().getItemCount() - 1 ||
|
|
||||||
// position == 0 && getAdapter().shouldShowHeader()) {
|
|
||||||
// return ((GridLayoutManager) layoutManager).getSpanCount();
|
|
||||||
// } else {
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
mAdapter.setLayoutManager(layoutManager);
|
mAdapter.setLayoutManager(layoutManager);
|
||||||
getRecyclerView().setLayoutManager(layoutManager);
|
getRecyclerView().setLayoutManager(layoutManager);
|
||||||
|
|
||||||
|
FastScroll.applyFastScroll(getRecyclerView(), new GalleryFastScrollViewHelper(getRecyclerView(), mAdapter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -59,6 +59,7 @@ import com.nextcloud.client.network.ClientFactory;
|
||||||
import com.nextcloud.client.preferences.AppPreferences;
|
import com.nextcloud.client.preferences.AppPreferences;
|
||||||
import com.nextcloud.client.utils.Throttler;
|
import com.nextcloud.client.utils.Throttler;
|
||||||
import com.nextcloud.common.NextcloudClient;
|
import com.nextcloud.common.NextcloudClient;
|
||||||
|
import com.nextcloud.utils.view.FastScroll;
|
||||||
import com.owncloud.android.MainApp;
|
import com.owncloud.android.MainApp;
|
||||||
import com.owncloud.android.R;
|
import com.owncloud.android.R;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
|
@ -429,6 +430,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
);
|
);
|
||||||
|
|
||||||
setRecyclerViewAdapter(mAdapter);
|
setRecyclerViewAdapter(mAdapter);
|
||||||
|
|
||||||
|
FastScroll.applyFastScroll(getRecyclerView());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void prepareCurrentSearch(SearchEvent event) {
|
protected void prepareCurrentSearch(SearchEvent event) {
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Android Library is available under MIT license
|
||||||
|
*
|
||||||
|
* @author Álvaro Brey Vilas
|
||||||
|
* Copyright (C) 2022 Álvaro Brey Vilas
|
||||||
|
* Copyright (C) 2022 Nextcloud GmbH
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.owncloud.android.ui.fragment.util
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener
|
||||||
|
import com.afollestad.sectionedrecyclerview.ItemCoord
|
||||||
|
import com.owncloud.android.datamodel.GalleryItems
|
||||||
|
import com.owncloud.android.ui.adapter.GalleryAdapter
|
||||||
|
import me.zhanghai.android.fastscroll.FastScroller
|
||||||
|
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||||
|
import me.zhanghai.android.fastscroll.Predicate
|
||||||
|
import kotlin.math.ceil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom ViewHelper to get fast scroll working on gallery, which has a gridview and variable height (due to headers)
|
||||||
|
*
|
||||||
|
* Copied from me.zhanghai.android.fastscroll.RecyclerViewHelper and heavily modified for gallery structure
|
||||||
|
*/
|
||||||
|
class GalleryFastScrollViewHelper(
|
||||||
|
private val mView: RecyclerView,
|
||||||
|
private val mPopupTextProvider: PopupTextProvider?
|
||||||
|
) : FastScroller.ViewHelper {
|
||||||
|
// used to calculate paddings
|
||||||
|
private val mTempRect = Rect()
|
||||||
|
|
||||||
|
private val layoutManager by lazy { mView.layoutManager as GridLayoutManager }
|
||||||
|
|
||||||
|
// header is always 1st in the adapter
|
||||||
|
private val headerHeight by lazy { getItemHeight(0) }
|
||||||
|
// the 2nd element is always an item
|
||||||
|
private val rowHeight by lazy { getItemHeight(1) }
|
||||||
|
|
||||||
|
private val columnCount by lazy { layoutManager.spanCount }
|
||||||
|
|
||||||
|
private fun getItemHeight(position: Int): Int {
|
||||||
|
if (mView.childCount <= position) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val itemView = mView.getChildAt(position)
|
||||||
|
mView.getDecoratedBoundsWithMargins(itemView, mTempRect)
|
||||||
|
return mTempRect.height()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addOnPreDrawListener(onPreDraw: Runnable) {
|
||||||
|
mView.addItemDecoration(object : ItemDecoration() {
|
||||||
|
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
|
onPreDraw.run()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addOnScrollChangedListener(onScrollChanged: Runnable) {
|
||||||
|
mView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
onScrollChanged.run()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addOnTouchEventListener(onTouchEvent: Predicate<MotionEvent?>) {
|
||||||
|
mView.addOnItemTouchListener(object : SimpleOnItemTouchListener() {
|
||||||
|
override fun onInterceptTouchEvent(recyclerView: RecyclerView, event: MotionEvent): Boolean {
|
||||||
|
return onTouchEvent.test(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(recyclerView: RecyclerView, event: MotionEvent) {
|
||||||
|
onTouchEvent.test(event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getScrollRange(): Int {
|
||||||
|
val headerCount = getHeaderCount()
|
||||||
|
val rowCount = getRowCount()
|
||||||
|
|
||||||
|
if (headerCount == 0 || rowCount == 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val totalHeaderHeight = headerCount * headerHeight
|
||||||
|
val totalRowHeight = rowCount * rowHeight
|
||||||
|
return mView.paddingTop + totalHeaderHeight + totalRowHeight + mView.paddingBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHeaderCount(): Int {
|
||||||
|
val adapter = mView.adapter as GalleryAdapter
|
||||||
|
return adapter.sectionCount
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRowCount(): Int {
|
||||||
|
val adapter = mView.adapter as GalleryAdapter
|
||||||
|
if (adapter.sectionCount == 0) return 0
|
||||||
|
// in each section, the final row may contain less than the max of items
|
||||||
|
return adapter.files.sumOf { itemCountToRowCount(it.files.size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates current absolute offset depending on view state (first visible element)
|
||||||
|
*/
|
||||||
|
override fun getScrollOffset(): Int {
|
||||||
|
val firstItemPosition = getFirstItemAdapterPosition()
|
||||||
|
if (firstItemPosition == RecyclerView.NO_POSITION) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter = mView.adapter as GalleryAdapter
|
||||||
|
val itemCoord: ItemCoord = adapter.getRelativePosition(firstItemPosition)
|
||||||
|
val isHeader = itemCoord.relativePos() == -1
|
||||||
|
|
||||||
|
val seenRowsInPreviousSections = adapter.files
|
||||||
|
.subList(0, itemCoord.section())
|
||||||
|
.sumOf { itemCountToRowCount(it.files.size) }
|
||||||
|
val seenRowsInThisSection = if (isHeader) 0 else itemCountToRowCount(itemCoord.relativePos())
|
||||||
|
val totalSeenRows = seenRowsInPreviousSections + seenRowsInThisSection
|
||||||
|
|
||||||
|
val seenHeaders = when {
|
||||||
|
isHeader -> itemCoord.section() // don't count the current section header
|
||||||
|
else -> itemCoord.section() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val firstItemTop = getFirstItemOffset()
|
||||||
|
|
||||||
|
val totalRowOffset = totalSeenRows * rowHeight
|
||||||
|
val totalHeaderOffset = seenHeaders * headerHeight
|
||||||
|
return mView.paddingTop + totalHeaderOffset + totalRowOffset - firstItemTop
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls to an absolute offset
|
||||||
|
*/
|
||||||
|
override fun scrollTo(offset: Int) {
|
||||||
|
mView.stopScroll()
|
||||||
|
val offsetTmp = offset - mView.paddingTop
|
||||||
|
val (position, remainingOffset) = findPositionForOffset(offsetTmp)
|
||||||
|
scrollToPositionWithOffset(position, -remainingOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an absolute offset, returns the closest position to that offset (without going over it),
|
||||||
|
* and the remaining offset
|
||||||
|
*/
|
||||||
|
private fun findPositionForOffset(offset: Int): Pair<Int, Int> {
|
||||||
|
val adapter = mView.adapter as GalleryAdapter
|
||||||
|
|
||||||
|
// find section
|
||||||
|
val sectionStartOffsets = getSectionStartOffsets(adapter.files)
|
||||||
|
val previousSections = sectionStartOffsets.filter { it <= offset }
|
||||||
|
|
||||||
|
val section = previousSections.size - 1
|
||||||
|
val sectionStartOffset = previousSections.last()
|
||||||
|
|
||||||
|
// now calculate where to scroll within the section
|
||||||
|
var remainingOffset = offset - sectionStartOffset
|
||||||
|
val positionWithinSection: Int
|
||||||
|
if (remainingOffset <= headerHeight) {
|
||||||
|
// header position
|
||||||
|
positionWithinSection = -1
|
||||||
|
} else {
|
||||||
|
// row position
|
||||||
|
remainingOffset -= headerHeight
|
||||||
|
val rowCount = remainingOffset / rowHeight
|
||||||
|
if (rowCount > 0) {
|
||||||
|
val rowStartIndex = rowCount * columnCount
|
||||||
|
positionWithinSection = rowStartIndex
|
||||||
|
|
||||||
|
remainingOffset -= rowCount * rowHeight
|
||||||
|
} else {
|
||||||
|
positionWithinSection = 0 // first item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val absolutePosition = adapter.getAbsolutePosition(section, positionWithinSection)
|
||||||
|
return Pair(absolutePosition, remainingOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of the offset heights at which the section corresponding to that index starts
|
||||||
|
*/
|
||||||
|
private fun getSectionStartOffsets(files: List<GalleryItems>): List<Int> {
|
||||||
|
val sectionHeights =
|
||||||
|
files.map { headerHeight + itemCountToRowCount(it.files.size) * rowHeight }
|
||||||
|
val sectionStartOffsets = sectionHeights.indices.map { i ->
|
||||||
|
when (i) {
|
||||||
|
0 -> 0
|
||||||
|
else -> sectionHeights.subList(0, i).sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sectionStartOffsets
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun itemCountToRowCount(itemsCount: Int): Int {
|
||||||
|
return ceil(itemsCount.toDouble() / columnCount).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPopupText(): String? {
|
||||||
|
var popupTextProvider: PopupTextProvider? = mPopupTextProvider
|
||||||
|
if (popupTextProvider == null) {
|
||||||
|
val adapter = mView.adapter
|
||||||
|
if (adapter is PopupTextProvider) {
|
||||||
|
popupTextProvider = adapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (popupTextProvider == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val position = getFirstItemAdapterPosition()
|
||||||
|
return if (position == RecyclerView.NO_POSITION) {
|
||||||
|
null
|
||||||
|
} else popupTextProvider.getPopupText(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFirstItemAdapterPosition(): Int {
|
||||||
|
if (mView.childCount == 0) {
|
||||||
|
return RecyclerView.NO_POSITION
|
||||||
|
}
|
||||||
|
val itemView = mView.getChildAt(0)
|
||||||
|
return layoutManager.getPosition(itemView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFirstItemOffset(): Int {
|
||||||
|
if (mView.childCount == 0) {
|
||||||
|
return RecyclerView.NO_POSITION
|
||||||
|
}
|
||||||
|
val itemView = mView.getChildAt(0)
|
||||||
|
mView.getDecoratedBoundsWithMargins(itemView, mTempRect)
|
||||||
|
return mTempRect.top
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollToPositionWithOffset(position: Int, offset: Int) {
|
||||||
|
var offsetTmp = offset
|
||||||
|
// LinearLayoutManager actually takes offset from paddingTop instead of top of RecyclerView.
|
||||||
|
offsetTmp -= mView.paddingTop
|
||||||
|
layoutManager.scrollToPositionWithOffset(position, offsetTmp)
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,12 +33,7 @@
|
||||||
<com.owncloud.android.ui.EmptyRecyclerView
|
<com.owncloud.android.ui.EmptyRecyclerView
|
||||||
android:id="@+id/list_root"
|
android:id="@+id/list_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent" />
|
||||||
app:fastScrollPopupBgColor="@color/color_accent"
|
|
||||||
app:fastScrollPopupTextColor="@color/login_text_color"
|
|
||||||
app:fastScrollThumbColor="@color/color_accent"
|
|
||||||
app:fastScrollAutoHide="true"
|
|
||||||
app:fastScrollAutoHideDelay="1500" />
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
|
Loading…
Reference in a new issue