我最终提出了与上述内容略有不同的内容。这不是理想的方法,但是对我来说效果很好,并且可能会对其他人有所帮助。我不会接受这个答案,希望其他人会带来更好和更少hacky的东西(并且可能我误解了RecyclerView
实现并缺少一些简单的方法,但是与此同时,这已经足够了用于政府工作!)
实现的基础如下:a中的滚动RecyclerView
有点类似于RecyclerView
和之间的拆分LinearLayoutManager
。我需要处理两种情况:
- 用户浏览该视图。默认行为是
RecyclerView
将弹道传递到内部Scroller
,然后内部执行滚动魔术。这是有问题的,因为这样RecyclerView
通常会将它们固定在不被捕捉的位置。我通过重写RecyclerView
fling()
实现来解决此问题,而不是匆忙地将LinearLayoutManager
其平滑滚动到一个位置。
- 用户以不足的速度抬起手指以启动滚动。在这种情况下不会发生甩动。如果视图不在捕捉位置,我想检测这种情况。我通过重写
onTouchEvent
方法来做到这一点。
的SnappyRecyclerView
:
public final class SnappyRecyclerView extends RecyclerView {
public SnappyRecyclerView(Context context) {
super(context);
}
public SnappyRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SnappyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean fling(int velocityX, int velocityY) {
final LayoutManager lm = getLayoutManager();
if (lm instanceof ISnappyLayoutManager) {
super.smoothScrollToPosition(((ISnappyLayoutManager) getLayoutManager())
.getPositionForVelocity(velocityX, velocityY));
return true;
}
return super.fling(velocityX, velocityY);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
final boolean ret = super.onTouchEvent(e);
final LayoutManager lm = getLayoutManager();
if (lm instanceof ISnappyLayoutManager
&& (e.getAction() == MotionEvent.ACTION_UP ||
e.getAction() == MotionEvent.ACTION_CANCEL)
&& getScrollState() == SCROLL_STATE_IDLE) {
smoothScrollToPosition(((ISnappyLayoutManager) lm).getFixScrollPos());
}
return ret;
}
}
灵活的布局管理器的界面:
public interface ISnappyLayoutManager {
int getPositionForVelocity(int velocityX, int velocityY);
int getFixScrollPos();
}
这里是一个示例LayoutManager
子类的LinearLayoutManager
导致一个LayoutManager
与平滑滚动:
public class SnappyLinearLayoutManager extends LinearLayoutManager implements ISnappyLayoutManager {
private static final float INFLEXION = 0.35f;
private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
private static double FRICTION = 0.84;
private double deceleration;
public SnappyLinearLayoutManager(Context context) {
super(context);
calculateDeceleration(context);
}
public SnappyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
calculateDeceleration(context);
}
private void calculateDeceleration(Context context) {
deceleration = SensorManager.GRAVITY_EARTH
* 39.3700787
* context.getResources().getDisplayMetrics().density * 160.0f * FRICTION;
}
@Override
public int getPositionForVelocity(int velocityX, int velocityY) {
if (getChildCount() == 0) {
return 0;
}
if (getOrientation() == HORIZONTAL) {
return calcPosForVelocity(velocityX, getChildAt(0).getLeft(), getChildAt(0).getWidth(),
getPosition(getChildAt(0)));
} else {
return calcPosForVelocity(velocityY, getChildAt(0).getTop(), getChildAt(0).getHeight(),
getPosition(getChildAt(0)));
}
}
private int calcPosForVelocity(int velocity, int scrollPos, int childSize, int currPos) {
final double dist = getSplineFlingDistance(velocity);
final double tempScroll = scrollPos + (velocity > 0 ? dist : -dist);
if (velocity < 0) {
return (int) Math.max(currPos + tempScroll / childSize, 0);
} else {
return (int) (currPos + (tempScroll / childSize) + 1);
}
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, State state, int position) {
final LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
protected int getHorizontalSnapPreference() {
return SNAP_TO_START;
}
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return SnappyLinearLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
private double getSplineFlingDistance(double velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return ViewConfiguration.getScrollFriction() * deceleration
* Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
private double getSplineDeceleration(double velocity) {
return Math.log(INFLEXION * Math.abs(velocity)
/ (ViewConfiguration.getScrollFriction() * deceleration));
}
@Override
public int getFixScrollPos() {
if (this.getChildCount() == 0) {
return 0;
}
final View child = getChildAt(0);
final int childPos = getPosition(child);
if (getOrientation() == HORIZONTAL
&& Math.abs(child.getLeft()) > child.getMeasuredWidth() / 2) {
return childPos + 1;
} else if (getOrientation() == VERTICAL
&& Math.abs(child.getTop()) > child.getMeasuredWidth() / 2) {
return childPos + 1;
}
return childPos;
}
}