Vista de lista ajustar al elemento

Estoy creando una lista de imágenes usando un ListView y las fotos son de un tamaño que cabría de 2 a 3 fotos en la pantalla.

El problema que tengo es que me gustaría que cuando el usuario deje de desplazarse, el primer elemento de la lista visible se ajuste a la parte superior de la pantalla, por ejemplo, si el desplazamiento finaliza y se muestra una pequeña parte de la primera imagen, desplazamos la lista hacia abajo para que la imagen siempre se muestre por completo, si la mayoría de las imágenes se muestran, desplazamos la lista hacia arriba para que la siguiente imagen sea completamente visible.

¿Hay alguna manera de lograr esto en Android con la vista de lista?

He encontrado una manera de hacer esto solo escuchando desplazarse y cambiar la posición cuando el desplazamiento finalizó al implementar ListView.OnScrollListener

@Override public void onScrollStateChanged(AbsListView view, int scrollState) { switch (scrollState) { case OnScrollListener.SCROLL_STATE_IDLE: if (scrolling){ // get first visible item View itemView = view.getChildAt(0); int top = Math.abs(itemView.getTop()); // top is a negative value int bottom = Math.abs(itemView.getBottom()); if (top >= bottom){ ((ListView)view).setSelectionFromTop(view.getFirstVisiblePosition()+1, 0); } else { ((ListView)view).setSelectionFromTop(view.getFirstVisiblePosition(), 0); } } scrolling = false; break; case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: case OnScrollListener.SCROLL_STATE_FLING: Log.i("TEST", "SCROLLING"); scrolling = true; break; } } 

El cambio no es tan suave pero funciona.

Utilizando un par de ideas de la solución de @in ninhoho, conseguí que mi vista de lista se ajustara al elemento con un desplazamiento suave en lugar de ir abruptamente hacia él. Una advertencia es que solo probé esta solución en un Moto X en un ListView básico con texto, pero funciona muy bien en el dispositivo. Sin embargo, confío en esta solución y lo animo a que envíe sus comentarios.

 listview.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO Auto-generated method stub if (scrollState == SCROLL_STATE_IDLE) { View itemView = view.getChildAt(0); int top = Math.abs(itemView.getTop()); int bottom = Math.abs(itemView.getBottom()); int scrollBy = top >= bottom ? bottom : -top; if (scrollBy == 0) { return; } smoothScrollDeferred(scrollBy, (ListView)view); } } private void smoothScrollDeferred(final int scrollByF, final ListView viewF) { final Handler h = new Handler(); h.post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub viewF.smoothScrollBy(scrollByF, 200); } }); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO Auto-generated method stub } }); 

La razón por la que difiero el desplazamiento suave es porque en mis pruebas, llamar directamente al método smoothScrollBy en la callback modificada por el estado tenía problemas para desplazarse. Además, no preveo que una solución robusta y completamente probada tenga mucho estado, y en mi solución a continuación, no tengo ningún estado. Esta solución aún no está en Google Play Store, pero debería servir como un buen punto de partida.

Usando la solución de @nininho,

En onScrollStateChanged cuando el estado cambia a SCROLL_STATE_IDLE , recuerde la posición para tomar una bandera y levantarla:

 snapTo = view.getFirstVisiblePosition(); shouldSnap = true; 

Luego, anule el método computeScroll() :

 @Override public void computeScroll() { super.computeScroll(); if(shouldSnap){ this.smoothScrollToPositionFromTop(snapTo, 0); shouldSnap = false; } } 

Puede hacer un desplazamiento mucho más suave si usa RecyclerView. OnScrollListener es mucho mejor.

He hecho un ejemplo aquí: https://github.com/plattysoft/SnappingList

Además de probar el código de arriba, una cosa de la que debes asegurarte es que tu listaView tenga una altura que se ajuste al número exacto de elementos que deseas que se muestre. por ejemplo, si desea que se muestren 4 elementos después del efecto de ajuste y su altura de fila (definida en su diseño) debe ser 1/4 de la altura total de la lista.

Tenga en cuenta que después de la llamada smoothScrollBy() , getFirstVisiblePosition() puede apuntar al elemento de la lista ARRIBA del más alto en la vista de lista. Esto es especialmente cierto cuando view.getChildAt(0).getBottom() == 0 . Tuve que llamar a view.setSelection(view.getFirstVisiblePosition() + 1) para remediar este extraño comportamiento.