如何在Android中创build滚动轮播页面?
我正在尝试为我的Android应用程序构build一个UI,其中包含一个可水平滚动轮播的垂直滚动页面(类似于Netflix应用程序的内容)。 这种行为是如何完成的?
一个基本的实现就足以让我开始。 UI还有一些其他的要求,我将在这里作为参考,因为它可能会影响我可以使用的类或库。
1)传送带之间的垂直滚动应当平滑,但是当用户释放时,UI应该“卡扣”到最近的传送带(因此用户总是在传送带排上而不是在两个传送带之间)。
2)旋转木马上的水平滚动应该是平滑的,但是当用户释放时,UI应该“抓住”旋转木马中最近的项目。
3)应该可以在传送带上的物品上叠加额外的信息
4)用户界面应适应任何屏幕大小。
5)应该可以通过箭头键进行导航(对于无触摸屏设备)
6)应该在广泛的Android版本上工作(可能通过支持库)
7)应该可以在GPL许可的开源应用程序中使用
可接受的答案不必满足所有这些要求。 至less,一个好的答案应该涉及导航多个传送带(相对于只有一个传送带)。
这里是一个基本上我所设想的模型(我是灵活的,不必看起来像这个…点只是为了澄清我在说什么 – 每行将包含很多项目,可以滚动左右,整个页面可以上下滚动)
大意
为了有一个灵活的devise和无限的项目,您可以创build一个RecyclerView
作为根视图与LinearLayoutManager.VERTICAL
作为LayoutManager
。 对于每行你可以把另一个RecyclerView
但现在LinearLayoutManager.HORIZONTAL
作为一个LayoutManager
。
结果
资源
码
要求
1)传送带之间的垂直滚动应当平滑,但是当用户释放时,UI应该“贴靠”最近的传送带(因此用户总是在传送带排上,而不是在两个传送带之间)。
2)旋转木马上的水平滚动应该是平滑的,但是当用户释放时,UI应该“抓住”旋转木马中最近的项目。
为了实现那些我使用OnScrollListener
和状态去SCROLL_STATE_IDLE我检查顶部和底部的意见,看看他们哪些有更多的可见区域,然后滚动到该位置。 对于每一行我都这样做左,右视图为每个行适配器。 通过这种方式,您的传送带或行的一侧总是适合的。 例如,如果顶部安装,底部不是或反之亦然。 我想如果你多玩一点,你可以做到这一点,但你必须知道窗口的尺寸,并在运行时改变传送带的尺寸。
3)应该可以在传送带上的物品上叠加额外的信息
如果您使用RelativeLayout
或FrameLayout
作为每个项目的根视图,则可以将信息放在彼此的顶部。 正如你所看到的数字是在图像的顶部。
4)用户界面应适应任何屏幕大小。
如果您知道如何支持多种屏幕尺寸,您可以轻松地进行操作,如果您不知道阅读文档。 支持多个屏幕
5)应该可以通过箭头键进行导航(对于无触屏的设备)
使用下面的function
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; } };
这里是testing适配器
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。 这是文档。
我会build议Recycler视图。
您可以创build水平和垂直列表或网格视图。 在我看来,这个观点有时会变得复杂。
我正在开发video点播应用程序,这节省了我。
在你的情况下,它会很容易设置。 我会给你一些代码。
您将需要以下内容:
XML视图 – 声明回收布局的位置。
适配器 – 您将需要一个视图来填充适配器并填充回收视图。
创build视图
<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"/>
声明这个你想要传送带显示的地方。
接下来你想创build适配器:
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);
您可以使用回收视图创build一个列表视图来实现你想要的。
这个类非常适合平滑的滚动和内存优化。
这是它的链接:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
我希望这可以帮助你。
你可以在ScrollView
中使用ScrollView
,在ScrollView
为Vertical LinearLayout
for loop
放置一个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);