public class AwesomeLayoutManager extends RecyclerView.LayoutManager { @Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT); } }
@Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { View view = recycler.getViewForPosition(0); addView(view); measureChildWithMargins(view, 0, 0); layoutDecorated(view, 0, 0, getWidth(), getHeight()); }
view.layout(left, top, right, bottom) -> layoutDecorated(view, left, top, right, bottom) view.getLeft() -> getDecoratedLeft(view) view.getWidth() -> getDecoratedWidth(view)
private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec) { Rect decorRect = new Rect(); calculateItemDecorationsForChild(child, decorRect); RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + decorRect.left, lp.rightMargin + decorRect.right); heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + decorRect.top, lp.bottomMargin + decorRect.bottom); child.measure(widthSpec, heightSpec); } private int updateSpecWithExtra(int spec, int startInset, int endInset) { if (startInset == 0 && endInset == 0) { return spec; } final int mode = View.MeasureSpec.getMode(spec); if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) { return View.MeasureSpec.makeMeasureSpec( View.MeasureSpec.getSize(spec) - startInset - endInset, mode); } return spec; }
@Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { View view = recycler.getViewForPosition(0); addView(view); final int widthSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY); final int heightSpec = View.MeasureSpec.makeMeasureSpec(getHeight(), View.MeasureSpec.EXACTLY); measureChildWithDecorationsAndMargin(view, widthSpec, heightSpec); layoutDecorated(view, 0, 0, getWidth(), getHeight()); }
private static final float VIEW_HEIGHT_PERCENT = 0.75f; @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { View view = recycler.getViewForPosition(0); addView(view); int viewHeight = (int) (getHeight() * VIEW_HEIGHT_PERCENT); final int widthSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY); final int heightSpec = View.MeasureSpec.makeMeasureSpec(getHeight(), View.MeasureSpec.EXACTLY); measureChildWithDecorationsAndMargin(view, widthSpec, heightSpec); layoutDecorated(view, 0, 0, getWidth(), viewHeight); }
@Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { fillDown(recycler); } private void fillDown(RecyclerView.Recycler recycler) { int pos = 0; boolean fillDown = true; int height = getHeight(); int viewTop = 0; int itemCount = getItemCount(); int viewHeight = (int) (getHeight() * VIEW_HEIGHT_PERCENT); final int widthSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY); final int heightSpec = View.MeasureSpec.makeMeasureSpec(getHeight(), View.MeasureSpec.EXACTLY); while (fillDown && pos < itemCount){ View view = recycler.getViewForPosition(pos); addView(view); measureChildWithDecorationsAndMargin(view, widthSpec, heightSpec); int decoratedMeasuredWidth = getDecoratedMeasuredWidth(view); layoutDecorated(view, 0, viewTop, decoratedMeasuredWidth, viewTop + viewHeight); viewTop = getDecoratedBottom(view); fillDown = viewTop <= height; pos++; } }
@Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { detachAndScrapAttachedViews(recycler); fillDown(recycler); }
@Override public boolean canScrollVertically() { return true; }
@Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { offsetChildrenVertical(-dy); return dy; }
@Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { int delta = scrollVerticallyInternal(dy); offsetChildrenVertical(-delta); return delta; } private int scrollVerticallyInternal(int dy) { int childCount = getChildCount(); int itemCount = getItemCount(); if (childCount == 0){ return 0; } final View topView = getChildAt(0); final View bottomView = getChildAt(childCount - 1); //, int viewSpan = getDecoratedBottom(bottomView) - getDecoratedTop(topView); if (viewSpan <= getHeight()) { return 0; } int delta = 0; // if (dy < 0){ View firstView = getChildAt(0); int firstViewAdapterPos = getPosition(firstView); if (firstViewAdapterPos > 0){ // delta = dy; } else { // int viewTop = getDecoratedTop(firstView); delta = Math.max(viewTop, dy); } } else if (dy > 0){ // View lastView = getChildAt(childCount - 1); int lastViewAdapterPos = getPosition(lastView); if (lastViewAdapterPos < itemCount - 1){ // delta = dy; } else { // int viewBottom = getDecoratedBottom(lastView); int parentBottom = getHeight(); delta = Math.min(viewBottom - parentBottom, dy); } } return delta; }
private SparseArray<View> viewCache = new SparseArray<>(); @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { detachAndScrapAttachedViews(recycler); fill(recycler); } private void fill(RecyclerView.Recycler recycler) { View anchorView = getAnchorView(); viewCache.clear(); // ... for (int i = 0, cnt = getChildCount(); i < cnt; i++) { View view = getChildAt(i); int pos = getPosition(view); viewCache.put(pos, view); } //... for (int i = 0; i < viewCache.size(); i++) { detachView(viewCache.valueAt(i)); } fillUp(anchorView, recycler); fillDown(anchorView, recycler); // , // , // for (int i=0; i < viewCache.size(); i++) { recycler.recycleView(viewCache.valueAt(i)); } } private void fillUp(@Nullable View anchorView, RecyclerView.Recycler recycler) { int anchorPos = 0; int anchorTop = 0; if (anchorView != null){ anchorPos = getPosition(anchorView); anchorTop = getDecoratedTop(anchorView); } boolean fillUp = true; int pos = anchorPos - 1; int viewBottom = anchorTop; // int viewHeight = (int) (getHeight() * VIEW_HEIGHT_PERCENT); final int widthSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY); final int heightSpec = View.MeasureSpec.makeMeasureSpec(viewHeight, View.MeasureSpec.EXACTLY); while (fillUp && pos >= 0){ View view = viewCache.get(pos); // if (view == null){ // - recycler , view = recycler.getViewForPosition(pos); addView(view, 0); measureChildWithDecorationsAndMargin(view, widthSpec, heightSpec); int decoratedMeasuredWidth = getDecoratedMeasuredWidth(view); layoutDecorated(view, 0, viewBottom - viewHeight, decoratedMeasuredWidth, viewBottom); } else { // - // measure/layout . attachView(view); viewCache.remove(pos); } viewBottom = getDecoratedTop(view); fillUp = (viewBottom > 0); pos--; } } private void fillDown(@Nullable View anchorView, RecyclerView.Recycler recycler) { int anchorPos = 0; int anchorTop = 0; if (anchorView != null){ anchorPos = getPosition(anchorView); anchorTop = getDecoratedTop(anchorView); } int pos = anchorPos; boolean fillDown = true; int height = getHeight(); int viewTop = anchorTop; int itemCount = getItemCount(); int viewHeight = (int) (getHeight() * VIEW_HEIGHT_PERCENT); final int widthSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY); final int heightSpec = View.MeasureSpec.makeMeasureSpec(viewHeight, View.MeasureSpec.EXACTLY); while (fillDown && pos < itemCount){ View view = viewCache.get(pos); if (view == null){ view = recycler.getViewForPosition(pos); addView(view); measureChildWithDecorationsAndMargin(view, widthSpec, heightSpec); int decoratedMeasuredWidth = getDecoratedMeasuredWidth(view); layoutDecorated(view, 0, viewTop, decoratedMeasuredWidth, viewTop + viewHeight); } else { attachView(view); viewCache.remove(pos); } viewTop = getDecoratedBottom(view); fillDown = viewTop <= height; pos++; } } // private View getAnchorView() { int childCount = getChildCount(); HashMap<Integer, View> viewsOnScreen = new HashMap<>(); Rect mainRect = new Rect(0, 0, getWidth(), getHeight()); for (int i = 0; i < childCount; i++) { View view = getChildAt(i); int top = getDecoratedTop(view); int bottom = getDecoratedBottom(view); int left = getDecoratedLeft(view); int right = getDecoratedRight(view); Rect viewRect = new Rect(left, top, right, bottom); boolean intersect = viewRect.intersect(mainRect); if (intersect){ int square = viewRect.width() * viewRect.height(); viewsOnScreen.put(square, view); } } if (viewsOnScreen.isEmpty()){ return null; } Integer maxSquare = null; for (Integer square : viewsOnScreen.keySet()) { if (maxSquare == null){ maxSquare = square; } else { maxSquare = Math.max(maxSquare, square); } } return viewsOnScreen.get(maxSquare); } @Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { int delta = scrollVerticallyInternal(dy); offsetChildrenVertical(-delta); fill(recycler); return delta; }
private void updateViewScale() { int childCount = getChildCount(); int height = getHeight(); int thresholdPx = (int) (height * SCALE_THRESHOLD_PERCENT); // SCALE_THRESHOLD_PERCENT = 0.66f or 2/3 for (int i = 0; i < childCount; i++) { float scale = 1f; View view = getChildAt(i); int viewTop = getDecoratedTop(view); if (viewTop >= thresholdPx){ int delta = viewTop - thresholdPx; scale = (height - delta) / (float)height; scale = Math.max(scale, 0); } view.setPivotX(view.getHeight()/2); view.setPivotY(view.getHeight() / -2); view.setScaleX(scale); view.setScaleY(scale); } }
recyclerView.setChildDrawingOrderCallback(new RecyclerView.ChildDrawingOrderCallback() { @Override public int onGetChildDrawingOrder(int childCount, int i) { return childCount - i - 1; } });
public enum Orientation {VERTICAL, HORIZONTAL} private Orientation orientation = Orientation.VERTICAL; private int mAnchorPos; public void setOrientation(Orientation orientation) { View anchorView = getAnchorView(); mAnchorPos = anchorView != null ? getPosition(anchorView) : 0; if (orientation != null){ this.orientation = orientation; } requestLayout(); } private void fill(RecyclerView.Recycler recycler) { View anchorView = getAnchorView(); viewCache.clear(); for (int i = 0, cnt = getChildCount(); i < cnt; i++) { View view = getChildAt(i); int pos = getPosition(view); viewCache.put(pos, view); } for (int i = 0; i < viewCache.size(); i++) { detachView(viewCache.valueAt(i)); } switch (orientation) { case VERTICAL: fillUp(anchorView, recycler); fillDown(anchorView, recycler); break; case HORIZONTAL: fillLeft(anchorView, recycler); fillRight(anchorView, recycler); break; } // , // , // for (int i=0; i < viewCache.size(); i++) { recycler.recycleView(viewCache.valueAt(i)); } updateViewScale(); } @Override public boolean canScrollVertically() { return orientation == Orientation.VERTICAL; } @Override public boolean canScrollHorizontally() { return orientation == Orientation.HORIZONTAL; }
public void openItem(int pos) { if (orientation == Orientation.VERTICAL){ View viewToOpen = null; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View view = getChildAt(i); int position = getPosition(view); if (position == pos){ viewToOpen = view; } } if (viewToOpen != null){ openView(viewToOpen); } } }
private void openView(final View viewToAnimate) { final ArrayList<ViewAnimationInfo> animationInfos = new ArrayList<>(); int childCount = getChildCount(); int animatedPos = getPosition(viewToAnimate); for (int i = 0; i < childCount; i++) { View view = getChildAt(i); int pos = getPosition(view); int posDelta = pos - animatedPos; final ViewAnimationInfo viewAnimationInfo = new ViewAnimationInfo(); viewAnimationInfo.startTop = getDecoratedTop(view); viewAnimationInfo.startBottom = getDecoratedBottom(view); viewAnimationInfo.finishTop = getHeight() * posDelta; viewAnimationInfo.finishBottom = getHeight() * posDelta + getHeight(); viewAnimationInfo.view = view; animationInfos.add(viewAnimationInfo); } ValueAnimator animator = ValueAnimator.ofFloat(0, 1); animator.setDuration(TRANSITION_DURATION_MS); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float animationProgress = (float) animation.getAnimatedValue(); for (ViewAnimationInfo animationInfo : animationInfos) { int top = (int) (animationInfo.startTop + animationProgress * (animationInfo.finishTop - animationInfo.startTop)); int bottom = (int) (animationInfo.startBottom + animationProgress * (animationInfo.finishBottom - animationInfo.startBottom)); layoutDecorated(animationInfo.view, 0, top, getWidth(), bottom); } updateViewScale(); } }); animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) {} @Override public void onAnimationEnd(Animator animation) { setOrientation(Orientation.HORIZONTAL); } @Override public void onAnimationCancel(Animator animation) {} @Override public void onAnimationRepeat(Animator animation) {} }); animator.start(); }
private static class ViewAnimationInfo { int startTop; int startBottom; int finishTop; int finishBottom; View view; }
Source: https://habr.com/ru/post/267497/
All Articles