有限项的无限滚动

时间:2017-11-11 21:27:08

标签: android android-layout android-canvas android-view android-gridview

我有一个GridView,其中包含5x50内的项目。

我需要向所有方向滚动它们,而不是在从顶部/左侧开始到达终点时停止。

例如从左到右滚动

滚动前的

1 2 3 4 5
6 7 8 9 10
滚动到右边后

5 1 2 3 4
10 6 7 8 9

从上到下(或从下到上)

滚动到底部之前

1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
滚动后

6 7 8 9 10
11 12 13 14 15
1 2 3 4 5

我尝试将其平滑滚动为GridView原生滚动。

3 个答案:

答案 0 :(得分:14)

activity_main.xml 中指定了以下视图层次结构:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

基本列表项 - TextViewitem.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"/>

然后在活动中:

public class MainActivity extends AppCompatActivity {

    private static final int spanCount = 5;
    private static final int totalItemCount = 15;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.recycler);
        // Shamelessly stolen from devunwired bit.ly/2yCqVIp
        recyclerView.addItemDecoration(new GridDividerDecoration(this));
        recyclerView.setLayoutManager(new GridLayoutManager(this, spanCount, LinearLayoutManager.VERTICAL, false));
        recyclerView.setAdapter(new MyAdapter(totalItemCount));
    }

    static class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {

        private final int totalSizeOfItems;
        private boolean hasBeenSetup = false;

        MyAdapter(int totalSizeOfItems) {
            this.totalSizeOfItems = totalSizeOfItems;
        }

        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
            final int cellSize = parent.getMeasuredWidth() / spanCount;
            view.setMinimumWidth(cellSize);
            view.setMinimumHeight(cellSize);
            setupRecyclerHeightIfNeeded(parent, cellSize);
            return new MyViewHolder(view);
        }

        // We need to perform this operation once, not each time `onCreateViewHolder` is called
        private void setupRecyclerHeightIfNeeded(View parent, int cellSize) {
            if (hasBeenSetup) return;
            hasBeenSetup = true;
            ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) parent.getLayoutParams();
            int numOfRows = (int) (totalItemCount / (double) spanCount);
            params.height = numOfRows * cellSize;
            new Handler().post(parent::requestLayout);
        }

        @Override
        public void onBindViewHolder(MyViewHolder holder, int pos) {
            int position = holder.getAdapterPosition() % totalSizeOfItems;
            holder.textView.setText(Integer.toString(position + 1));
        }

        @Override
        public int getItemCount() {
            // this will result the list to be "infinite"
            return Integer.MAX_VALUE;
        }
    }

    static class MyViewHolder extends RecyclerView.ViewHolder {

        TextView textView;

        MyViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView;
        }
    }
}

在输出时,您将获得:

enter image description here

处理水平滚动需要进行少量更改:

  • GridLayoutManager的方向应更改为HORIZONTAL
  • 内部适配器适当的宽度/高度设置器应替换

除此之外 - 一切都应该是相似的。

答案 1 :(得分:5)

此解决方案基于 @azizbekian 源代码(感谢有趣的示例)。找到与你问的内容很接近的东西真的很有趣( 同时 )。所以,这是我修改过的代码:

public class MainActivity extends AppCompatActivity {

    private static final int spanCount = 5;
    private static final int totalItemCount = 15;

    private GridLayoutManager gridLayoutManager;
    private RecyclerView recyclerView;

