Как создать прокручиваемую страницу каруселей в Android?

Я пытаюсь создать пользовательский интерфейс для Android-приложения, содержащего вертикально прокручиваемую страницу горизонтально прокручиваемых каруселей (что-то вроде приложения Netflix). Как достигается такой тип поведения?

Основной реализации было бы достаточно, чтобы начать меня. Есть еще несколько требований к пользовательскому интерфейсу, который я буду включать сюда для справки, поскольку это может повлиять на то, какие классы или библиотеки я могу использовать.

1) Вертикальная прокрутка между каруселями должна быть гладкой, но когда пользователь выпускает, пользовательский интерфейс должен «привязать» к ближайшей карусели (так что пользователь всегда находится на карусельном ряду, а не между двумя каруселями).

2) Горизонтальная прокрутка на карусели должна быть гладкой, но когда пользователь выпускает, пользовательский интерфейс должен «привязать» к ближайшему элементу в карусели.

3) Должна быть возможность наложить дополнительную информацию по предмету в карусели

4) Пользовательский интерфейс должен быть адаптирован к любому размеру экрана.

5) Должен быть навигационный с помощью клавиш со стрелками (для устройств с сенсорным экраном)

6) Должен работать в широком диапазоне версий Android (возможно, через библиотеку поддержки)

7) Должно быть хорошо, чтобы использовать в приложении с открытым исходным кодом, лицензированном под лицензией GPL

Приемлемые ответы НЕ должны отвечать всем этим требованиям. Как минимум, хороший ответ должен включать в себя навигацию по нескольким каруселям (по сравнению с одной карусели).

Вот макет, в основном, то, что я представляю (я гибкий, не должен выглядеть так). Точка просто для того, чтобы уточнить, о чем я говорю – каждая строка будет содержать много элементов, которые могли бы Прокручивать влево и вправо, и вся страница может быть прокручена вверх и вниз)

Введите описание изображения здесь

Solutions Collecting From Web of "Как создать прокручиваемую страницу каруселей в Android?"

Главная идея

Чтобы иметь гибкий дизайн и иметь неограниченные элементы, вы можете создать RecyclerView в виде корневого представления с LinearLayoutManager.VERTICAL в качестве LayoutManager . Для каждой строки вы можете поместить другой RecyclerView но теперь с LinearLayoutManager.HORIZONTAL как LayoutManager .

результат

Введите описание изображения здесь

Источник

Код

Требования

1) Вертикальная прокрутка между каруселями должна быть гладкой, но когда пользователь выпускает, пользовательский интерфейс должен «привязать» к ближайшей карусели (так что пользователь всегда находится на карусельном ряду, а не между двумя каруселями).

2) Горизонтальная прокрутка на карусели должна быть гладкой, но когда пользователь выпускает, пользовательский интерфейс должен «привязать» к ближайшему элементу в карусели.

Для достижения тех, что я использовал OnScrollListener и когда состояния идут SCROLL_STATE_IDLE, я проверяю верхнее и нижнее представления, чтобы увидеть, какая из них имеет более видимую область, а затем прокрутите ее до этой позиции. Для каждой строки я делаю это для просмотра влево и вправо для каждого адаптера строки. Таким образом, всегда одна сторона ваших каруселей или рядов подходит. Например, если верхняя часть установлена, нижняя часть не соответствует или наоборот. Я думаю, что если вы сыграете немного больше, вы можете это достичь, но вы должны знать размер окна и изменить размерность каруселей во время выполнения.

3) Должна быть возможность наложить дополнительную информацию по предмету в карусели

Если вы используете RelativeLayout или FrameLayout в качестве корневого представления каждого элемента, вы можете помещать информацию друг на друга. Как вы можете видеть, цифры находятся на верхней части изображений.

4) Пользовательский интерфейс должен быть адаптирован к любому размеру экрана.

Если вы знаете, как поддерживать множественный размер экрана, вы можете сделать это легко, если вы не знаете, как читать документ. Поддержка нескольких экранов

5) Должен быть навигационный с помощью клавиш со стрелками (для устройств с сенсорным экраном)

Использовать ниже функцию

 mRecyclerView.scrollToPosition(position); 

6) Должен работать в широком диапазоне версий Android (возможно, через библиотеку поддержки)

Импортировать android. Support.v7 .widget.RecyclerView;

7) Должно быть хорошо, чтобы использовать в приложении с открытым исходным кодом, лицензированном под лицензией GPL

ОК

Счастливое кодирование !!

Вы можете использовать ListView с пользовательским OnTouchListener (для привязки элементов) для вертикальной прокрутки и TwoWayGridView снова с пользовательским OnTouchListener (для привязки элементов)

Введите описание изображения здесь

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" /> 

Код операции будет выглядеть следующим образом:

 @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; } }; 

И вот тестовые адаптеры

 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; } } 

Попробуйте ViewPager. Это документация.

Я бы предложил представление Recycler.

Вы можете создавать горизонтальные и вертикальные списки или gridviews. По-моему, просмотрщик иногда может усложняться.

Я работаю над видео по требованию, и это спасло меня.

В вашем случае это будет легко настроить. Я дам вам код.

Вам понадобится следующее:
XML-вид – где объявлен макет утилиты.
Адаптер – вам потребуется представление, чтобы заполнить адаптер и заполнить recycleview.

Создание представления

 <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"/> 

Объявите это, где вы хотите, чтобы карусель отображалась.

Затем вы хотите создать адаптер:

 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); } } 

Здесь вы загружаете данные в адаптер для заполнения каждого элемента карусели.
Наконец, объявите его и вызовите адаптер:

 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); 

Вы можете создать представление списка с просмотрами переходов для достижения желаемого.
Этот класс отлично подходит для плавной прокрутки и оптимизации памяти.

Это ссылка на него:

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

Я надеюсь, это поможет вам.

Вы можете использовать ScrollView как родительский ScrollView внутри этого ScrollView чтобы Vertical LinearLayout в for loop раздувал макет, который состоит из coverflow для эффекта карусели

  • Ссылка на github android-coverflow

Мне понадобилось что-то вроде этого некоторое время назад, я просто использовал это: https://github.com/simonrob/Android-Horizontal-ListView

Простой, мощный, настраиваемый.

Пример моей версии:

 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; } } } }; } 

Вот 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" /> 

В 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);