detector - Android: desplazamiento de ViewPager basado en la velocidad
gesture android studio (5)
La forma en que se desplaza el ViewPager ahora es por un elemento por gesto. Se trata del gesto de lanzamiento de la misma manera, sin importar si se trata de un lanzamiento rápido a pantalla completa o un arrastre lento; en la página final avanza solo un paso.
¿Hay proyectos o ejemplos que podrían agregar un lanzamiento basado en la velocidad que desplaza múltiples elementos según la velocidad del lanzamiento existente (si aún está en progreso) y avanza más si el gesto de lanzamiento es amplio y rápido?
¿Y si no hay por dónde empezar con algo como esto?
PD Se ofrece la recompensa. Por favor, no hay respuestas con referencias a la Galería o HorizontalScrollView
He encontrado una mejor realización para mí que en la respuesta marcada, este ViewPager se comporta mejor con los toques cuando quiero dejar de desplazarme https://github.com/Benjamin-Dobell/VelocityViewPager
Otra opción es copiar un código fuente completo de implementación de ViewPager
desde la biblioteca de soporte y personalizar un método determineTargetPage(...)
. Es responsable de determinar a qué página desplazarse en el gesto de lanzamiento. Este enfoque no es muy conveniente, pero funciona bastante bien. Ver código de implementación a continuación:
private int determineTargetPage(int curPage, float pageOffset, int velocity, int dx) {
int target;
if (Math.abs(dx) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
target = calculateFinalPage(curPage, velocity);
} else {
final float truncator = curPage >= mCurItem ? 0.4f : 0.6f;
target = (int) (curPage + pageOffset + truncator);
}
if (mItems.size() > 0) {
final ItemInfo first = mItems.get(0);
final ItemInfo last = mItems.get(mItems.size() - 1);
// Only let the user target pages we have items for
target = Math.max(first.position, Math.min(target, last.position));
}
return target;
}
private int calculateFinalPage(int curPage, int velocity) {
float distance = Math.abs(velocity) * MAX_SETTLE_DURATION / 1000f;
float normalDistance = (float) Math.sqrt(distance / 2) * 25;
int step = (int) - Math.signum(velocity);
int width = getClientWidth();
int page = curPage;
for (int i = curPage; i >= 0 && i < mAdapter.getCount(); i += step) {
float pageWidth = mAdapter.getPageWidth(i);
float remainingDistance = normalDistance - pageWidth * width;
if (remainingDistance >= 0) {
normalDistance = remainingDistance;
} else {
page = i;
break;
}
}
return page;
}
Puede anular la clase ScrollView u HorizontalScrollView y agregar ese comportamiento. Hay muchos errores en la Galería, y recuerdo que está en desuso desde el nivel 14 de la API.
ViewPager es una clase de la biblioteca de soporte. Descargue el código fuente de la biblioteca de asistencia y cambie aproximadamente 10 líneas de código en el método onTouchEvent para agregar la función deseada.
Utilizo la biblioteca de soporte modificada en mis proyectos durante aproximadamente un año, porque a veces necesito modificar varias líneas de código para hacer un pequeño cambio o agregar un nuevo método y no quiero copiar el código fuente de los componentes. Yo uso la versión modificada de fragmentos y viewpager.
Pero hay un problema que obtendrás: una vez en aproximadamente 6 meses tienes que combinar la biblioteca de soporte personalizada con la nueva versión oficial si necesitas nuevas funciones. Y tenga cuidado con los cambios, no querrá interrumpir la compatibilidad con las clases de biblioteca.
La técnica aquí es extender ViewPager
e imitar la mayor parte de lo que el localizador hará internamente, junto con la lógica de desplazamiento desde el widget de la Gallery
. La idea general es monitorear el lanzamiento (y la velocidad y los pergaminos que lo acompañan) y luego enviarlos como eventos de arrastre falsos al ViewPager
subyacente. Sin embargo, si lo hace solo, no funcionará (aún obtendrá un solo desplazamiento de página). Esto sucede porque el arrastre falso implementa límites en los límites en que el desplazamiento será efectivo. Puede imitar los cálculos en el ViewPager
extendido y detectar cuándo sucederá esto, luego simplemente pase la página y continúe como siempre. El beneficio de usar un arrastre falso significa que no tiene que lidiar con el ajuste a las páginas o el manejo de los bordes del ViewPager
.
Probé el siguiente código en el ejemplo de demostraciones de animación, descargable desde http://developer.android.com/training/animation/screen-slide.html al reemplazar ViewPager en ScreenSlideActivity
con este VelocityViewPager
(tanto en el diseño activity_screen_slide
como en el campo dentro de la actividad).
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author: Dororo @
* An extended ViewPager which implements multiple page flinging.
*
*/
package com.example.android.animationsdemo;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.GestureDetector;
import android.widget.Scroller;
public class VelocityViewPager extends ViewPager implements GestureDetector.OnGestureListener {
private GestureDetector mGestureDetector;
private FlingRunnable mFlingRunnable = new FlingRunnable();
private boolean mScrolling = false;
public VelocityViewPager(Context context) {
super(context);
}
public VelocityViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, this);
}
// We have to intercept this touch event else fakeDrag functions won''t work as it will
// be in a real drag when we want to initialise the fake drag.
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// give all the events to the gesture detector. I''m returning true here so the viewpager doesn''t
// get any events at all, I''m sure you could adjust this to make that not true.
mGestureDetector.onTouchEvent(event);
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
mFlingRunnable.startUsingVelocity((int)velX);
return false;
}
private void trackMotion(float distX) {
// The following mimics the underlying calculations in ViewPager
float scrollX = getScrollX() - distX;
final int width = getWidth();
final int widthWithMargin = width + this.getPageMargin();
final float leftBound = Math.max(0, (this.getCurrentItem() - 1) * widthWithMargin);
final float rightBound = Math.min(this.getCurrentItem() + 1, this.getAdapter().getCount() - 1) * widthWithMargin;
if (scrollX < leftBound) {
scrollX = leftBound;
// Now we know that we''ve hit the bound, flip the page
if (this.getCurrentItem() > 0) {
this.setCurrentItem(this.getCurrentItem() - 1, false);
}
}
else if (scrollX > rightBound) {
scrollX = rightBound;
// Now we know that we''ve hit the bound, flip the page
if (this.getCurrentItem() < (this.getAdapter().getCount() - 1) ) {
this.setCurrentItem(this.getCurrentItem() + 1, false);
}
}
// Do the fake dragging
if (mScrolling) {
this.fakeDragBy(distX);
}
else {
this.beginFakeDrag();
this.fakeDragBy(distX);
mScrolling = true;
}
}
private void endFlingMotion() {
mScrolling = false;
this.endFakeDrag();
}
// The fling runnable which moves the view pager and tracks decay
private class FlingRunnable implements Runnable {
private Scroller mScroller; // use this to store the points which will be used to create the scroll
private int mLastFlingX;
private FlingRunnable() {
mScroller = new Scroller(getContext());
}
public void startUsingVelocity(int initialVel) {
if (initialVel == 0) {
// there is no velocity to fling!
return;
}
removeCallbacks(this); // stop pending flings
int initialX = initialVel < 0 ? Integer.MAX_VALUE : 0;
mLastFlingX = initialX;
// setup the scroller to calulate the new x positions based on the initial velocity. Impose no cap on the min/max x values.
mScroller.fling(initialX, 0, initialVel, 0, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
post(this);
}
private void endFling() {
mScroller.forceFinished(true);
endFlingMotion();
}
@Override
public void run() {
final Scroller scroller = mScroller;
boolean animationNotFinished = scroller.computeScrollOffset();
final int x = scroller.getCurrX();
int delta = x - mLastFlingX;
trackMotion(delta);
if (animationNotFinished) {
mLastFlingX = x;
post(this);
}
else {
endFling();
}
}
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distX, float distY) {
trackMotion(-distX);
return false;
}
// Unused Gesture Detector functions below
@Override
public boolean onDown(MotionEvent event) {
return false;
}
@Override
public void onLongPress(MotionEvent event) {
// we don''t want to do anything on a long press, though you should probably feed this to the page being long-pressed.
}
@Override
public void onShowPress(MotionEvent event) {
// we don''t want to show any visual feedback
}
@Override
public boolean onSingleTapUp(MotionEvent event) {
// we don''t want to snap to the next page on a tap so ignore this
return false;
}
}
Hay algunos problemas menores con esto, que pueden resolverse fácilmente, pero los dejaré a usted, a saber, cosas como si se desplaza (arrastrando, no lanzando) puede terminar a mitad de camino entre las páginas (querrá ajustarse) el evento ACTION_UP). Además, los eventos táctiles se están anulando por completo para hacer esto, por lo que deberá ViewPager
los eventos relevantes al ViewPager
subyacente cuando sea apropiado.