¿Cómo crear una página desplazable de carruseles en Android?

Estoy tratando de construir una interfaz de usuario para mi aplicación de Android que contiene una página desplazable verticalmente de carruseles horizontalmente desplazables (algo así como lo hace la aplicación Netflix). ¿Cómo se logra este tipo de comportamiento?

Una implementación básica sería suficiente para empezar. Hay algunos otros requisitos para la interfaz de usuario, que incluiré aquí como referencia, ya que puede afectar a qué clases o bibliotecas puedo utilizar.

1) El desplazamiento vertical entre los carruseles debe ser suave, pero cuando el usuario se libera, la interfaz de usuario debe "encajar" en el carrusel más cercano (por lo que el usuario está siempre en una fila de carrusel, no entre dos carruseles).

2) El desplazamiento horizontal en un carrusel debe ser suave, pero cuando el usuario se libera, la interfaz de usuario debe "encajar" en el elemento más cercano del carrusel.

3) Debe ser posible superponer la información adicional sobre un artículo en el carrusel

4) La interfaz de usuario debe ser adaptable a cualquier tamaño de pantalla.

5) Debe ser navegable con las teclas de flecha (para dispositivos sin pantalla táctil)

6) Debe trabajar en una amplia gama de versiones de Android (posiblemente a través de la biblioteca de soporte)

7) Debe estar bien para usar en una aplicación de código abierto con licencia bajo la licencia GPL

Las respuestas aceptables NO tienen que cumplir con todos estos requisitos. Como mínimo, una buena respuesta debe implicar la navegación de carruseles múltiples (contra sólo un carrusel).

Aquí está una maqueta de básicamente lo que estoy imaginando (soy flexible, no tiene que parecer esto .. punto es sólo para aclarar lo que estoy hablando – cada fila contendría una gran cantidad de elementos que podrían Se desplaza hacia la izquierda y la derecha, y toda la página se puede desplazar hacia arriba y hacia abajo)

Introduzca aquí la descripción de la imagen

Idea principal

Para tener un diseño flexible y tener elementos ilimitados , puede crear un RecyclerView como una vista de raíz con un LinearLayoutManager.VERTICAL como LayoutManager . Para cada fila puede poner otro RecyclerView pero ahora con un LinearLayoutManager.HORIZONTAL como LayoutManager .

Resultado

Introduzca aquí la descripción de la imagen

Fuente

Código

Requisitos

1) El desplazamiento vertical entre los carruseles debe ser suave, pero cuando el usuario se libera, la interfaz de usuario debe "encajar" en el carrusel más cercano (por lo que el usuario está siempre en una fila de carrusel, no entre dos carruseles).

2) El desplazamiento horizontal en un carrusel debe ser suave, pero cuando el usuario se libera, la interfaz de usuario debe "encajar" en el elemento más cercano del carrusel.

Para conseguir ésos utilicé OnScrollListener y cuando los estados van SCROLL_STATE_IDLE compruebo las vistas superiores e inferiores para ver cuál de ellas tiene región más visible entonces rueda a esa posición. Para cada filas lo hago para las vistas izquierda y derecha para cada adaptador de fila. De esta manera siempre caben un lado de sus carruseles o filas. Por ejemplo, si la parte superior está montada, el fondo no es o viceversa. Creo que si juegas un poco más puedes lograrlo pero debes conocer la dimensión de la ventana y cambiar la dimensión de los carruseles en tiempo de ejecución.

3) Debe ser posible superponer la información adicional sobre un artículo en el carrusel

Si utiliza RelativeLayout o FrameLayout como una vista raíz de cada elemento, puede poner información encima de cada uno. Como se puede ver los números están en la parte superior de las imágenes.

4) La interfaz de usuario debe ser adaptable a cualquier tamaño de pantalla.

Si sabe cómo soportar el tamaño de pantalla múltiple que puede hacer tan fácilmente, si usted no sabe leer el documento. Soporte de varias pantallas

5) Debe ser navegable con las teclas de flecha (para dispositivos sin pantalla táctil)

Utilizar debajo de la función

 mRecyclerView.scrollToPosition(position); 

6) Debe trabajar en una amplia gama de versiones de Android (posiblemente a través de la biblioteca de soporte)

Import android Support.v7 .widget.RecyclerView;

7) Debe estar bien para usar en una aplicación de código abierto con licencia bajo la licencia GPL

De acuerdo

Codificación feliz!

