Questo team di scrittura ha dedicato lunghe ore alla ricerca per rispondere alla tua domanda, condividiamo la soluzione con te e il nostro obiettivo è quello di essere di grande supporto per te.
Soluzione:
Ho messo insieme velocemente una semplice soluzione basata su un CoordinatorLayout.Behavior
. Non è perfetta, si può forse dedicare un po' di tempo alla sua messa a punto, ma non è male. In ogni caso, il risultato dovrebbe essere simile a questo:
Una piccola nota a margine prima di iniziare con la risposta: Raccomando vivamente di usare l'opzione NestedScrollView
della libreria di supporto invece di un normale ScrollView
. Sono identici in tutto e per tutto, ma il NestedScrollView
implementa il comportamento corretto dello scorrimento annidato sui livelli API inferiori.
Comunque, iniziamo con la mia risposta: La soluzione che ho proposto funzionerebbe con qualsiasi contenitore scorrevole, sia esso un contenitore ScrollView
, ListView
o RecyclerView
e non c'è bisogno di sottoclassi di alcun elemento Views
per implementarlo.
Per prima cosa è necessario aggiungere la Design Support Library di Google al progetto, se non la si sta già utilizzando:
compile 'com.android.support:design:25.0.1'
Ricordate che se non state puntando al livello API 25 (cosa che dovreste fare, tra l'altro), allora dovete includere la versione più recente per il vostro livello API (ad esempio, compile 'com.android.support:design:24.2.0'
).compile 'com.android.support:design:24.2.0'
per il livello API 24).
Qualunque contenitore scrollabile si stia usando, deve essere avvolto in un elemento CoordinatorLayout
nel layout. Nel mio esempio sto usando un elemento NestedScrollView
:
Il CoordinatorLayout
consente di assegnare un elemento Behavior
alle sue viste figlie dirette. In questo caso assegneremo un elemento Behavior
alla vista NestedScrollView
che implementerà l'effetto di rimbalzo dell'overscroll.
Diamo un'occhiata al codice dell'elemento Behavior
:
public class OverScrollBounceBehavior extends CoordinatorLayout.Behavior {
private int mOverScrollY;
public OverScrollBounceBehavior() {
}
public OverScrollBounceBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
mOverScrollY = 0;
return true;
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
if (dyUnconsumed == 0) {
return;
}
mOverScrollY -= dyUnconsumed;
final ViewGroup group = (ViewGroup) target;
final int count = group.getChildCount();
for (int i = 0; i < count; i++) {
final View view = group.getChildAt(i);
view.setTranslationY(mOverScrollY);
}
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
final ViewGroup group = (ViewGroup) target;
final int count = group.getChildCount();
for (int i = 0; i < count; i++) {
final View view = group.getChildAt(i);
ViewCompat.animate(view).translationY(0).start();
}
}
}
Spieghiamo che cosa è un Behavior
e come funzionano va oltre lo scopo di questa risposta, quindi mi limiterò a spiegare rapidamente cosa fa il codice qui sopra. Il Behavior
intercetta tutti gli eventi di scorrimento che avvengono nei figli diretti dell'elemento CoordinatorLayout
. Nella sezione onStartNestedScroll()
si restituisce true
poiché siamo interessati a qualsiasi evento di scorrimento. In onNestedScroll()
guardiamo il metodo dyUnconsumed
che ci dice quanta parte dello scorrimento verticale non è stata consumata dal contenitore di scorrimento (in altre parole, l'overscroll) e quindi trasla i figli del contenitore di scorrimento di quella quantità. Poiché stiamo ottenendo solo valori delta, dobbiamo sommarli tutti nel parametro mOverscrollY
. onStopNestedScroll()
viene richiamato quando l'evento di scorrimento si ferma. Questo è il momento in cui animiamo tutti i figli del contenitore di scorrimento, riportandoli alla loro posizione originale.
Per assegnare la variabile Behavior
all'elemento NestedScrollView
dobbiamo usare il parametro layout_behavior
e passare il nome completo della classe dell'elemento Behavior
che vogliamo usare. Nel mio esempio, la classe di cui sopra si trova nel pacchetto com.github.wrdlbrnft.testapp
quindi devo impostare com.github.wrdlbrnft.testapp.OverScrollBounceBehavior
come valore. layout_behavior
è un attributo personalizzato dell'elemento CoordinatorLayout
quindi è necessario inserire il prefisso con lo spazio dei nomi corretto:
Si noti lo spazio dei nomi che ho aggiunto all'elemento CoordinatorLayout
e al tag app:layout_behavior
che ho aggiunto all'elemento NestedScrollView
.
E questo è tutto ciò che dovete fare! Sebbene questa risposta si sia rivelata più lunga di quanto intendessi, ho saltato alcune delle nozioni di base, come l'attributo CoordinatorLayout
e Behaviors
. Quindi, se non avete familiarità con questi o avete altre domande, non esitate a chiedere.
Grazie a Xaver Kapeller, ho scritto la mia soluzione con la sovrascrittura di fling e piccole aggiunte, usando kotlin e androidx
Aggiungere la dipendenza del coordinatore
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
Creare una nuova classe che estenda CoordinatorLayout.Behavior
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
class OverScrollBehavior(context: Context, attributeSet: AttributeSet)
: CoordinatorLayout.Behavior() {
companion object {
private const val OVER_SCROLL_AREA = 4
}
private var overScrollY = 0
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: View,
directTargetChild: View,
target: View,
axes: Int,
type: Int
): Boolean {
overScrollY = 0
return true
}
override fun onNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: View,
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
consumed: IntArray
) {
if (dyUnconsumed == 0) {
return
}
overScrollY -= (dyUnconsumed/OVER_SCROLL_AREA)
val group = target as ViewGroup
val count = group.childCount
for (i in 0 until count) {
val view = group.getChildAt(i)
view.translationY = overScrollY.toFloat()
}
}
override fun onStopNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: View,
target: View,
type: Int
) {
// Smooth animate to 0 when the user stops scrolling
moveToDefPosition(target)
}
override fun onNestedPreFling(
coordinatorLayout: CoordinatorLayout,
child: View,
target: View,
velocityX: Float,
velocityY: Float
): Boolean {
// Scroll view by inertia when current position equals to 0
if (overScrollY == 0) {
return false
}
// Smooth animate to 0 when user fling view
moveToDefPosition(target)
return true
}
private fun moveToDefPosition(target: View) {
val group = target as ViewGroup
val count = group.childCount
for (i in 0 until count) {
val view = group.getChildAt(i)
ViewCompat.animate(view)
.translationY(0f)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
}
}
}
Creare un file XML con CoordinatorLayout e NestedScrollView
E non dimenticare di aggiungere
app:layout_behavior=".OverScrollBehavior" // Or your file name
al markup XML di NestedScrollView
Ecco le recensioni e i punteggi
Ricorda che hai la possibilità di commentare se ti è stato utile.