    private int orientation = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.recycler);
        recyclerView.setAdapter(new MyAdapter(totalItemCount));

        // Shamelessly stolen from devunwired bit.ly/2yCqVIp
        recyclerView.addItemDecoration(new GridDividerDecoration(this));

        gridLayoutManager = new GridLayoutManager(this, spanCount);
        recyclerView.setLayoutManager(gridLayoutManager);

        RecyclerView.OnScrollListener listener = new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recycler, int dx, int dy) {
                super.onScrolled(recycler, dx, dy);

                if (dx > 0) {
                    orientation = LinearLayoutManager.HORIZONTAL;

                } else if (dx < 0) {
                    orientation = LinearLayoutManager.VERTICAL;

                } else if (dy > 0) {
                    orientation = LinearLayoutManager.VERTICAL;

                } else if (dy < 0) {
                    orientation = LinearLayoutManager.HORIZONTAL;
                }

                recycler.post(new Runnable() {
                    @Override
                    public void run() {
                        gridLayoutManager.setOrientation(orientation);
                        recyclerView.setLayoutManager(gridLayoutManager);
                        recyclerView.getAdapter().notifyDataSetChanged();
                    }
                });
            }
        };

        recyclerView.addOnScrollListener(listener);
    }

    static class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {

        private final int totalSizeOfItems;
        private boolean hasBeenSetup = false;

        MyAdapter(int totalSizeOfItems) {
            this.totalSizeOfItems = totalSizeOfItems;
        }

        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
            final int cellSize = parent.getMeasuredWidth() / spanCount;
            view.setMinimumWidth(cellSize);
            view.setMinimumHeight(cellSize);
            setupRecyclerHeightIfNeeded(parent, cellSize);
            return new MyViewHolder(view);
        }

        // We need to perform this operation once, not each time `onCreateViewHolder` is called
        private void setupRecyclerHeightIfNeeded(final View parent, int cellSize) {
            if (hasBeenSetup) return;

            hasBeenSetup = true;
            ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) parent.getLayoutParams();

            int numOfRows = (int) (totalItemCount / (double) spanCount);
            params.height = numOfRows * cellSize + 100; // modified based on my phone height

            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    parent.requestLayout();
                }
            });
        }

        @Override
        public void onBindViewHolder(MyViewHolder holder, int pos) {
            int position = holder.getAdapterPosition() % totalSizeOfItems;
            holder.textView.setText(Integer.toString(position + 1));
        }

        @Override
        public int getItemCount() {
            // this will result the list to be "infinite"
            return Integer.MAX_VALUE;
        }
    }

    static class MyViewHolder extends RecyclerView.ViewHolder {

        TextView textView;

        MyViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView;
        }
    }
} 

布局向上滚动以显示垂直方向,向左滚动以显示水平方向。输出如下(抱歉丑陋的动画):

enter image description here

希望它会对你有所帮助。

答案 2 :(得分:3)

Here是另一种解决方案,但采用画布方法。

<强> activity_main.xml中

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraint_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <nice.fontaine.infinitescroll.CanvasView
        android:id="@+id/canvas_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</android.support.constraint.ConstraintLayout>

<强> MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CanvasView canvas = findViewById(R.id.canvas_view);

        String[][] labels = new String[][] {
                {"5", "8", "2"},
                {"4", "7", "1"},
                {"3", "6", "9"}
        };
        int columns = 3;
        int rows = 3;

        canvas.with(labels, columns, rows);
    }
}

<强> CanvasView.java

public class CanvasView extends View {

    private final Panning panning;
    private final GridManager gridManager;
    private Rect bounds;
    private Point current = new Point(0, 0);
    private List<Overlay> overlays;
    public CanvasView(Context context, AttributeSet attrs) {
        super(context, attrs);
        bounds = new Rect();
        panning = new Panning();
        overlays = new ArrayList<>();
        gridManager = new GridManager(this);
        init();
    }

    public void with(String[][] labels, int columns, int rows) {
        gridManager.with(labels, columns, rows);
    }