Puede utilizar ListView con un OnTouchListener personalizado (para elementos de ajuste) para el desplazamiento vertical y TwoWayGridView de nuevo con un OnTouchListener personalizado (para elementos de ajuste)

Introduzca aquí la descripción de la imagen

Main.xml

 <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/containerList" android:layout_width="match_parent" android:layout_height="300dp" android:background="#E8E8E8" android:divider="@android:color/transparent" android:dividerHeight="16dp" /> 

List_item_hgrid.xml

 <com.jess.ui.TwoWayGridView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/grid" android:layout_width="match_parent" android:layout_height="160dp" android:layout_marginBottom="16dp" app:cacheColorHint="#E8E8E8" app:columnWidth="128dp" app:gravity="center" app:horizontalSpacing="16dp" app:numColumns="auto_fit" app:numRows="1" app:rowHeight="128dp" app:scrollDirectionLandscape="horizontal" app:scrollDirectionPortrait="horizontal" app:stretchMode="spacingWidthUniform" app:verticalSpacing="16dp" /> 

Y el código de actividad será algo como lo siguiente

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.test); ListView containerList = (ListView) findViewById(R.id.containerList); containerList.setAdapter(new DummyGridsAdapter(this)); containerList.setOnTouchListener(mContainerListOnTouchListener); } private View.OnTouchListener mContainerListOnTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: View itemView = ((ListView) view).getChildAt(0); int top = itemView.getTop(); if (Math.abs(top) >= itemView.getHeight() / 2) { top = itemView.getHeight() - Math.abs(top); } ((ListView) view).smoothScrollBy(top, 400); } return false; } }; 

Y aquí están los adaptadores de prueba

 private static class DummyGridsAdapter extends BaseAdapter { private Context mContext; private TwoWayGridView[] mChildGrid; public DummyGridsAdapter(Context context) { mContext = context; mChildGrid = new TwoWayGridView[getCount()]; for (int i = 0; i < mChildGrid.length; i++) { mChildGrid[i] = (TwoWayGridView) LayoutInflater.from(context). inflate(R.layout.list_item_hgrid, null); mChildGrid[i].setAdapter(new DummyImageAdapter(context)); mChildGrid[i].setOnTouchListener(mChildGridOnTouchListener); } } @Override public int getCount() { return 8; } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { return mChildGrid[position]; } private View.OnTouchListener mChildGridOnTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: View itemView = ((TwoWayGridView) view).getChildAt(0); int left = itemView.getLeft(); if (Math.abs(left) >= itemView.getWidth() / 2) { left = itemView.getWidth() - Math.abs(left); } ((TwoWayGridView) view).smoothScrollBy(left, 400); } return false; } }; } private static class DummyImageAdapter extends BaseAdapter { private Context mContext; private final int mDummyViewWidthHeight; public DummyImageAdapter(Context context) { mContext = context; mDummyViewWidthHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 128, context.getResources().getDisplayMetrics()); } @Override public int getCount() { return 16; } @Override public Object getItem(int position) { int component = (getCount() - position - 1) * 255 / getCount(); return Color.argb(255, 255, component, component); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView = new ImageView(mContext); imageView.setBackgroundColor((Integer) getItem(position)); imageView.setLayoutParams(new TwoWayGridView.LayoutParams(mDummyViewWidthHeight, mDummyViewWidthHeight)); return imageView; } } 

Intente ViewPager. Esta es la documentación.

Yo sugeriría la vista del reciclador.

Puede crear listas horizontales y verticales o vistas de cuadrícula. En mi opinión el viewpager puede llegar a ser complicado a veces.

Estoy trabajando en la aplicación de video a pedido y esto me salvó.

En su caso, será fácil de configurar. Te daré un código.

Necesitará lo siguiente:
XML View – donde se declara el diseño de reciclaje.
Adaptador – Necesitará una vista para rellenar el adaptador y rellenar el recycleview.

Creación de la vista

 <android.support.v7.widget.RecyclerView android:id="@+id/recycle_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="none" android:orientation="horizontal" android:gravity="center" android:overScrollMode="never"/> 

Declare esto donde desea que el carrusel se muestre.

