|
|
@@ -0,0 +1,602 @@
|
|
|
+package com.usai.redant.raimage.PhotoList;
|
|
|
+
|
|
|
+import android.content.Context;
|
|
|
+import android.graphics.PointF;
|
|
|
+import android.graphics.Rect;
|
|
|
+import android.support.annotation.IntDef;
|
|
|
+import android.support.v7.widget.LinearSmoothScroller;
|
|
|
+import android.support.v7.widget.RecyclerView;
|
|
|
+import android.util.AttributeSet;
|
|
|
+import android.util.SparseArray;
|
|
|
+import android.util.SparseBooleanArray;
|
|
|
+import android.view.View;
|
|
|
+import android.view.ViewGroup;
|
|
|
+import android.widget.LinearLayout;
|
|
|
+
|
|
|
+import java.lang.annotation.Retention;
|
|
|
+import java.lang.annotation.RetentionPolicy;
|
|
|
+
|
|
|
+/**
|
|
|
+ * CustomLayoutManager
|
|
|
+ * HowToCustomLayoutManager <com.vilyever.howtocustomlayoutmanager>
|
|
|
+ * Created by vilyever on 2016/1/7.
|
|
|
+ * Feature:
|
|
|
+ * 展示一个斜线布局的示例
|
|
|
+ * 支持横向纵向滑动
|
|
|
+ */
|
|
|
+public class CustomLayoutManager extends RecyclerView.LayoutManager {
|
|
|
+ /** Convenience Var to call this */
|
|
|
+ final CustomLayoutManager self = this;
|
|
|
+
|
|
|
+ int screen_w,screen_h;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * init with orientation and itemLineCount
|
|
|
+ * @param orientation {@link com.usai.redant.raimage.PhotoList.CustomLayoutManager.State#orientation}
|
|
|
+// * @param itemLineCount {@link com.usai.redant.raimage.PhotoList.CustomLayoutManager.State#itemLineCount}
|
|
|
+ */
|
|
|
+ public CustomLayoutManager(@Orientation int orientation, int screen_w, int screen_h) {
|
|
|
+ setOrientation(orientation);
|
|
|
+ this.screen_w = screen_w;
|
|
|
+ this.screen_h = screen_h;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @see RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler, RecyclerView.State) */
|
|
|
+ @Override
|
|
|
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
|
|
+ if (state.isPreLayout()) { // 跳过preLayout,preLayout主要用于支持动画,暂时先使用自带的简单的fading
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ self.detachAndScrapAttachedViews(recycler); // 分离所有的itemView
|
|
|
+
|
|
|
+ self.getState().contentWidth = 0;
|
|
|
+ self.getState().contentHeight = 0;
|
|
|
+
|
|
|
+ if (getItemCount() == 0) {
|
|
|
+ self.getState().scrolledX = 0;
|
|
|
+ self.getState().scrolledY = 0;
|
|
|
+ self.getState().totalSpreadCount = 0;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ self.getState().totalSpreadCount = 1;
|
|
|
+
|
|
|
+ int offsetX = 15; // 当前X偏移,下一个item的left为此值
|
|
|
+ int offsetY = 0; // 当前Y偏移,下一个item的top为此值
|
|
|
+ int occupiedLineBlock = 0; // 当前line已被占用的块数
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@link #orientation} 为 {@link #HORIZONTAL} 时,当前line的最大宽度
|
|
|
+ * {@link #orientation} 为 {@link #VERTICAL} 时,当前line的最大高度
|
|
|
+ */
|
|
|
+ int currentLineSpreadLength = 0;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@link #orientation} 为 {@link #HORIZONTAL} 时,当前line的总高度
|
|
|
+ * {@link #orientation} 为 {@link #VERTICAL} 时,当前line的总宽度
|
|
|
+ */
|
|
|
+ int currentLineLength = 0;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@link #orientation} 为 {@link #HORIZONTAL} 时,所有line的最大高度
|
|
|
+ * {@link #orientation} 为 {@link #VERTICAL} 时,所有line的最大宽度
|
|
|
+ */
|
|
|
+ int maxLineLength = 0;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算所有item的frame,以及总宽高
|
|
|
+ */
|
|
|
+ for (int i = 0; i < getItemCount(); i++) {
|
|
|
+ View scrap = recycler.getViewForPosition(i); // 根据position获取一个碎片view,可以从回收的view中获取,也可能新构造一个
|
|
|
+
|
|
|
+
|
|
|
+ self.addView(scrap);
|
|
|
+ self.measureChildWithMargins(scrap, 0, 0); // 计算此碎片view包含边距的尺寸
|
|
|
+
|
|
|
+ int width = self.getDecoratedMeasuredWidth(scrap); // 获取此碎片view包含边距和装饰的宽度width
|
|
|
+ int height = self.getDecoratedMeasuredHeight(scrap); // 获取此碎片view包含边距和装饰的高度height
|
|
|
+
|
|
|
+ LayoutParams layoutParams = (LayoutParams) scrap.getLayoutParams();
|
|
|
+ occupiedLineBlock += Math.max(layoutParams.occupationLineBlocks, 1); // 记录当前line已占用的块数,每个item至少占1块
|
|
|
+
|
|
|
+ /**宽度超过屏幕宽度就移动到下一行*/
|
|
|
+ int col = screen_w / width;
|
|
|
+ int margin = (screen_w - col * width) / (col + 1);
|
|
|
+ if (getOrientation() == VERTICAL) {
|
|
|
+ if (i == 0) {
|
|
|
+ offsetX = margin;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 占用的块数若超过了{@link com.vilyever.howtocustomlayoutmanager.CustomLayoutManager.State#itemLineCount},切换到下一line */
|
|
|
+ if (occupiedLineBlock > col/*self.getState().itemLineCount*/) {
|
|
|
+ if (self.getState().orientation == HORIZONTAL) {
|
|
|
+ offsetX += currentLineSpreadLength; // 横向偏移当前line的最大宽度
|
|
|
+ offsetY = 0; // 纵向重置到0
|
|
|
+ self.getState().contentWidth += currentLineSpreadLength; // contentWidth增加当前line的最大宽度
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ offsetX = margin; // 横向重置到0
|
|
|
+ offsetY += currentLineSpreadLength + margin; // 纵向偏移当前line的最大高度
|
|
|
+ self.getState().contentHeight += currentLineSpreadLength; // contentHeight增加当前line的最大高度
|
|
|
+ }
|
|
|
+
|
|
|
+ occupiedLineBlock = layoutParams.occupationLineBlocks; // 切换到新line布置item
|
|
|
+ currentLineSpreadLength = 0; // 新line还没有item,不占空间
|
|
|
+ maxLineLength = Math.max(maxLineLength, currentLineLength); // 记录之前所有line的最大长度
|
|
|
+ currentLineLength = 0; // 重置
|
|
|
+ self.getState().totalSpreadCount++;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ Rect frame = self.getState().itemsFrames.get(i); // 若先前生成过Rect,重复使用
|
|
|
+ if (frame == null) {
|
|
|
+ frame = new Rect();
|
|
|
+ }
|
|
|
+
|
|
|
+ frame.set(offsetX, offsetY, offsetX + width, offsetY + height);
|
|
|
+ self.getState().itemsFrames.put(i, frame); // 记录每个item的frame
|
|
|
+ self.getState().itemsAttached.put(i, false); // 因为先前已经回收了所有item,此处将item显示标识置否
|
|
|
+
|
|
|
+ if (self.getState().orientation == HORIZONTAL) {
|
|
|
+ offsetY += height; // 纵向偏移,横向不变
|
|
|
+ currentLineSpreadLength = Math.max(currentLineSpreadLength, width); // 记录当前line最大宽度
|
|
|
+ currentLineLength += height; // 记录当前line总高度
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ offsetX += width + margin; // 横向偏移,纵向不变
|
|
|
+ currentLineSpreadLength = Math.max(currentLineSpreadLength, height); // 记录当前line最大高度
|
|
|
+ currentLineLength += width; // 记录当前line总宽度
|
|
|
+ }
|
|
|
+
|
|
|
+ self.detachAndScrapView(scrap, recycler); // 回收本次计算的碎片view
|
|
|
+ }
|
|
|
+
|
|
|
+ if (self.getState().orientation == HORIZONTAL) {
|
|
|
+ self.getState().contentWidth += currentLineSpreadLength; // contentWidth增加最后line的最大宽度
|
|
|
+ self.getState().contentHeight = maxLineLength; // contentHeight设为所有line的最大高度
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.getState().contentWidth = maxLineLength; // contentWidth设为所有line的最大宽度
|
|
|
+ self.getState().contentHeight += currentLineSpreadLength; // contentWidth增加最后line的最大高度
|
|
|
+ }
|
|
|
+
|
|
|
+ self.getState().contentWidth = Math.max(self.getState().contentWidth, self.getHorizontalSpace()); // 内容宽度最小为RecyclerView容器宽度
|
|
|
+ self.getState().contentHeight = Math.max(self.getState().contentHeight, self.getVerticalSpace()); // 内容高度最小为RecyclerView容器高度
|
|
|
+
|
|
|
+ // 依照新内容宽高调整记录的滑动距离,防止滑动偏移过大
|
|
|
+ self.fixScrollOffset();
|
|
|
+
|
|
|
+ self.layoutItems(recycler, state); // 放置当前scroll offset处要展示的item
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @see RecyclerView.LayoutManager#canScrollHorizontally() */
|
|
|
+ @Override
|
|
|
+ public boolean canScrollHorizontally() {
|
|
|
+ return self.getState().canScrollHorizontal;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @see RecyclerView.LayoutManager#scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State) */
|
|
|
+ @Override
|
|
|
+ public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
|
|
|
+ int willScroll = dx;
|
|
|
+ /**
|
|
|
+ * 限制滑动距离的最小值和最大值
|
|
|
+ */
|
|
|
+ if (self.getState().scrolledX + dx < 0) {
|
|
|
+ willScroll = -self.getState().scrolledX;
|
|
|
+ }
|
|
|
+ else if (self.getState().scrolledX + dx > self.getState().contentWidth - self.getHorizontalSpace()) {
|
|
|
+ willScroll = self.getState().contentWidth - self.getHorizontalSpace() - self.getState().scrolledX;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果将要滑动的距离为0,返回-dx以显示边缘光晕
|
|
|
+ if (willScroll == 0) {
|
|
|
+ return -dx;
|
|
|
+ }
|
|
|
+
|
|
|
+ self.getState().scrolledX += willScroll;
|
|
|
+
|
|
|
+ // 平移容器内的item
|
|
|
+ self.offsetChildrenHorizontal(-willScroll);
|
|
|
+ // 移除屏幕外的item,添加当前可显示的新item
|
|
|
+ self.layoutItems(recycler, state);
|
|
|
+
|
|
|
+ return willScroll;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @see RecyclerView.LayoutManager#canScrollVertically() */
|
|
|
+ @Override
|
|
|
+ public boolean canScrollVertically() {
|
|
|
+ return self.getState().canScrollVertical;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @see RecyclerView.LayoutManager#scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State) */
|
|
|
+ @Override
|
|
|
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
|
|
|
+ int willScroll = dy;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 限制滑动距离的最小值和最大值
|
|
|
+ */
|
|
|
+ if (self.getState().scrolledY + dy < 0) {
|
|
|
+ willScroll = -self.getState().scrolledY;
|
|
|
+ }
|
|
|
+ else if (self.getState().scrolledY + dy > self.getState().contentHeight - self.getVerticalSpace()) {
|
|
|
+ willScroll = self.getState().contentHeight - self.getVerticalSpace() - self.getState().scrolledY;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果将要滑动的距离为0,返回-dy以显示边缘光晕
|
|
|
+ if (willScroll == 0) {
|
|
|
+ return -dy;
|
|
|
+ }
|
|
|
+
|
|
|
+ self.getState().scrolledY += willScroll;
|
|
|
+
|
|
|
+ // 平移容器内的item
|
|
|
+ self.offsetChildrenVertical(-willScroll);
|
|
|
+ // 移除屏幕外的item,添加当前可显示的新item
|
|
|
+ self.layoutItems(recycler, state);
|
|
|
+
|
|
|
+ return willScroll;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void scrollToPosition(int position) {
|
|
|
+ position = Math.max(position, 0);
|
|
|
+ position = Math.min(position, self.getItemCount());
|
|
|
+
|
|
|
+ self.getState().scrolledX = self.getState().itemsFrames.get(position).left;
|
|
|
+ self.getState().scrolledY = self.getState().itemsFrames.get(position).top;
|
|
|
+ self.fixScrollOffset();
|
|
|
+
|
|
|
+ self.requestLayout();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
|
|
+ position = Math.max(position, 0);
|
|
|
+ position = Math.min(position, self.getItemCount());
|
|
|
+
|
|
|
+ /*
|
|
|
+ * LinearSmoothScroller's default behavior is to scroll the contents until
|
|
|
+ * the child is fully visible. It will snap to the top-left or bottom-right
|
|
|
+ * of the parent depending on whether the direction of travel was positive
|
|
|
+ * or negative.
|
|
|
+ */
|
|
|
+ LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
|
|
|
+ /*
|
|
|
+ * LinearSmoothScroller, at a minimum, just need to know the vector
|
|
|
+ * (x/y distance) to travel in order to get from the current positioning
|
|
|
+ * to the target.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public PointF computeScrollVectorForPosition(int targetPosition) {
|
|
|
+ int oldScrollX = self.getState().scrolledX;
|
|
|
+ int oldScrollY = self.getState().scrolledY;
|
|
|
+
|
|
|
+ self.getState().scrolledX = self.getState().itemsFrames.get(targetPosition).left;
|
|
|
+ self.getState().scrolledY = self.getState().itemsFrames.get(targetPosition).top;
|
|
|
+ self.fixScrollOffset();
|
|
|
+
|
|
|
+ int newScrollX = self.getState().scrolledX;
|
|
|
+ int newScrollY = self.getState().scrolledY;
|
|
|
+
|
|
|
+ self.getState().scrolledX = oldScrollX;
|
|
|
+ self.getState().scrolledY = oldScrollY;
|
|
|
+
|
|
|
+ return new PointF(newScrollX - oldScrollX, newScrollY - oldScrollY);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ scroller.setTargetPosition(position);
|
|
|
+ self.startSmoothScroll(scroller);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @see RecyclerView.LayoutManager#generateDefaultLayoutParams() */
|
|
|
+ @Override
|
|
|
+ public LayoutParams generateDefaultLayoutParams() {
|
|
|
+ return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
|
+ }
|
|
|
+ /** @see RecyclerView.LayoutManager#generateLayoutParams(ViewGroup.LayoutParams) */
|
|
|
+ @Override
|
|
|
+ public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
|
|
|
+ if (lp instanceof ViewGroup.MarginLayoutParams) {
|
|
|
+ return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
|
|
|
+ } else {
|
|
|
+ return new LayoutParams(lp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /** @see RecyclerView.LayoutManager#generateLayoutParams(Context, AttributeSet) */
|
|
|
+ @Override
|
|
|
+ public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
|
|
|
+ return new LayoutParams(c, attrs);
|
|
|
+ }
|
|
|
+ /** @see RecyclerView.LayoutManager#checkLayoutParams(RecyclerView.LayoutParams) */
|
|
|
+ @Override
|
|
|
+ public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
|
|
|
+ return lp instanceof LayoutParams;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class LayoutParams extends RecyclerView.LayoutParams {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 占用块数
|
|
|
+ * 不同方向布局时,占用行数或列数的值
|
|
|
+ * {@link State#itemLineCount}
|
|
|
+ * default is 1, must > 0
|
|
|
+ */
|
|
|
+ public int occupationLineBlocks = 1;
|
|
|
+
|
|
|
+ public LayoutParams(Context c, AttributeSet attrs) {
|
|
|
+ super(c, attrs);
|
|
|
+ }
|
|
|
+ public LayoutParams(int width, int height) {
|
|
|
+ super(width, height);
|
|
|
+ }
|
|
|
+ public LayoutParams(ViewGroup.MarginLayoutParams source) {
|
|
|
+ super(source);
|
|
|
+ }
|
|
|
+ public LayoutParams(ViewGroup.LayoutParams source) {
|
|
|
+ super(source);
|
|
|
+ }
|
|
|
+ public LayoutParams(RecyclerView.LayoutParams source) {
|
|
|
+ super(source);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 摆放当前状态下要展示的item
|
|
|
+ * @param recycler Recycler to use for fetching potentially cached views for a
|
|
|
+ * position
|
|
|
+ * @param state Transient state of RecyclerView
|
|
|
+ */
|
|
|
+ private void layoutItems(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
|
|
+ if (state.isPreLayout()) { // 跳过preLayout,preLayout主要用于支持动画
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 当前scroll offset状态下的显示区域
|
|
|
+ Rect displayFrame = new Rect(self.getState().scrolledX, self.getState().scrolledY, self.getState().scrolledX + self.getHorizontalSpace(), self.getState().scrolledY + self.getVerticalSpace());
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移除已显示的但在当前scroll offset状态下处于屏幕外的item
|
|
|
+ */
|
|
|
+ Rect childFrame = new Rect();
|
|
|
+ for (int i = 0; i < self.getChildCount(); i++) {
|
|
|
+ View child = self.getChildAt(i);
|
|
|
+ childFrame.left = self.getDecoratedLeft(child);
|
|
|
+ childFrame.top = self.getDecoratedTop(child);
|
|
|
+ childFrame.right = self.getDecoratedRight(child);
|
|
|
+ childFrame.bottom = self.getDecoratedBottom(child);
|
|
|
+
|
|
|
+ if (!Rect.intersects(displayFrame, childFrame)) {
|
|
|
+ self.getState().itemsAttached.put(self.getPosition(child), false);
|
|
|
+ self.removeAndRecycleView(child, recycler);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 摆放需要显示的item
|
|
|
+ * 由于RecyclerView实际上并没有scroll,也就是说RecyclerView容器的滑动效果是依赖于LayoutManager对item进行平移来实现的
|
|
|
+ * 故在放置item时要将item的计算位置平移到实际位置
|
|
|
+ */
|
|
|
+ for (int i = 0; i < self.getItemCount(); i++) {
|
|
|
+ if (Rect.intersects(displayFrame, self.getState().itemsFrames.get(i))) {
|
|
|
+ /**
|
|
|
+ * 在onLayoutChildren时由于移除了所有的item view,可以遍历全部item进行添加
|
|
|
+ * 在scroll时就不同了,由于scroll时会先将已显示的item view进行平移,然后移除屏幕外的item view,此时仍然在屏幕内显示的item view就无需再次添加了
|
|
|
+ */
|
|
|
+ if (!self.getState().itemsAttached.get(i)) {
|
|
|
+ View scrap = recycler.getViewForPosition(i);
|
|
|
+ self.measureChildWithMargins(scrap, 0, 0);
|
|
|
+ self.addView(scrap);
|
|
|
+
|
|
|
+ Rect frame = self.getState().itemsFrames.get(i);
|
|
|
+ self.layoutDecorated(scrap,
|
|
|
+ frame.left - self.getState().scrolledX,
|
|
|
+ frame.top - self.getState().scrolledY,
|
|
|
+ frame.right - self.getState().scrolledX,
|
|
|
+ frame.bottom - self.getState().scrolledY); // Important!布局到RecyclerView容器中,所有的计算都是为了得出任意position的item的边界来布局
|
|
|
+
|
|
|
+ self.getState().itemsAttached.put(i, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录当前LayoutManager的一些信息
|
|
|
+ */
|
|
|
+ private State state;
|
|
|
+ public State getState() {
|
|
|
+ if (state == null) {
|
|
|
+ state = new State();
|
|
|
+ }
|
|
|
+ return state;
|
|
|
+ }
|
|
|
+ class State {
|
|
|
+ /**
|
|
|
+ * 存放所有item的位置和尺寸
|
|
|
+ */
|
|
|
+ SparseArray<Rect> itemsFrames;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录item是否已经展示
|
|
|
+ */
|
|
|
+ SparseBooleanArray itemsAttached;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 横向滑动距离
|
|
|
+ * @see #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)
|
|
|
+ */
|
|
|
+ int scrolledX;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 纵向滑动距离
|
|
|
+ * @see #scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)
|
|
|
+ */
|
|
|
+ int scrolledY;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内容宽度
|
|
|
+ * note:最小宽度为容器宽度
|
|
|
+ */
|
|
|
+ int contentWidth;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内容高度
|
|
|
+ * note:最小高度为容器高度
|
|
|
+ */
|
|
|
+ int contentHeight;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否允许横向滑动
|
|
|
+ * 默认允许
|
|
|
+ */
|
|
|
+ boolean canScrollHorizontal;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否允许纵向滑动
|
|
|
+ * 默认允许
|
|
|
+ */
|
|
|
+ boolean canScrollVertical;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
|
|
|
+ */
|
|
|
+ int orientation;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@link #orientation} 为 {@link #HORIZONTAL} 时,每列显示的item数
|
|
|
+ * {@link #orientation} 为 {@link #VERTICAL} 时,每行显示的item数
|
|
|
+ * 每个item有可能占用多格 {@link com.usai.redant.raimage.PhotoList.CustomLayoutManager.LayoutParams#occupationLineBlocks}
|
|
|
+ */
|
|
|
+ int itemLineCount;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@link #orientation} 为 {@link #HORIZONTAL} 时,表示列数
|
|
|
+ * {@link #orientation} 为 {@link #VERTICAL} 时,表示行数
|
|
|
+ */
|
|
|
+ int totalSpreadCount;
|
|
|
+
|
|
|
+ public State() {
|
|
|
+ itemsFrames = new SparseArray<>();
|
|
|
+ itemsAttached = new SparseBooleanArray();
|
|
|
+ scrolledX = 0;
|
|
|
+ scrolledY = 0;
|
|
|
+ contentWidth = 0;
|
|
|
+ contentHeight = 0;
|
|
|
+ canScrollHorizontal = true;
|
|
|
+ canScrollVertical = true;
|
|
|
+ itemLineCount = 1;
|
|
|
+ totalSpreadCount = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 启用/禁用横向滑动
|
|
|
+ * @param canScrollHorizontal 启用/禁用
|
|
|
+ * @return {@link State#canScrollHorizontal}
|
|
|
+ */
|
|
|
+ public CustomLayoutManager setCanScrollHorizontal(boolean canScrollHorizontal) {
|
|
|
+ self.getState().canScrollHorizontal = canScrollHorizontal;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ public boolean getCanScrollHorizontal() {
|
|
|
+ return self.getState().canScrollHorizontal;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 启用/禁用纵向滑动
|
|
|
+ * @param canScrollVertical 启用/禁用
|
|
|
+ * @return {@link State#canScrollVertical}
|
|
|
+ */
|
|
|
+ public CustomLayoutManager setCanScrollVertical(boolean canScrollVertical) {
|
|
|
+ self.getState().canScrollVertical = canScrollVertical;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ public boolean getCanScrollVertical() {
|
|
|
+ return self.getState().canScrollVertical;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 纵然此LayoutManager在水平方向和垂直方向都可以滑动
|
|
|
+ * 此LayoutManager仍然带有orientation属性
|
|
|
+ * orientation将影响item摆放的次序
|
|
|
+ *
|
|
|
+ * 若Direction为Vertical,item的摆放顺序为从左到右,一行铺满后填充下一行
|
|
|
+ * 若Direction为Horizontal,item的摆放顺序为从上到下,一列铺满后填充下一列
|
|
|
+ */
|
|
|
+ @IntDef({HORIZONTAL, VERTICAL})
|
|
|
+ @Retention(RetentionPolicy.SOURCE)
|
|
|
+ public @interface Orientation {}
|
|
|
+ public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
|
|
|
+ public static final int VERTICAL = LinearLayout.VERTICAL;
|
|
|
+ public CustomLayoutManager setOrientation(@Orientation int orientation) {
|
|
|
+ if (orientation != HORIZONTAL && orientation != VERTICAL) {
|
|
|
+ throw new IllegalArgumentException("invalid orientation:" + orientation);
|
|
|
+ }
|
|
|
+ assertNotInLayoutOrScroll(null);
|
|
|
+ if (orientation == self.getState().orientation) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ self.getState().orientation = orientation;
|
|
|
+ self.requestLayout();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ @Orientation
|
|
|
+ public int getOrientation() {
|
|
|
+ return self.getState().orientation;
|
|
|
+ }
|
|
|
+
|
|
|
+ public CustomLayoutManager setItemLineCount(int itemLineCount) {
|
|
|
+ assertNotInLayoutOrScroll(null);
|
|
|
+ if (itemLineCount == self.getState().itemLineCount) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ self.getState().itemLineCount = itemLineCount;
|
|
|
+ self.requestLayout();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ public int getItemLineCount() {
|
|
|
+ return self.getState().itemLineCount;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 依照内容宽高调整记录的滑动距离,防止滑动偏移过大
|
|
|
+ */
|
|
|
+ private void fixScrollOffset() {
|
|
|
+ if (self.getState().contentWidth == self.getHorizontalSpace()) {
|
|
|
+ self.getState().scrolledX = 0;
|
|
|
+ }
|
|
|
+ if (self.getState().scrolledX > (self.getState().contentWidth - self.getHorizontalSpace())) {
|
|
|
+ self.getState().scrolledX = self.getState().contentWidth - self.getHorizontalSpace();
|
|
|
+ }
|
|
|
+ if (self.getState().contentHeight == self.getVerticalSpace()) {
|
|
|
+ self.getState().scrolledY = 0;
|
|
|
+ }
|
|
|
+ if (self.getState().scrolledY > (self.getState().contentHeight - self.getVerticalSpace())) {
|
|
|
+ self.getState().scrolledY = self.getState().contentHeight - self.getVerticalSpace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 容器去除padding后的宽度
|
|
|
+ * @return 实际可摆放item的空间
|
|
|
+ */
|
|
|
+ private int getHorizontalSpace() {
|
|
|
+ return self.getWidth() - self.getPaddingRight() - self.getPaddingLeft();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 容器去除padding后的高度
|
|
|
+ * @return 实际可摆放item的空间
|
|
|
+ */
|
|
|
+ private int getVerticalSpace() {
|
|
|
+ return self.getHeight() - self.getPaddingBottom() - self.getPaddingTop();
|
|
|
+ }
|
|
|
+}
|