    private void init() {
        ViewTreeObserver observer = getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            @Override
            public void onGlobalLayout() {
                int width = getWidth();
                int height = getHeight();
                bounds.set(0, 0, width, height);
                gridManager.generate(bounds);
                getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        super.onSizeChanged(width, height, oldWidth, oldHeight);
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        new Canvas(bitmap);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        bounds.offsetTo(-current.x, -current.y);
        gridManager.generate(bounds);
        canvas.translate(current.x, current.y);
        for (Overlay overlay : overlays) {
            if (overlay.intersects(bounds)) {
                overlay.onDraw(canvas);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        current = panning.handle(event);
        invalidate();
        return true;
    }

    public void addChild(Overlay overlay) {
        this.overlays.add(overlay);
    }
}

<强> GridManager.java

class GridManager {

    private final CanvasView canvas;
    private int columns;
    private int rows;
    private String[][] labels;
    private final Map<String, Overlay> cache;

    GridManager(CanvasView canvas) {
        this.canvas = canvas;
        cache = new HashMap<>();
    }

    void with(String[][] labels, int columns, int rows) {
        this.columns = columns;
        this.rows = rows;
        this.labels = labels;
    }

    void generate(Rect bounds) {
        if (columns == 0 || rows == 0 || labels == null) return;
        int width = bounds.width();
        int height = bounds.height();

        int overlayWidth = width / columns;
        int overlayHeight = height / rows;

        int minX = mod(floor(bounds.left, overlayWidth), columns);
        int minY = mod(floor(bounds.top, overlayHeight), rows);

        int startX = floorToMod(bounds.left, overlayWidth);
        int startY = floorToMod(bounds.top, overlayHeight);

        for (int j = 0; j <= rows; j++) {
            for (int i = 0; i <= columns; i++) {
                String label = getLabel(minX, minY, i, j);
                int x = startX + i * overlayWidth;
                int y = startY + j * overlayHeight;

                String key = x + "_" + y;
                if (!cache.containsKey(key)) {
                    Overlay overlay = new Overlay(label, x, y, overlayWidth, overlayHeight);
                    cache.put(key, overlay);
                    canvas.addChild(overlay);
                }
            }
        }
    }

    private String getLabel(int minX, int minY, int i, int j) {
        int m = mod(minX + i, columns);
        int n = mod(minY + j, rows);
        return labels[n][m];
    }

    private int floor(double numerator, double denominator) {
        return (int) Math.floor(numerator / denominator);
    }

    private int floorToMod(int value, int modulo) {
        return value - mod(value, modulo);
    }

    private int mod(int value, int modulo) {
        return (value % modulo + modulo) % modulo;
    }
}

<强> Panning.java

class Panning {

    private Point start;
    private Point delta = new Point(0, 0);
    private Point cursor = new Point(0, 0);
    private boolean isFirst;

    Point handle(MotionEvent event) {
        final Point point = new Point((int) event.getX(), (int) event.getY());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                press();
                break;
            case MotionEvent.ACTION_MOVE:
                drag(point);
                break;
        }
        return new Point(cursor.x + delta.x, cursor.y + delta.y);
    }

    private void press() {
        isFirst = true;
    }

    private void drag(final Point point) {
        if (isFirst) {
            start = point;
            cursor.offset(delta.x, delta.y);
            isFirst = false;
        }
        delta.x = point.x - start.x;
        delta.y = point.y - start.y;
    }
}

<强> Overlay.java

class Overlay {

    private final String text;
    private final int x;
    private final int y;
    private final Paint paint;
    private final Rect bounds;
    private final Rect rect;
    private final Rect textRect;

    Overlay(String text, int x, int y, int width, int height) {
        this.text = text;
        this.bounds = new Rect(x, y, x + width, y + height);
        this.rect = new Rect();
        this.textRect = new Rect();
        paint = new Paint();
        paint.setColor(Color.BLACK);
        setTextSize(text);
        this.x = x + width / 2 - textRect.width() / 2;
        this.y = y + height / 2 + textRect.height() / 2;
    }

    boolean intersects(Rect r) {
        rect.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
        return rect.intersect(r.left, r.top, r.right, r.bottom);
    }

    void onDraw(Canvas canvas) {
        // rectangle
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRect(bounds, paint);

        // centered text
        paint.setStrokeWidth(2);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawText(text, x, y, paint);
    }

    private void setTextSize(String text) {
        final float testTextSize = 100f;
        paint.setTextSize(testTextSize);
        paint.getTextBounds(text, 0, text.length(), textRect);
    }
}