Подтвердить что ты не робот

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

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

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

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

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

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

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

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

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

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

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

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

enter image description here

4b9b3361

Ответ 1

Основная идея

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

Результат

enter image description here

Источник

Код

Требования

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

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

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

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

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

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

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

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

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

mRecyclerView.scrollToPosition(position);

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

import android. support.v7.widget.RecyclerView;

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

Ok

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

Ответ 2

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

enter image description here

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

}

Ответ 3

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

Ответ 4

Я бы предложил представление 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

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

Ответ 5

Мне понадобилось что-то вроде этого некоторое время назад, я просто использовал это: 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);

Ответ 6

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