Prechádzať zdrojové kódy

[메인][New] 가로/세로 스냅퍼 수정, 마지막 뷰 세부 설정

hyodong.min 7 rokov pred
rodič
commit
66d8980443

+ 7 - 14
app/src/main/java/kr/co/zumo/app/lifeplus/view/screen/main/MainCategorySnapper.java

@@ -33,7 +33,7 @@ public class MainCategorySnapper extends RecyclerView.OnFlingListener {
   private int minVelocity = 200;
 
   private static final int TIME_MAX = 990;
-  private static final int TIME_MIN = 200;
+  private static final int TIME_MIN = 250;
 
   static final float MILLISECONDS_PER_INCH = 100f;
 
@@ -48,8 +48,6 @@ public class MainCategorySnapper extends RecyclerView.OnFlingListener {
   @Nullable
   private OrientationHelper mHorizontalHelper;
 
-  private int targetChildPosition = 0;
-
   // Handles the snap on scroll case.
   private final RecyclerView.OnScrollListener mScrollListener =
     new RecyclerView.OnScrollListener() {
@@ -192,8 +190,6 @@ public class MainCategorySnapper extends RecyclerView.OnFlingListener {
       return false;
     }
 
-    targetChildPosition = targetPosition;
-
     smoothScroller.setTargetPosition(targetPosition);
     layoutManager.startSmoothScroll(smoothScroller);
     return true;
@@ -292,7 +288,7 @@ public class MainCategorySnapper extends RecyclerView.OnFlingListener {
            */
           OrientationHelper helper = getOrientationHelper(layoutManager);
           if (null != helper) {
-            offset = (layoutManager.getWidth() - helper.getDecoratedMeasurement(targetView));
+            offset = (helper.getEnd() - helper.getDecoratedMeasurement(targetView));
           }
 //          Log.e("APP#  MainCategorySnapper | onTargetFound", "|" + "layoutManager.getWidth(): " + layoutManager.getWidth());
 //          Log.e("APP#  MainCategorySnapper | onTargetFound", "|" + "helper.getDecoratedMeasurement(targetView): " + helper.getDecoratedMeasurement(targetView));
@@ -426,6 +422,7 @@ public class MainCategorySnapper extends RecyclerView.OnFlingListener {
       top = 0;
     }
     int absClosest = Integer.MAX_VALUE;
+    int lastIndex = mRecyclerView.getAdapter().getItemCount() - 1;
 
     int secondItemPositionMax = 0;
 
@@ -450,16 +447,14 @@ public class MainCategorySnapper extends RecyclerView.OnFlingListener {
 
       /**
        * 마지막이 기준 위치보다 왼쪽에 있으면 녀석을 타겟으로 본다.
-       * - 마지막 녀석은 크기가 작아서 그걸로 확인
        */
-      if (secondItemPositionMax > childSize) {
-        // padding start + childSize > lastChild.x
+      if (mRecyclerView.getChildAdapterPosition(child) == lastIndex) {
 //        Log.w("APP# MainCategorySnapper | findTopView", "|" + "padding: " + helper.getStartAfterPadding());
 //        Log.w("APP# MainCategorySnapper | findTopView", "|" + "secondItemPositionMax: " + secondItemPositionMax);
 //        Log.w("APP# MainCategorySnapper | findTopView", "|" + "secondItemPosition: " + secondItemPosition);
 //        Log.w("APP# MainCategorySnapper | findTopView", "|" + "snapOffset: " + snapOffset);
 //        Log.w("APP# MainCategorySnapper | findTopView", "|" + "child.x: " + child.getX());
-        if (secondItemPositionMax - (childSize >> 1) > child.getX()) {
+        if (secondItemPositionMax /*- (childSize >> 1)*/ > child.getX()) {
           closestChild = child;
         }
       }
@@ -540,8 +535,7 @@ public class MainCategorySnapper extends RecyclerView.OnFlingListener {
       return RecyclerView.NO_POSITION;
     }
 
-    ScrollVectorProvider vectorProvider =
-      (ScrollVectorProvider) layoutManager;
+    ScrollVectorProvider vectorProvider = (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.
@@ -600,8 +594,7 @@ public class MainCategorySnapper extends RecyclerView.OnFlingListener {
    * @return A float value that is the average number of pixels needed to scroll by one view in
    * the relevant direction.
    */
-  private float computeDistancePerChild(LayoutManager layoutManager,
-                                        OrientationHelper helper) {
+  private float computeDistancePerChild(LayoutManager layoutManager, OrientationHelper helper) {
     View minPosView = null;
     View maxPosView = null;
     int minPos = Integer.MAX_VALUE;

+ 111 - 32
app/src/main/java/kr/co/zumo/app/lifeplus/view/screen/main/MainContentsSnapper.java

@@ -3,6 +3,7 @@ package kr.co.zumo.app.lifeplus.view.screen.main;
 import android.graphics.PointF;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.LinearSmoothScroller;
 import android.support.v7.widget.OrientationHelper;
 import android.support.v7.widget.RecyclerView;
@@ -15,6 +16,8 @@ import android.view.View;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.Scroller;
 
+import kr.co.zumo.app.lifeplus.util.ResourceUtil;
+
 /**
  * MainContentsSnapper
  * <pre>
@@ -31,7 +34,7 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
   private int minVelocity = 200;
 
   private static final int TIME_MAX = 990;
-  private static final int TIME_MIN = 200;
+  private static final int TIME_MIN = 250;
 
   static final float MILLISECONDS_PER_INCH = 100f;
 
@@ -211,6 +214,13 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
       return;
     }
 
+//    int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);
+//    snapDistance[0] -= snapOffset;  // 마지막 뷰일 경우 snapOffset 을 적용하면 부자연스럽게 스크롤 됨
+//    snapDistance[1] -= snapOffset;
+//    if (snapDistance[0] != 0 || snapDistance[1] != 0) {
+//      mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1], new DecelerateInterpolator());
+//    }
+
     /**
      * 현재 스크롤 포지션에 가장 가까운 뷰로 스크롤 시킨다.
      * - recyclerView 의 스크롤을 이용하지않고 직접 스크롤시킨다.
@@ -230,7 +240,7 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
   /**
    * Creates a scroller to be used in the snapping implementation.
    *
-   * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+   * @param layoutManager The {@link LayoutManager} associated with the attached
    *                      {@link RecyclerView}.
    * @return a {@link SmoothScroller} which will handle the scrolling.
    */
@@ -242,7 +252,7 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
   /**
    * Creates a scroller to be used in the snapping implementation.
    *
-   * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+   * @param layoutManager The {@link LayoutManager} associated with the attached
    *                      {@link RecyclerView}.
    * @return a {@link LinearSmoothScroller} which will handle the scrolling.
    * @deprecated use {@link #createScroller(LayoutManager)} instead.
@@ -260,9 +270,46 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
           // The associated RecyclerView has been removed so there is no action to take.
           return;
         }
+        int offset = snapOffset;
+        if (mRecyclerView.getChildAdapterPosition(targetView) == (mRecyclerView.getAdapter().getItemCount() - 1)) {
+          /**
+           * 보통 뷰일 때 스크롤 타겟 위치
+           * - 0 + offset
+           *
+           * 마지막 뷰일 때
+           * => offset = screenHeight - 마지막 뷰 사이즈
+           */
+          OrientationHelper helper = getOrientationHelper(layoutManager);
+          if (null != helper) {
+            offset = (helper.getEnd() - helper.getDecoratedMeasurement(targetView));
+          }
+//          Log.e("APP#  MainCategorySnapper | onTargetFound", "|" + "layoutManager.getWidth(): " + layoutManager.getWidth());
+//          Log.e("APP#  MainCategorySnapper | onTargetFound", "|" + "helper.getDecoratedMeasurement(targetView): " + helper.getDecoratedMeasurement(targetView));
+//          Log.e("APP#  MainCategorySnapper | onTargetFound", "|" + "offset: " + offset);
+        }
         int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView);
-        final int dx = snapDistances[0] - snapOffset;
-        final int dy = snapDistances[1] - snapOffset;
+        final int dx;
+        final int dy;
+
+        /**
+         * 방향을 고려해서 offset 적용한다. 항상 적용하면 긴쪽을 기준으로 time 이 계산되어서 이동 거리가 0일 경우 느리게 움직일 수 있다.
+         */
+        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
+        if (null != linearLayoutManager) {
+          if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
+            dx = snapDistances[0] - offset;
+            dy = snapDistances[1];
+          }
+          else {
+            dx = snapDistances[0];
+            dy = snapDistances[1] - offset;
+          }
+        }
+        else {
+          dx = snapDistances[0];
+          dy = snapDistances[1];
+        }
+
         final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
         if (time > 0) {
           int newTime = Math.min(TIME_MAX, Math.max(time, TIME_MIN));
@@ -277,6 +324,16 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
     };
   }
 
+  private OrientationHelper getOrientationHelper(LayoutManager layoutManager) {
+    if (layoutManager.canScrollVertically()) {
+      return getVerticalHelper(layoutManager);
+    }
+    else if (layoutManager.canScrollHorizontally()) {
+      return getHorizontalHelper(layoutManager);
+    }
+    return null;
+  }
+
   /**
    * Override this method to snap to a particular point within the target view or the container
    * view on any axis.
@@ -284,7 +341,7 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
    * This method is called when the {@link SnapHelper} has intercepted a fling and it needs
    * to know the exact distance required to scroll by in order to snap to the target view.
    *
-   * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
+   * @param layoutManager the {@link LayoutManager} associated with the attached
    *                      {@link RecyclerView}
    * @param targetView    the target view that is chosen as the view to snap
    * @return the output coordinates the put the result into. out[0] is the distance
@@ -292,7 +349,7 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
    */
   @SuppressWarnings("WeakerAccess")
   @Nullable
-  public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
+  public int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager, @NonNull View targetView) {
     int[] out = new int[2];
     if (layoutManager.canScrollHorizontally()) {
       out[0] = distanceToTop(layoutManager, targetView, getHorizontalHelper(layoutManager));
@@ -320,18 +377,16 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
    * <p>
    * If this method returns {@code null}, SnapHelper will not snap to any view.
    *
-   * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
+   * @param layoutManager the {@link LayoutManager} associated with the attached
    *                      {@link RecyclerView}
    * @return the target view to which to snap on fling or end of scroll
    */
   @SuppressWarnings("WeakerAccess")
   @Nullable
-  public View findSnapView(RecyclerView.LayoutManager layoutManager) {
-    if (layoutManager.canScrollVertically()) {
-      return findTopView(layoutManager, getVerticalHelper(layoutManager));
-    }
-    else if (layoutManager.canScrollHorizontally()) {
-      return findTopView(layoutManager, getHorizontalHelper(layoutManager));
+  public View findSnapView(LayoutManager layoutManager) {
+    OrientationHelper helper = getOrientationHelper(layoutManager);
+    if (null != helper) {
+      return findTopView(layoutManager, helper);
     }
     return null;
   }
@@ -339,13 +394,13 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
   /**
    * Return the child view that is currently closest to the top of this parent.
    *
-   * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+   * @param layoutManager The {@link LayoutManager} associated with the attached
    *                      {@link RecyclerView}.
    * @param helper        The relevant {@link OrientationHelper} for the attached {@link RecyclerView}.
    * @return the child view that is currently closest to the top of this parent.
    */
   @Nullable
-  private View findTopView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
+  private View findTopView(LayoutManager layoutManager, OrientationHelper helper) {
     int childCount = layoutManager.getChildCount();
     if (childCount == 0) {
       return null;
@@ -360,12 +415,17 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
       top = 0;
     }
     int absClosest = Integer.MAX_VALUE;
+    int dp100 = ResourceUtil.dpToPx(100);
+    int lastIndex = mRecyclerView.getAdapter().getItemCount() - 1;
 
-    for (int i = 0; i < childCount; i++) {
+    int secondItemPositionMax = 0;
+
+    for (int i = 0; i < childCount; ++i) {
       final View child = layoutManager.getChildAt(i);
       int childStart = helper.getDecoratedStart(child);
       int childEnd = helper.getDecoratedEnd(child);
-      int childSize = Math.abs(childEnd - childStart) + snapOffset;
+      int childSize = Math.abs(childEnd - childStart);
+      int secondItemPosition = childSize + snapOffset;
       int childCenter = childStart + (helper.getDecoratedMeasurement(child) >> 2);
       int absDistance = Math.abs(childCenter - (top + (childSize >> 2)));
 
@@ -374,11 +434,32 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
         absClosest = absDistance;
         closestChild = child;
       }
+
+      if (secondItemPositionMax < secondItemPosition) {
+        secondItemPositionMax = secondItemPosition;
+      }
+
+      /**
+       * todo 화면에 2 개 이상 표시될 수 있을 경우 처리 필요
+       */
+
+      /**
+       * 마지막이 -100dp 이상 보여질 경우 타겟으로 설정
+       */
+      if (mRecyclerView.getChildAdapterPosition(child) == lastIndex) {
+//        Log.w("APP# MainContentsSnapper | findTopView", "|" + "secondItemPositionMax: " + secondItemPositionMax);
+//        Log.w("APP# MainContentsSnapper | findTopView", "|" + "secondItemPosition: " + secondItemPosition);
+//        Log.w("APP# MainContentsSnapper | findTopView", "|" + "snapOffset: " + snapOffset);
+//        Log.w("APP# MainContentsSnapper | findTopView", "|" + "child.y: " + child.getY());
+        if (helper.getEnd() - (childSize - dp100) > child.getY()) {
+          closestChild = child;
+        }
+      }
     }
     return closestChild;
   }
 
-  private int distanceToTop(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) {
+  private int distanceToTop(@NonNull LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) {
     /**
      * targetView 의 시작 위치(Top)를 반환한다.
      */
@@ -388,14 +469,14 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
   /**
    * 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
+   * @param layoutManager The {@link 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,
+  private int estimateNextPositionDiffForFling(LayoutManager layoutManager,
                                                OrientationHelper helper, int velocityX, int velocityY) {
     int[] distances = calculateScrollDistance(velocityX, velocityY);
     float distancePerChild = computeDistancePerChild(layoutManager, helper);
@@ -423,16 +504,16 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
   /**
    * Override to provide a particular adapter target position for snapping.
    *
-   * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
+   * @param layoutManager the {@link LayoutManager} associated with the attached
    *                      {@link RecyclerView}
    * @param velocityX     fling velocity on the horizontal axis
    * @param velocityY     fling velocity on the vertical axis
    * @return the target adapter position to you want to snap or {@link RecyclerView#NO_POSITION}
    * if no snapping should happen
    */
-  public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
+  public int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,
                                     int velocityY) {
-    if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
+    if (!(layoutManager instanceof ScrollVectorProvider)) {
       return RecyclerView.NO_POSITION;
     }
 
@@ -451,8 +532,7 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
       return RecyclerView.NO_POSITION;
     }
 
-    RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider =
-      (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager;
+    ScrollVectorProvider vectorProvider = (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.
@@ -504,15 +584,14 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
    * <p>
    * Returns a negative value if it cannot be calculated.
    *
-   * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+   * @param layoutManager The {@link LayoutManager} associated with the attached
    *                      {@link RecyclerView}.
    * @param helper        The relevant {@link OrientationHelper} for the attached
-   *                      {@link RecyclerView.LayoutManager}.
+   *                      {@link 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) {
+  private float computeDistancePerChild(LayoutManager layoutManager, OrientationHelper helper) {
     View minPosView = null;
     View maxPosView = null;
     int minPos = Integer.MAX_VALUE;
@@ -552,7 +631,7 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
   }
 
   @NonNull
-  private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
+  private OrientationHelper getVerticalHelper(@NonNull LayoutManager layoutManager) {
     if (mVerticalHelper == null || mVerticalHelper.getLayoutManager() != layoutManager) {
       mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
     }
@@ -561,7 +640,7 @@ public class MainContentsSnapper extends RecyclerView.OnFlingListener {
 
   @NonNull
   private OrientationHelper getHorizontalHelper(
-    @NonNull RecyclerView.LayoutManager layoutManager) {
+    @NonNull LayoutManager layoutManager) {
     if (mHorizontalHelper == null || mHorizontalHelper.getLayoutManager() != layoutManager) {
       mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
     }