浏览代码

[공통][New] CustomSnapHelper 테스트 중
- 잘 작동한다. 긴 스크롤 시 끊기는 현상이 있음.

hyodong.min 7 年之前
父节点
当前提交
3f7c79bb3f
共有 1 个文件被更改,包括 280 次插入2 次删除
  1. 280 2
      app/src/main/java/kr/co/zumo/app/lifeplus/view/custom/main/banner/MainBannerView.java

+ 280 - 2
app/src/main/java/kr/co/zumo/app/lifeplus/view/custom/main/banner/MainBannerView.java

@@ -1,13 +1,23 @@
 package kr.co.zumo.app.lifeplus.view.custom.main.banner;
 
 import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.constraint.ConstraintLayout;
 import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.LinearSmoothScroller;
 import android.support.v7.widget.LinearSnapHelper;
+import android.support.v7.widget.OrientationHelper;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.SnapHelper;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Scroller;
 
 import java.util.List;
 
@@ -45,12 +55,280 @@ public class MainBannerView extends ConstraintLayout {
   public void init(Context context, List<TextImageBean> textImageBeanList, int itemCount) {
     LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     inflater.inflate(R.layout.main_banner_view, this);
-    SnapHelper snapHelper = new LinearSnapHelper();
+    SnapHelper snapHelper = new Snapper(800);
     recyclerView = (RecyclerView) findViewById(R.id.recycler_view_main_banner_view);
+    recyclerView.setNestedScrollingEnabled(false);
     recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
     mainBannerAdapter = new MainBannerAdapter(context, inflater, textImageBeanList, itemCount);
-    snapHelper.attachToRecyclerView(recyclerView );
+    snapHelper.attachToRecyclerView(recyclerView);
     recyclerView.setAdapter(mainBannerAdapter);
+
   }
 
+  public class Snapper extends LinearSnapHelper {
+
+    static final float MILLISECONDS_PER_INCH = 200f;
+
+    private static final float INVALID_DISTANCE = 1f;
+    @Nullable
+    private OrientationHelper mVerticalHelper;
+    @Nullable
+    private OrientationHelper mHorizontalHelper;
+
+    private int minVelocity = 200;
+
+    public Snapper(int minVelocity) {
+      if (minVelocity > this.minVelocity) {
+        this.minVelocity = minVelocity;
+      }
+    }
+
+    @Override
+    public boolean onFling(int velocityX, int velocityY) {
+      Log.w("APP# Snapper | onFling", "|" + "velociyX: " + velocityX);
+      Scroller mGravityScroller = new Scroller(recyclerView.getContext(), new DecelerateInterpolator());
+      mGravityScroller.fling(0, 0, velocityX, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+//      Log.w("APP# MainBannerView | onFling", "| " + mGravityScroller.getFinalX());
+//      boolean result = super.onFling(velocityX, velocityY);
+//      Log.w("APP# Snapper | onFling", "|" + "result: " + result);
+      RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+      if (layoutManager == null) {
+        return false;
+      }
+      RecyclerView.Adapter adapter = recyclerView.getAdapter();
+      if (adapter == null) {
+        return false;
+      }
+      int minFlingVelocity = 0;
+      return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
+        && snapFromFling2(layoutManager, velocityX, velocityY);
+    }
+
+    boolean snapFromFling2(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
+      if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
+        return false;
+      }
+
+      RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);
+      if (smoothScroller == null) {
+        return false;
+      }
+
+      int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
+      Log.w("APP# Snapper | snapFromFling2", "|" + "targetPosition: " + targetPosition);
+      if (targetPosition == RecyclerView.NO_POSITION) {
+        return false;
+      }
+
+      smoothScroller.setTargetPosition(targetPosition);
+      layoutManager.startSmoothScroll(smoothScroller);
+      return true;
+    }
+
+    @Override
+    public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
+      if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
+        return RecyclerView.NO_POSITION;
+      }
+
+      final int itemCount = layoutManager.getItemCount();
+      if (itemCount == 0) {
+        return RecyclerView.NO_POSITION;
+      }
+
+      final View currentView = findSnapView(layoutManager);
+      if (currentView == null) {
+        return RecyclerView.NO_POSITION;
+      }
+
+      final int currentPosition = layoutManager.getPosition(currentView);
+      if (currentPosition == RecyclerView.NO_POSITION) {
+        return RecyclerView.NO_POSITION;
+      }
+
+      RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider =
+        (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager;
+      // deltaJumps sign comes from the velocity which may not match the order of children in
+      // the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to
+      // get the direction.
+      PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1);
+      if (vectorForEnd == null) {
+        // cannot get a vector for the given position.
+        return RecyclerView.NO_POSITION;
+      }
+
+      int vDeltaJump, hDeltaJump;
+      if (layoutManager.canScrollHorizontally()) {
+        hDeltaJump = estimateNextPositionDiffForFling(layoutManager,
+          getHorizontalHelper(layoutManager), velocityX, 0);
+        if (vectorForEnd.x < 0) {
+          hDeltaJump = -hDeltaJump;
+        }
+      }
+      else {
+        hDeltaJump = 0;
+      }
+      if (layoutManager.canScrollVertically()) {
+        vDeltaJump = estimateNextPositionDiffForFling(layoutManager,
+          getVerticalHelper(layoutManager), 0, velocityY);
+        if (vectorForEnd.y < 0) {
+          vDeltaJump = -vDeltaJump;
+        }
+      }
+      else {
+        vDeltaJump = 0;
+      }
+
+      int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump;
+      Log.i("APP# Snapper | findTargetSnapPosition", "|" + "deltaJump: " + deltaJump);
+      if (deltaJump == 0) {
+        return RecyclerView.NO_POSITION;
+      }
+
+      int targetPos = currentPosition + deltaJump;
+      if (targetPos < 0) {
+        targetPos = 0;
+      }
+      if (targetPos >= itemCount) {
+        targetPos = itemCount - 1;
+      }
+      return targetPos;
+    }
+
+    /**
+     * Estimates a position to which SnapHelper will try to scroll to in response to a fling.
+     *
+     * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+     *                      {@link RecyclerView}.
+     * @param helper        The {@link OrientationHelper} that is created from the LayoutManager.
+     * @param velocityX     The velocity on the x axis.
+     * @param velocityY     The velocity on the y axis.
+     * @return The diff between the target scroll position and the current position.
+     */
+    private int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager, OrientationHelper helper, int velocityX, int velocityY) {
+      int[] distances = calculateScrollDistance(velocityX, velocityY);
+      float distancePerChild = computeDistancePerChild(layoutManager, helper);
+      if (distancePerChild <= 0) {
+        return 0;
+      }
+      int distance = Math.abs(distances[0]) > Math.abs(distances[1]) ? distances[0] : distances[1];
+
+      int min = 0;
+      // 속도가 minVelocity 보다 크면 기본 1칸 이동
+      if (Math.abs(velocityX) > minVelocity || Math.abs(velocityY) > minVelocity) {
+        min = 1;
+      }
+      int result = Math.round(distance / distancePerChild);
+      if (result == 0 && min > 0) {
+        result = distance > 0 ? min : -min;
+      }
+
+      return result;
+    }
+
+    /**
+     * Computes an average pixel value to pass a single child.
+     * <p>
+     * Returns a negative value if it cannot be calculated.
+     *
+     * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+     *                      {@link RecyclerView}.
+     * @param helper        The relevant {@link OrientationHelper} for the attached
+     *                      {@link RecyclerView.LayoutManager}.
+     * @return A float value that is the average number of pixels needed to scroll by one view in
+     * the relevant direction.
+     */
+    private float computeDistancePerChild(RecyclerView.LayoutManager layoutManager,
+                                          OrientationHelper helper) {
+      View minPosView = null;
+      View maxPosView = null;
+      int minPos = Integer.MAX_VALUE;
+      int maxPos = Integer.MIN_VALUE;
+      int childCount = layoutManager.getChildCount();
+      if (childCount == 0) {
+        return INVALID_DISTANCE;
+      }
+
+      for (int i = 0; i < childCount; i++) {
+        View child = layoutManager.getChildAt(i);
+        final int pos = layoutManager.getPosition(child);
+        if (pos == RecyclerView.NO_POSITION) {
+          continue;
+        }
+        if (pos < minPos) {
+          minPos = pos;
+          minPosView = child;
+        }
+        if (pos > maxPos) {
+          maxPos = pos;
+          maxPosView = child;
+        }
+      }
+      if (minPosView == null || maxPosView == null) {
+        return INVALID_DISTANCE;
+      }
+      int start = Math.min(helper.getDecoratedStart(minPosView),
+        helper.getDecoratedStart(maxPosView));
+      int end = Math.max(helper.getDecoratedEnd(minPosView),
+        helper.getDecoratedEnd(maxPosView));
+      int distance = end - start;
+      if (distance == 0) {
+        return INVALID_DISTANCE;
+      }
+      return 1f * distance / ((maxPos - minPos) + 1);
+    }
+
+    @NonNull
+    private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
+      if (mVerticalHelper == null || mVerticalHelper.getLayoutManager() != layoutManager) {
+        mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
+      }
+      return mVerticalHelper;
+    }
+
+    @NonNull
+    private OrientationHelper getHorizontalHelper(
+      @NonNull RecyclerView.LayoutManager layoutManager) {
+      if (mHorizontalHelper == null || mHorizontalHelper.getLayoutManager() != layoutManager) {
+        mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
+      }
+      return mHorizontalHelper;
+    }
+
+    /**
+     * Creates a scroller to be used in the snapping implementation.
+     *
+     * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+     *                      {@link RecyclerView}.
+     * @return a {@link RecyclerView.SmoothScroller} which will handle the scrolling.
+     */
+    @Nullable
+    protected RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) {
+
+      if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
+        return null;
+      }
+      return new LinearSmoothScroller(recyclerView.getContext()) {
+        @Override
+        protected void onTargetFound(View targetView, RecyclerView.State state, RecyclerView.SmoothScroller.Action action) {
+          if (recyclerView == null) {
+            // The associated RecyclerView has been removed so there is no action to take.
+            return;
+          }
+          int[] snapDistances = calculateDistanceToFinalSnap(recyclerView.getLayoutManager(), targetView);
+          final int dx = snapDistances[0];
+          final int dy = snapDistances[1];
+          final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
+          if (time > 0) {
+            action.update(dx, dy, time, mDecelerateInterpolator);
+          }
+        }
+
+        @Override
+        protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+          return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+        }
+      };
+    }
+  }
 }