Android水平スクロール可能なカルーセルの垂直スクロール可能なページを含むアプリ(Netflixアプリのようなもの)のUIを作成しようとしています。このタイプの動作はどのように達成されますか?
基本的な実装で十分です。 UIには他にもいくつかの要件がありますが、使用できるクラスやライブラリに影響を与える可能性があるため、参照用にここに含めます。
1)カルーセル間の垂直スクロールはスムーズにする必要がありますが、ユーザーが離すとき、UIは最も近いカルーセルに「スナップ」する必要があります(したがって、ユーザーは2つのカルーセル間ではなく、常にカルーセル行にあります) 。
2)カルーセルの水平スクロールはスムーズにする必要がありますが、ユーザーが離すと、UIはカルーセル内の最も近いアイテムに「スナップ」する必要があります。
3)カルーセル内のアイテムに追加情報をオーバーレイできるようにする必要があります
4)UIは任意の画面サイズに適応できる必要があります。
5)矢印キーでナビゲートできる必要があります(タッチスクリーンのないデバイスの場合)
6)広範囲のAndroidバージョン(おそらくサポートライブラリ経由))で動作するはずです
7)GPLの下でライセンスされたオープンソースアプリで使用しても問題ありません
受け入れられる回答はこれらの要件をすべて満たす必要はありません。少なくとも、1つのカルーセルではなく複数のカルーセルをナビゲートすることをお勧めします。
基本的に私が想定しているもののモックアップです(柔軟で、このように見える必要はありません..ポイントは、私が話していることを明確にすることです-それぞれ行には左右にスクロールできる多くのアイテムが含まれ、ページ全体を上下にスクロールできます)
柔軟な設計と無制限のアイテムを持つために、LinearLayoutManager.VERTICAL
を持つルートビューとしてRecyclerView
を作成できます。 LayoutManager
として。行ごとに、別のRecyclerView
を配置できますが、LinearLayoutManager.HORIZONTAL
をLayoutManager
として使用できます。
1)カルーセル間の垂直スクロールはスムーズにする必要がありますが、ユーザーが離すと、UIは最も近いカルーセルに「スナップ」する必要があります(したがって、ユーザーは2つのカルーセル間ではなく、常にカルーセル行にあります)。
2)カルーセルでの水平スクロールはスムーズにする必要がありますが、ユーザーが離すと、UIはカルーセル内の最も近いアイテムに「スナップ」する必要があります。
それらを達成するためにOnScrollListener
を使用し、状態がSCROLL_STATE_IDLEになったら、上下のビューを確認して、どちらがより見える領域があるかを確認し、その位置までスクロールします。行ごとに、各行アダプターの左ビューと右ビューでこれを行います。このようにして、常にカルーセルまたは列の片側が収まります。たとえば、上部が取り付けられている場合、下部は取り付けられていない、またはその逆です。もう少しプレイすれば達成できると思いますが、実行時にウィンドウの大きさを知って、カルーセルの大きさを変更する必要があります。
3)カルーセル内のアイテムに追加情報をオーバーレイできるようにする必要があります
RelativeLayout
またはFrameLayout
を各アイテムのルートビューとして使用する場合、情報を互いに重ねることができます。ご覧のとおり、数字は画像の上部にあります。
4)UIはあらゆる画面サイズに適応できる必要があります。
複数の画面サイズをサポートする方法を知っている場合は、ドキュメントを読んでわからない場合でも簡単に行うことができます。 複数画面のサポート
5)矢印キーを使用してナビゲートできる必要があります(タッチスクリーンのないデバイスの場合)
以下の機能を使用
mRecyclerView.scrollToPosition(position);
6)広範囲のAndroidバージョン(おそらくサポートライブラリ経由)で動作するはずです)
import Android。support.v7。widget.RecyclerView;
7)GPLの下でライセンスされたオープンソースアプリで使用しても問題ありません
Ok
ハッピーコーディング!!
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;
}
}
私はしばらく前にそのようなものが必要でした、私はそれを使用しました: 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);
リサイクルビューをお勧めします。
水平および垂直のリストまたはグリッドビューを作成できます。私の意見では、ビューページャーは時々複雑になる可能性があります。
私はビデオオンデマンドアプリケーションに取り組んでおり、これが私を救った。
あなたの場合、設定は簡単です。いくつかのコードを提供します。
次のものが必要です。
XMLビュー-リサイクルレイアウトが宣言されている場所。
Adapter-アダプターにデータを入力し、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
内の親として使用すると、Vertical LinearLayout
in for loop
coverflow
forカルーセル効果で構成されるレイアウトを膨らませます