A continuación, desea crear el adaptador:

 public class HorizontalCarouselItemAdapter extends RecyclerView.Adapter<HorizontalCarouselItemAdapter.ViewHolder> { List<objects> items; int itemLayout; public HorizontalCarouselItemAdapter(Context context, int itemLayout, List<objects> items) { this.context = context; this.itemLayout = itemLayout; this.items = items; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false); return new ViewHolder(v); } @Override public void onBindViewHolder(final ViewHolder holder, final int position) { this.holders = holder; final GenericAsset itemAdapter = items.get(position); holder.itemImage.setDrawable //manipulate variables here } @Override public int getItemCount() { return items.size(); } public static class ViewHolder extends RecyclerView.ViewHolder { public ImageView itemImage; public ViewHolder(View itemView) { super(itemView); itemImage = (ImageView) itemView.findViewById(R.id.carousel_cell_holder_image); } } 

Aquí es donde se alimentan los datos al adaptador para rellenar cada elemento del carrusel.
Finalmente declararlo y llamar al adaptador:

 recyclerView = (RecyclerView)findViewById(R.id.recycle_view); ListLayoutManager manager = new ListLayoutManager(getApplication(), ListLayoutManager.Orientation.HORIZONTAL); recyclerView.setLayoutManager(manager); CustomAdpater adapter = new CustomAdapter(getApplication(), data); recyclerView.setAdapter(adapter); 

Puede crear una vista de lista con vistas de reciclado para lograr lo que desea.
Esta clase es ideal para desplazamiento suave y optimización de la memoria.

Este es el enlace para ello:

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html

Espero que esto te ayude.

Puede utilizar un ScrollView como padre dentro de ScrollView que coloca un Vertical LinearLayout en el for loop inflar un diseño que consisten coverflow para el efecto carrusel

  • Github enlace de android-coverflow

Necesitaba algo así hace un tiempo atrás, sólo he utilizado eso: https://github.com/simonrob/Android-Horizontal-ListView

Simple, potente, personalizable.

Ejemplo de mi versión:

 public class HorizontalListView extends AdapterView<ListAdapter> { public boolean mAlwaysOverrideTouch = true; protected ListAdapter mAdapter; private int mLeftViewIndex = -1; private int mRightViewIndex = 0; protected int mCurrentX; protected int mNextX; private int mMaxX = Integer.MAX_VALUE; private int mDisplayOffset = 0; protected Scroller mScroller; private GestureDetector mGesture; private Queue<View> mRemovedViewQueue = new LinkedList<View>(); private OnItemSelectedListener mOnItemSelected; private OnItemClickListener mOnItemClicked; private OnItemLongClickListener mOnItemLongClicked; private boolean mDataChanged = false; public HorizontalListView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } private synchronized void initView() { mLeftViewIndex = -1; mRightViewIndex = 0; mDisplayOffset = 0; mCurrentX = 0; mNextX = 0; mMaxX = Integer.MAX_VALUE; mScroller = new Scroller(getContext()); mGesture = new GestureDetector(getContext(), mOnGesture); } @Override public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) { mOnItemSelected = listener; } @Override public void setOnItemClickListener(AdapterView.OnItemClickListener listener) { mOnItemClicked = listener; } @Override public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener) { mOnItemLongClicked = listener; } private DataSetObserver mDataObserver = new DataSetObserver() { @Override public void onChanged() { synchronized (HorizontalListView.this) { mDataChanged = true; } invalidate(); requestLayout(); } @Override public void onInvalidated() { reset(); invalidate(); requestLayout(); } }; @Override public ListAdapter getAdapter() { return mAdapter; } @Override public View getSelectedView() { //TODO: implement return null; } @Override public void setAdapter(ListAdapter adapter) { if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mDataObserver); } mAdapter = adapter; mAdapter.registerDataSetObserver(mDataObserver); reset(); } private synchronized void reset() { initView(); removeAllViewsInLayout(); requestLayout(); } @Override public void setSelection(int position) { //TODO: implement } private void addAndMeasureChild(final View child, int viewPos) { LayoutParams params = child.getLayoutParams(); if (params == null) { params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); } addViewInLayout(child, viewPos, params, true); child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); } @Override protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mAdapter == null) { return; } if (mDataChanged) { int oldCurrentX = mCurrentX; initView(); removeAllViewsInLayout(); mNextX = oldCurrentX; mDataChanged = false; } if (mScroller.computeScrollOffset()) { mNextX = mScroller.getCurrX(); } if (mNextX <= 0) { mNextX = 0; mScroller.forceFinished(true); } if (mNextX >= mMaxX) { mNextX = mMaxX; mScroller.forceFinished(true); } int dx = mCurrentX - mNextX; removeNonVisibleItems(dx); fillList(dx); positionItems(dx); mCurrentX = mNextX; if (!mScroller.isFinished()) { post(new Runnable() { @Override public void run() { requestLayout(); } }); } } private void fillList(final int dx) { int edge = 0; View child = getChildAt(getChildCount() - 1); if (child != null) { edge = child.getRight(); } fillListRight(edge, dx); edge = 0; child = getChildAt(0); if (child != null) { edge = child.getLeft(); } fillListLeft(edge, dx); } private void fillListRight(int rightEdge, final int dx) { while (rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) { View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this); addAndMeasureChild(child, -1); rightEdge += child.getMeasuredWidth(); if (mRightViewIndex == mAdapter.getCount() - 1) { mMaxX = mCurrentX + rightEdge - getWidth(); } if (mMaxX < 0) { mMaxX = 0; } mRightViewIndex++; } } private void fillListLeft(int leftEdge, final int dx) { while (leftEdge + dx > 0 && mLeftViewIndex >= 0) { View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this); addAndMeasureChild(child, 0); leftEdge -= child.getMeasuredWidth(); mLeftViewIndex--; mDisplayOffset -= child.getMeasuredWidth(); } } private void removeNonVisibleItems(final int dx) { View child = getChildAt(0); while (child != null && child.getRight() + dx <= 0) { mDisplayOffset += child.getMeasuredWidth(); mRemovedViewQueue.offer(child); removeViewInLayout(child); mLeftViewIndex++; child = getChildAt(0); } child = getChildAt(getChildCount() - 1); while (child != null && child.getLeft() + dx >= getWidth()) { mRemovedViewQueue.offer(child); removeViewInLayout(child); mRightViewIndex--; child = getChildAt(getChildCount() - 1); } } private void positionItems(final int dx) { if (getChildCount() > 0) { mDisplayOffset += dx; int left = mDisplayOffset; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); int childWidth = child.getMeasuredWidth(); child.layout(left, 0, left + childWidth, child.getMeasuredHeight()); left += childWidth; } } } public synchronized void scrollTo(int x) { mScroller.startScroll(mNextX, 0, x - mNextX, 0); requestLayout(); } public synchronized void scrollToChild(int position) { //TODO requestLayout(); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { return mGesture.onTouchEvent(ev); } protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { synchronized (HorizontalListView.this) { mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0); } requestLayout(); return true; } protected boolean onDown(MotionEvent e) { mScroller.forceFinished(true); return true; } private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { return HorizontalListView.this.onDown(e); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return HorizontalListView.this.onFling(e1, e2, velocityX, velocityY); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { synchronized (HorizontalListView.this) { mNextX += (int) distanceX; } requestLayout(); return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { Rect viewRect = new Rect(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); int left = child.getLeft(); int right = child.getRight(); int top = child.getTop(); int bottom = child.getBottom(); viewRect.set(left, top, right, bottom); if (viewRect.contains((int) e.getX(), (int) e.getY())) { if (mOnItemClicked != null) { mOnItemClicked.onItemClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i)); } if (mOnItemSelected != null) { mOnItemSelected.onItemSelected(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i)); } break; } } return true; } @Override public void onLongPress(MotionEvent e) { Rect viewRect = new Rect(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); int left = child.getLeft(); int right = child.getRight(); int top = child.getTop(); int bottom = child.getBottom(); viewRect.set(left, top, right, bottom); if (viewRect.contains((int) e.getX(), (int) e.getY())) { if (mOnItemLongClicked != null) { mOnItemLongClicked.onItemLongClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i)); } break; } } } }; } 

Aquí está el XML:

  <com.example.package.widgets.HorizontalListView android:id="@+id/horizontal_listview" android:layout_marginTop="30dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_width="fill_parent" android:layout_height="80dp" android:background="@color/light_gray" /> 

En el OnCreate:

 mAdapter = new ArrayAdapter<Uri>(this, R.layout.viewitem) { @Override public int getCount() { return listUriAdapter.size(); } @Override public Uri getItem(int position) { return listUriAdapter.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(final int position, View convertView, ViewGroup parent) { // do what you have to do return retval; } }; onItemClickListener = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { } }; onItemLongClickListener = new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) { return false; } }; horizontalListView.setOnItemClickListener(onItemClickListener); horizontalListView.setOnItemLongClickListener(onItemLongClickListener); horizontalListView.setAdapter(mAdapter); 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.