update
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2017 GcsSloop
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Last modified 2017-09-20 16:32:43
|
||||
*
|
||||
* GitHub: https://github.com/GcsSloop
|
||||
* WeiBo: http://weibo.com/GcsSloop
|
||||
* WebSite: http://www.gcssloop.com
|
||||
*/
|
||||
|
||||
package com.shayu.onetoone.widget;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* 作用:Pager配置
|
||||
* 作者:GcsSloop
|
||||
* 摘要:主要用于Log的显示与关闭
|
||||
*/
|
||||
public class PagerConfig {
|
||||
private static final String TAG = "PagerGrid";
|
||||
private static boolean sShowLog = false;
|
||||
private static int sFlingThreshold = 1000; // Fling 阀值,滚动速度超过该阀值才会触发滚动
|
||||
private static float sMillisecondsPreInch = 60f; // 每一个英寸滚动需要的微秒数,数值越大,速度越慢
|
||||
|
||||
/**
|
||||
* 判断是否输出日志
|
||||
*
|
||||
* @return true 输出,false 不输出
|
||||
*/
|
||||
public static boolean isShowLog() {
|
||||
return sShowLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否输出日志
|
||||
*
|
||||
* @param showLog 是否输出
|
||||
*/
|
||||
public static void setShowLog(boolean showLog) {
|
||||
sShowLog = showLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前滚动速度阀值
|
||||
*
|
||||
* @return 当前滚动速度阀值
|
||||
*/
|
||||
public static int getFlingThreshold() {
|
||||
return sFlingThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前滚动速度阀值
|
||||
*
|
||||
* @param flingThreshold 滚动速度阀值
|
||||
*/
|
||||
public static void setFlingThreshold(int flingThreshold) {
|
||||
sFlingThreshold = flingThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取滚动速度 英寸/微秒
|
||||
*
|
||||
* @return 英寸滚动速度
|
||||
*/
|
||||
public static float getMillisecondsPreInch() {
|
||||
return sMillisecondsPreInch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置像素滚动速度 英寸/微秒
|
||||
*
|
||||
* @param millisecondsPreInch 英寸滚动速度
|
||||
*/
|
||||
public static void setMillisecondsPreInch(float millisecondsPreInch) {
|
||||
sMillisecondsPreInch = millisecondsPreInch;
|
||||
}
|
||||
|
||||
//--- 日志 -------------------------------------------------------------------------------------
|
||||
|
||||
public static void Logi(String msg) {
|
||||
if (!PagerConfig.isShowLog()) return;
|
||||
Log.i(TAG, msg);
|
||||
}
|
||||
|
||||
public static void Loge(String msg) {
|
||||
if (!PagerConfig.isShowLog()) return;
|
||||
Log.e(TAG, msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,908 @@
|
||||
/*
|
||||
* Copyright 2017 GcsSloop
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Last modified 2017-09-20 16:32:43
|
||||
*
|
||||
* GitHub: https://github.com/GcsSloop
|
||||
* WeiBo: http://weibo.com/GcsSloop
|
||||
* WebSite: http://www.gcssloop.com
|
||||
*/
|
||||
|
||||
package com.shayu.onetoone.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static android.view.View.MeasureSpec.EXACTLY;
|
||||
import static com.shayu.onetoone.widget.PagerConfig.Loge;
|
||||
import static com.shayu.onetoone.widget.PagerConfig.Logi;
|
||||
import static com.yunbao.common.views.weight.VerticalViewPager.SCROLL_STATE_IDLE;
|
||||
|
||||
|
||||
/**
|
||||
* 作用:分页的网格布局管理器
|
||||
* 作者:GcsSloop
|
||||
* 摘要:
|
||||
* 1. 网格布局
|
||||
* 2. 支持水平分页和垂直分页
|
||||
* 3. 杜绝高内存占用
|
||||
*/
|
||||
public class PagerGridLayoutManager extends RecyclerView.LayoutManager
|
||||
implements RecyclerView.SmoothScroller.ScrollVectorProvider {
|
||||
private static final String TAG = PagerGridLayoutManager.class.getSimpleName();
|
||||
|
||||
public static final int VERTICAL = 0; // 垂直滚动
|
||||
public static final int HORIZONTAL = 1; // 水平滚动
|
||||
|
||||
@IntDef({VERTICAL, HORIZONTAL})
|
||||
public @interface OrientationType {} // 滚动类型
|
||||
|
||||
@OrientationType
|
||||
private int mOrientation; // 默认水平滚动
|
||||
|
||||
private int mOffsetX = 0; // 水平滚动距离(偏移量)
|
||||
private int mOffsetY = 0; // 垂直滚动距离(偏移量)
|
||||
|
||||
private int mRows; // 行数
|
||||
private int mColumns; // 列数
|
||||
private int mOnePageSize; // 一页的条目数量
|
||||
|
||||
private SparseArray<Rect> mItemFrames; // 条目的显示区域
|
||||
|
||||
private int mItemWidth = 0; // 条目宽度
|
||||
private int mItemHeight = 0; // 条目高度
|
||||
|
||||
private int mWidthUsed = 0; // 已经使用空间,用于测量View
|
||||
private int mHeightUsed = 0; // 已经使用空间,用于测量View
|
||||
|
||||
private int mMaxScrollX; // 最大允许滑动的宽度
|
||||
private int mMaxScrollY; // 最大允许滑动的高度
|
||||
private int mScrollState = SCROLL_STATE_IDLE; // 滚动状态
|
||||
|
||||
private boolean mAllowContinuousScroll = true; // 是否允许连续滚动
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param rows 行数
|
||||
* @param columns 列数
|
||||
* @param orientation 方向
|
||||
*/
|
||||
public PagerGridLayoutManager(@IntRange(from = 1, to = 100) int rows,
|
||||
@IntRange(from = 1, to = 100) int columns,
|
||||
@OrientationType int orientation) {
|
||||
mItemFrames = new SparseArray<>();
|
||||
mOrientation = orientation;
|
||||
mRows = rows;
|
||||
mColumns = columns;
|
||||
mOnePageSize = mRows * mColumns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow(RecyclerView view) {
|
||||
super.onAttachedToWindow(view);
|
||||
mRecyclerView = view;
|
||||
}
|
||||
|
||||
//--- 处理布局 ----------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 布局子View
|
||||
*
|
||||
* @param recycler Recycler
|
||||
* @param state State
|
||||
*/
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
Logi("Item onLayoutChildren");
|
||||
Logi("Item onLayoutChildren isPreLayout = " + state.isPreLayout());
|
||||
Logi("Item onLayoutChildren isMeasuring = " + state.isMeasuring());
|
||||
Loge("Item onLayoutChildren state = " + state);
|
||||
|
||||
// 如果是 preLayout 则不重新布局
|
||||
if (state.isPreLayout() || !state.didStructureChange()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getItemCount() == 0) {
|
||||
removeAndRecycleAllViews(recycler);
|
||||
// 页面变化回调
|
||||
setPageCount(0);
|
||||
setPageIndex(0, false);
|
||||
return;
|
||||
} else {
|
||||
setPageCount(getTotalPageCount());
|
||||
setPageIndex(getPageIndexByOffset(), false);
|
||||
}
|
||||
|
||||
// 计算页面数量
|
||||
int mPageCount = getItemCount() / mOnePageSize;
|
||||
if (getItemCount() % mOnePageSize != 0) {
|
||||
mPageCount++;
|
||||
}
|
||||
|
||||
// 计算可以滚动的最大数值,并对滚动距离进行修正
|
||||
if (canScrollHorizontally()) {
|
||||
mMaxScrollX = (mPageCount - 1) * getUsableWidth();
|
||||
mMaxScrollY = 0;
|
||||
if (mOffsetX > mMaxScrollX) {
|
||||
mOffsetX = mMaxScrollX;
|
||||
}
|
||||
} else {
|
||||
mMaxScrollX = 0;
|
||||
mMaxScrollY = (mPageCount - 1) * getUsableHeight();
|
||||
if (mOffsetY > mMaxScrollY) {
|
||||
mOffsetY = mMaxScrollY;
|
||||
}
|
||||
}
|
||||
|
||||
// 接口回调
|
||||
// setPageCount(mPageCount);
|
||||
// setPageIndex(mCurrentPageIndex, false);
|
||||
|
||||
Logi("count = " + getItemCount());
|
||||
|
||||
if (mItemWidth <= 0) {
|
||||
mItemWidth = getUsableWidth() / mColumns;
|
||||
}
|
||||
if (mItemHeight <= 0) {
|
||||
mItemHeight = getUsableHeight() / mRows;
|
||||
}
|
||||
|
||||
mWidthUsed = getUsableWidth() - mItemWidth;
|
||||
mHeightUsed = getUsableHeight() - mItemHeight;
|
||||
|
||||
// 预存储两页的View显示区域
|
||||
for (int i = 0; i < mOnePageSize * 2; i++) {
|
||||
getItemFrameByPosition(i);
|
||||
}
|
||||
|
||||
if (mOffsetX == 0 && mOffsetY == 0) {
|
||||
// 预存储View
|
||||
for (int i = 0; i < mOnePageSize; i++) {
|
||||
if (i >= getItemCount()) break; // 防止数据过少时导致数组越界异常
|
||||
View view = recycler.getViewForPosition(i);
|
||||
addView(view);
|
||||
measureChildWithMargins(view, mWidthUsed, mHeightUsed);
|
||||
}
|
||||
}
|
||||
|
||||
// 回收和填充布局
|
||||
recycleAndFillItems(recycler, state, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局结束
|
||||
*
|
||||
* @param state State
|
||||
*/
|
||||
@Override
|
||||
public void onLayoutCompleted(RecyclerView.State state) {
|
||||
super.onLayoutCompleted(state);
|
||||
if (state.isPreLayout()) return;
|
||||
// 页面状态回调
|
||||
setPageCount(getTotalPageCount());
|
||||
setPageIndex(getPageIndexByOffset(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收和填充布局
|
||||
*
|
||||
* @param recycler Recycler
|
||||
* @param state State
|
||||
* @param isStart 是否从头开始,用于控制View遍历方向,true 为从头到尾,false 为从尾到头
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private void recycleAndFillItems(RecyclerView.Recycler recycler, RecyclerView.State state,
|
||||
boolean isStart) {
|
||||
if (state.isPreLayout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logi("mOffsetX = " + mOffsetX);
|
||||
Logi("mOffsetY = " + mOffsetY);
|
||||
|
||||
// 计算显示区域区前后多存储一列或则一行
|
||||
Rect displayRect = new Rect(mOffsetX - mItemWidth, mOffsetY - mItemHeight,
|
||||
getUsableWidth() + mOffsetX + mItemWidth, getUsableHeight() + mOffsetY + mItemHeight);
|
||||
// 对显显示区域进行修正(计算当前显示区域和最大显示区域对交集)
|
||||
displayRect.intersect(0, 0, mMaxScrollX + getUsableWidth(), mMaxScrollY + getUsableHeight());
|
||||
Loge("displayRect = " + displayRect.toString());
|
||||
|
||||
int startPos = 0; // 获取第一个条目的Pos
|
||||
int pageIndex = getPageIndexByOffset();
|
||||
startPos = pageIndex * mOnePageSize;
|
||||
Logi("startPos = " + startPos);
|
||||
startPos = startPos - mOnePageSize * 2;
|
||||
if (startPos < 0) {
|
||||
startPos = 0;
|
||||
}
|
||||
int stopPos = startPos + mOnePageSize * 4;
|
||||
if (stopPos > getItemCount()) {
|
||||
stopPos = getItemCount();
|
||||
}
|
||||
|
||||
Loge("startPos = " + startPos);
|
||||
Loge("stopPos = " + stopPos);
|
||||
|
||||
detachAndScrapAttachedViews(recycler); // 移除所有View
|
||||
|
||||
if (isStart) {
|
||||
for (int i = startPos; i < stopPos; i++) {
|
||||
addOrRemove(recycler, displayRect, i);
|
||||
}
|
||||
} else {
|
||||
for (int i = stopPos - 1; i >= startPos; i--) {
|
||||
addOrRemove(recycler, displayRect, i);
|
||||
}
|
||||
}
|
||||
Loge("child count = " + getChildCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加或者移除条目
|
||||
*
|
||||
* @param recycler RecyclerView
|
||||
* @param displayRect 显示区域
|
||||
* @param i 条目下标
|
||||
*/
|
||||
private void addOrRemove(RecyclerView.Recycler recycler, Rect displayRect, int i) {
|
||||
View child = recycler.getViewForPosition(i);
|
||||
Rect rect = getItemFrameByPosition(i);
|
||||
if (!Rect.intersects(displayRect, rect)) {
|
||||
removeAndRecycleView(child, recycler); // 回收入暂存区
|
||||
} else {
|
||||
addView(child);
|
||||
measureChildWithMargins(child, mWidthUsed, mHeightUsed);
|
||||
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
|
||||
layoutDecorated(child,
|
||||
rect.left - mOffsetX + lp.leftMargin + getPaddingLeft(),
|
||||
rect.top - mOffsetY + lp.topMargin + getPaddingTop(),
|
||||
rect.right - mOffsetX - lp.rightMargin + getPaddingLeft(),
|
||||
rect.bottom - mOffsetY - lp.bottomMargin + getPaddingTop());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--- 处理滚动 ----------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 水平滚动
|
||||
*
|
||||
* @param dx 滚动距离
|
||||
* @param recycler 回收器
|
||||
* @param state 滚动状态
|
||||
* @return 实际滚动距离
|
||||
*/
|
||||
@Override
|
||||
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State
|
||||
state) {
|
||||
int newX = mOffsetX + dx;
|
||||
int result = dx;
|
||||
if (newX > mMaxScrollX) {
|
||||
result = mMaxScrollX - mOffsetX;
|
||||
} else if (newX < 0) {
|
||||
result = 0 - mOffsetX;
|
||||
}
|
||||
mOffsetX += result;
|
||||
setPageIndex(getPageIndexByOffset(), true);
|
||||
offsetChildrenHorizontal(-result);
|
||||
if (result > 0) {
|
||||
recycleAndFillItems(recycler, state, true);
|
||||
} else {
|
||||
recycleAndFillItems(recycler, state, false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 垂直滚动
|
||||
*
|
||||
* @param dy 滚动距离
|
||||
* @param recycler 回收器
|
||||
* @param state 滚动状态
|
||||
* @return 实际滚动距离
|
||||
*/
|
||||
@Override
|
||||
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State
|
||||
state) {
|
||||
int newY = mOffsetY + dy;
|
||||
int result = dy;
|
||||
if (newY > mMaxScrollY) {
|
||||
result = mMaxScrollY - mOffsetY;
|
||||
} else if (newY < 0) {
|
||||
result = 0 - mOffsetY;
|
||||
}
|
||||
mOffsetY += result;
|
||||
setPageIndex(getPageIndexByOffset(), true);
|
||||
offsetChildrenVertical(-result);
|
||||
if (result > 0) {
|
||||
recycleAndFillItems(recycler, state, true);
|
||||
} else {
|
||||
recycleAndFillItems(recycler, state, false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听滚动状态,滚动结束后通知当前选中的页面
|
||||
*
|
||||
* @param state 滚动状态
|
||||
*/
|
||||
@Override
|
||||
public void onScrollStateChanged(int state) {
|
||||
Logi("onScrollStateChanged = " + state);
|
||||
mScrollState = state;
|
||||
super.onScrollStateChanged(state);
|
||||
if (state == SCROLL_STATE_IDLE) {
|
||||
setPageIndex(getPageIndexByOffset(), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--- 私有方法 ----------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 获取条目显示区域
|
||||
*
|
||||
* @param pos 位置下标
|
||||
* @return 显示区域
|
||||
*/
|
||||
private Rect getItemFrameByPosition(int pos) {
|
||||
Rect rect = mItemFrames.get(pos);
|
||||
if (null == rect) {
|
||||
rect = new Rect();
|
||||
// 计算显示区域 Rect
|
||||
|
||||
// 1. 获取当前View所在页数
|
||||
int page = pos / mOnePageSize;
|
||||
|
||||
// 2. 计算当前页数左上角的总偏移量
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
if (canScrollHorizontally()) {
|
||||
offsetX += getUsableWidth() * page;
|
||||
} else {
|
||||
offsetY += getUsableHeight() * page;
|
||||
}
|
||||
|
||||
// 3. 根据在当前页面中的位置确定具体偏移量
|
||||
int pagePos = pos % mOnePageSize; // 在当前页面中是第几个
|
||||
int row = pagePos / mColumns; // 获取所在行
|
||||
int col = pagePos - (row * mColumns); // 获取所在列
|
||||
|
||||
offsetX += col * mItemWidth;
|
||||
offsetY += row * mItemHeight;
|
||||
|
||||
// 状态输出,用于调试
|
||||
Logi("pagePos = " + pagePos);
|
||||
Logi("行 = " + row);
|
||||
Logi("列 = " + col);
|
||||
|
||||
Logi("offsetX = " + offsetX);
|
||||
Logi("offsetY = " + offsetY);
|
||||
|
||||
rect.left = offsetX;
|
||||
rect.top = offsetY;
|
||||
rect.right = offsetX + mItemWidth;
|
||||
rect.bottom = offsetY + mItemHeight;
|
||||
|
||||
// 存储
|
||||
mItemFrames.put(pos, rect);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的宽度
|
||||
*
|
||||
* @return 宽度 - padding
|
||||
*/
|
||||
private int getUsableWidth() {
|
||||
return getWidth() - getPaddingLeft() - getPaddingRight();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的高度
|
||||
*
|
||||
* @return 高度 - padding
|
||||
*/
|
||||
private int getUsableHeight() {
|
||||
return getHeight() - getPaddingTop() - getPaddingBottom();
|
||||
}
|
||||
|
||||
|
||||
//--- 页面相关(私有) -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 获取总页数
|
||||
*/
|
||||
private int getTotalPageCount() {
|
||||
if (getItemCount() <= 0) return 0;
|
||||
int totalCount = getItemCount() / mOnePageSize;
|
||||
if (getItemCount() % mOnePageSize != 0) {
|
||||
totalCount++;
|
||||
}
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据pos,获取该View所在的页面
|
||||
*
|
||||
* @param pos position
|
||||
* @return 页面的页码
|
||||
*/
|
||||
private int getPageIndexByPos(int pos) {
|
||||
return pos / mOnePageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 offset 获取页面Index
|
||||
*
|
||||
* @return 页面 Index
|
||||
*/
|
||||
private int getPageIndexByOffset() {
|
||||
int pageIndex;
|
||||
if (canScrollVertically()) {
|
||||
int pageHeight = getUsableHeight();
|
||||
if (mOffsetY <= 0 || pageHeight <= 0) {
|
||||
pageIndex = 0;
|
||||
} else {
|
||||
pageIndex = mOffsetY / pageHeight;
|
||||
if (mOffsetY % pageHeight > pageHeight / 2) {
|
||||
pageIndex++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int pageWidth = getUsableWidth();
|
||||
if (mOffsetX <= 0 || pageWidth <= 0) {
|
||||
pageIndex = 0;
|
||||
} else {
|
||||
pageIndex = mOffsetX / pageWidth;
|
||||
if (mOffsetX % pageWidth > pageWidth / 2) {
|
||||
pageIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Logi("getPageIndexByOffset pageIndex = " + pageIndex);
|
||||
return pageIndex;
|
||||
}
|
||||
|
||||
|
||||
//--- 公开方法 ----------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 创建默认布局参数
|
||||
*
|
||||
* @return 默认布局参数
|
||||
*/
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理测量逻辑
|
||||
*
|
||||
* @param recycler RecyclerView
|
||||
* @param state 状态
|
||||
* @param widthMeasureSpec 宽度属性
|
||||
* @param heightMeasureSpec 高估属性
|
||||
*/
|
||||
@Override
|
||||
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(recycler, state, widthMeasureSpec, heightMeasureSpec);
|
||||
int widthsize = View.MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
|
||||
int widthmode = View.MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
|
||||
|
||||
int heightsize = View.MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
|
||||
int heightmode = View.MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
|
||||
|
||||
// 将 wrap_content 转换为 match_parent
|
||||
if (widthmode != EXACTLY && widthsize > 0) {
|
||||
widthmode = EXACTLY;
|
||||
}
|
||||
if (heightmode != EXACTLY && heightsize > 0) {
|
||||
heightmode = EXACTLY;
|
||||
}
|
||||
setMeasuredDimension(View.MeasureSpec.makeMeasureSpec(widthsize, widthmode),
|
||||
View.MeasureSpec.makeMeasureSpec(heightsize, heightmode));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以水平滚动
|
||||
*
|
||||
* @return true 是,false 不是。
|
||||
*/
|
||||
@Override
|
||||
public boolean canScrollHorizontally() {
|
||||
return mOrientation == HORIZONTAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以垂直滚动
|
||||
*
|
||||
* @return true 是,false 不是。
|
||||
*/
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
return mOrientation == VERTICAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到下一页第一个条目的位置
|
||||
*
|
||||
* @return 第一个搞条目的位置
|
||||
*/
|
||||
int findNextPageFirstPos() {
|
||||
int page = mLastPageIndex;
|
||||
page++;
|
||||
if (page >= getTotalPageCount()) {
|
||||
page = getTotalPageCount() - 1;
|
||||
}
|
||||
Loge("computeScrollVectorForPosition next = " + page);
|
||||
return page * mOnePageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到上一页的第一个条目的位置
|
||||
*
|
||||
* @return 第一个条目的位置
|
||||
*/
|
||||
int findPrePageFirstPos() {
|
||||
// 在获取时由于前一页的View预加载出来了,所以获取到的直接就是前一页
|
||||
int page = mLastPageIndex;
|
||||
page--;
|
||||
Loge("computeScrollVectorForPosition pre = " + page);
|
||||
if (page < 0) {
|
||||
page = 0;
|
||||
}
|
||||
Loge("computeScrollVectorForPosition pre = " + page);
|
||||
return page * mOnePageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 X 轴偏移量
|
||||
*
|
||||
* @return X 轴偏移量
|
||||
*/
|
||||
public int getOffsetX() {
|
||||
return mOffsetX;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Y 轴偏移量
|
||||
*
|
||||
* @return Y 轴偏移量
|
||||
*/
|
||||
public int getOffsetY() {
|
||||
return mOffsetY;
|
||||
}
|
||||
|
||||
|
||||
//--- 页面对齐 ----------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 计算到目标位置需要滚动的距离{@link RecyclerView.SmoothScroller.ScrollVectorProvider}
|
||||
*
|
||||
* @param targetPosition 目标控件
|
||||
* @return 需要滚动的距离
|
||||
*/
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
PointF vector = new PointF();
|
||||
int[] pos = getSnapOffset(targetPosition);
|
||||
vector.x = pos[0];
|
||||
vector.y = pos[1];
|
||||
return vector;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取偏移量(为PagerGridSnapHelper准备)
|
||||
* 用于分页滚动,确定需要滚动的距离。
|
||||
* {@link PagerGridSnapHelper}
|
||||
*
|
||||
* @param targetPosition 条目下标
|
||||
*/
|
||||
int[] getSnapOffset(int targetPosition) {
|
||||
int[] offset = new int[2];
|
||||
int[] pos = getPageLeftTopByPosition(targetPosition);
|
||||
offset[0] = pos[0] - mOffsetX;
|
||||
offset[1] = pos[1] - mOffsetY;
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条目下标获取该条目所在页面的左上角位置
|
||||
*
|
||||
* @param pos 条目下标
|
||||
* @return 左上角位置
|
||||
*/
|
||||
private int[] getPageLeftTopByPosition(int pos) {
|
||||
int[] leftTop = new int[2];
|
||||
int page = getPageIndexByPos(pos);
|
||||
if (canScrollHorizontally()) {
|
||||
leftTop[0] = page * getUsableWidth();
|
||||
leftTop[1] = 0;
|
||||
} else {
|
||||
leftTop[0] = 0;
|
||||
leftTop[1] = page * getUsableHeight();
|
||||
}
|
||||
return leftTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要对齐的View
|
||||
*
|
||||
* @return 需要对齐的View
|
||||
*/
|
||||
public View findSnapView() {
|
||||
if (null != getFocusedChild()) {
|
||||
return getFocusedChild();
|
||||
}
|
||||
if (getChildCount() <= 0) {
|
||||
return null;
|
||||
}
|
||||
int targetPos = getPageIndexByOffset() * mOnePageSize; // 目标Pos
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
int childPos = getPosition(getChildAt(i));
|
||||
if (childPos == targetPos) {
|
||||
return getChildAt(i);
|
||||
}
|
||||
}
|
||||
return getChildAt(0);
|
||||
}
|
||||
|
||||
|
||||
//--- 处理页码变化 -------------------------------------------------------------------------------
|
||||
|
||||
private boolean mChangeSelectInScrolling = true; // 是否在滚动过程中对页面变化回调
|
||||
private int mLastPageCount = -1; // 上次页面总数
|
||||
private int mLastPageIndex = -1; // 上次页面下标
|
||||
|
||||
/**
|
||||
* 设置页面总数
|
||||
*
|
||||
* @param pageCount 页面总数
|
||||
*/
|
||||
private void setPageCount(int pageCount) {
|
||||
if (pageCount >= 0) {
|
||||
if (mPageListener != null && pageCount != mLastPageCount) {
|
||||
mPageListener.onPageSizeChanged(pageCount);
|
||||
}
|
||||
mLastPageCount = pageCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前选中页面
|
||||
*
|
||||
* @param pageIndex 页面下标
|
||||
* @param isScrolling 是否处于滚动状态
|
||||
*/
|
||||
private void setPageIndex(int pageIndex, boolean isScrolling) {
|
||||
Loge("setPageIndex = " + pageIndex + ":" + isScrolling);
|
||||
if (pageIndex == mLastPageIndex) return;
|
||||
// 如果允许连续滚动,那么在滚动过程中就会更新页码记录
|
||||
if (isAllowContinuousScroll()) {
|
||||
mLastPageIndex = pageIndex;
|
||||
} else {
|
||||
// 否则,只有等滚动停下时才会更新页码记录
|
||||
if (!isScrolling) {
|
||||
mLastPageIndex = pageIndex;
|
||||
}
|
||||
}
|
||||
if (isScrolling && !mChangeSelectInScrolling) return;
|
||||
if (pageIndex >= 0) {
|
||||
if (null != mPageListener) {
|
||||
mPageListener.onPageSelect(pageIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否在滚动状态更新选中页码
|
||||
*
|
||||
* @param changeSelectInScrolling true:更新、false:不更新
|
||||
*/
|
||||
public void setChangeSelectInScrolling(boolean changeSelectInScrolling) {
|
||||
mChangeSelectInScrolling = changeSelectInScrolling;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置滚动方向
|
||||
*
|
||||
* @param orientation 滚动方向
|
||||
* @return 最终的滚动方向
|
||||
*/
|
||||
@OrientationType
|
||||
public int setOrientationType(@OrientationType int orientation) {
|
||||
if (mOrientation == orientation || mScrollState != SCROLL_STATE_IDLE) return mOrientation;
|
||||
mOrientation = orientation;
|
||||
mItemFrames.clear();
|
||||
int x = mOffsetX;
|
||||
int y = mOffsetY;
|
||||
mOffsetX = y / getUsableHeight() * getUsableWidth();
|
||||
mOffsetY = x / getUsableWidth() * getUsableHeight();
|
||||
int mx = mMaxScrollX;
|
||||
int my = mMaxScrollY;
|
||||
mMaxScrollX = my / getUsableHeight() * getUsableWidth();
|
||||
mMaxScrollY = mx / getUsableWidth() * getUsableHeight();
|
||||
return mOrientation;
|
||||
}
|
||||
|
||||
//--- 滚动到指定位置 -----------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||
int targetPageIndex = getPageIndexByPos(position);
|
||||
smoothScrollToPage(targetPageIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平滑滚动到上一页
|
||||
*/
|
||||
public void smoothPrePage() {
|
||||
smoothScrollToPage(getPageIndexByOffset() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平滑滚动到下一页
|
||||
*/
|
||||
public void smoothNextPage() {
|
||||
smoothScrollToPage(getPageIndexByOffset() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平滑滚动到指定页面
|
||||
*
|
||||
* @param pageIndex 页面下标
|
||||
*/
|
||||
public void smoothScrollToPage(int pageIndex) {
|
||||
if (pageIndex < 0 || pageIndex >= mLastPageCount) {
|
||||
Log.e(TAG, "pageIndex is outOfIndex, must in [0, " + mLastPageCount + ").");
|
||||
return;
|
||||
}
|
||||
if (null == mRecyclerView) {
|
||||
Log.e(TAG, "RecyclerView Not Found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果滚动到页面之间距离过大,先直接滚动到目标页面到临近页面,在使用 smoothScroll 最终滚动到目标
|
||||
// 否则在滚动距离很大时,会导致滚动耗费的时间非常长
|
||||
int currentPageIndex = getPageIndexByOffset();
|
||||
if (Math.abs(pageIndex - currentPageIndex) > 3) {
|
||||
if (pageIndex > currentPageIndex) {
|
||||
scrollToPage(pageIndex - 3);
|
||||
} else if (pageIndex < currentPageIndex) {
|
||||
scrollToPage(pageIndex + 3);
|
||||
}
|
||||
}
|
||||
|
||||
// 具体执行滚动
|
||||
LinearSmoothScroller smoothScroller = new PagerGridSmoothScroller(mRecyclerView);
|
||||
int position = pageIndex * mOnePageSize;
|
||||
smoothScroller.setTargetPosition(position);
|
||||
startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
//=== 直接滚动 ===
|
||||
|
||||
@Override
|
||||
public void scrollToPosition(int position) {
|
||||
int pageIndex = getPageIndexByPos(position);
|
||||
scrollToPage(pageIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
public void prePage() {
|
||||
scrollToPage(getPageIndexByOffset() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
public void nextPage() {
|
||||
scrollToPage(getPageIndexByOffset() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到指定页面
|
||||
*
|
||||
* @param pageIndex 页面下标
|
||||
*/
|
||||
public void scrollToPage(int pageIndex) {
|
||||
if (pageIndex < 0 || pageIndex >= mLastPageCount) {
|
||||
Log.e(TAG, "pageIndex = " + pageIndex + " is out of bounds, mast in [0, " + mLastPageCount + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
if (null == mRecyclerView) {
|
||||
Log.e(TAG, "RecyclerView Not Found!");
|
||||
return;
|
||||
}
|
||||
|
||||
int mTargetOffsetXBy = 0;
|
||||
int mTargetOffsetYBy = 0;
|
||||
if (canScrollVertically()) {
|
||||
mTargetOffsetXBy = 0;
|
||||
mTargetOffsetYBy = pageIndex * getUsableHeight() - mOffsetY;
|
||||
} else {
|
||||
mTargetOffsetXBy = pageIndex * getUsableWidth() - mOffsetX;
|
||||
mTargetOffsetYBy = 0;
|
||||
}
|
||||
Loge("mTargetOffsetXBy = " + mTargetOffsetXBy);
|
||||
Loge("mTargetOffsetYBy = " + mTargetOffsetYBy);
|
||||
mRecyclerView.scrollBy(mTargetOffsetXBy, mTargetOffsetYBy);
|
||||
setPageIndex(pageIndex, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否允许连续滚动,默认为允许
|
||||
*
|
||||
* @return true 允许, false 不允许
|
||||
*/
|
||||
public boolean isAllowContinuousScroll() {
|
||||
return mAllowContinuousScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否允许连续滚动
|
||||
*
|
||||
* @param allowContinuousScroll true 允许,false 不允许
|
||||
*/
|
||||
public void setAllowContinuousScroll(boolean allowContinuousScroll) {
|
||||
mAllowContinuousScroll = allowContinuousScroll;
|
||||
}
|
||||
|
||||
//--- 对外接口 ----------------------------------------------------------------------------------
|
||||
|
||||
private PageListener mPageListener = null;
|
||||
|
||||
public void setPageListener(PageListener pageListener) {
|
||||
mPageListener = pageListener;
|
||||
}
|
||||
|
||||
public interface PageListener {
|
||||
/**
|
||||
* 页面总数量变化
|
||||
*
|
||||
* @param pageSize 页面总数
|
||||
*/
|
||||
void onPageSizeChanged(int pageSize);
|
||||
|
||||
/**
|
||||
* 页面被选中
|
||||
*
|
||||
* @param pageIndex 选中的页面
|
||||
*/
|
||||
void onPageSelect(int pageIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2018 GcsSloop
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Last modified 2018-04-09 23:56:59
|
||||
*
|
||||
* GitHub: https://github.com/GcsSloop
|
||||
* WeiBo: http://weibo.com/GcsSloop
|
||||
* WebSite: http://www.gcssloop.com
|
||||
*/
|
||||
|
||||
package com.shayu.onetoone.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static com.shayu.onetoone.widget.PagerConfig.Logi;
|
||||
|
||||
|
||||
/**
|
||||
* 作用:用于处理平滑滚动
|
||||
* 作者:GcsSloop
|
||||
* 摘要:用于用户手指抬起后页面对齐或者 Fling 事件。
|
||||
*/
|
||||
public class PagerGridSmoothScroller extends LinearSmoothScroller {
|
||||
private RecyclerView mRecyclerView;
|
||||
|
||||
public PagerGridSmoothScroller(@NonNull RecyclerView recyclerView) {
|
||||
super(recyclerView.getContext());
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
|
||||
RecyclerView.LayoutManager manager = mRecyclerView.getLayoutManager();
|
||||
if (null == manager) return;
|
||||
if (manager instanceof PagerGridLayoutManager) {
|
||||
PagerGridLayoutManager layoutManager = (PagerGridLayoutManager) manager;
|
||||
int pos = mRecyclerView.getChildAdapterPosition(targetView);
|
||||
int[] snapDistances = layoutManager.getSnapOffset(pos);
|
||||
final int dx = snapDistances[0];
|
||||
final int dy = snapDistances[1];
|
||||
Logi("dx = " + dx);
|
||||
Logi("dy = " + dy);
|
||||
final int time = calculateTimeForScrolling(Math.max(Math.abs(dx), Math.abs(dy)));
|
||||
if (time > 0) {
|
||||
action.update(dx, dy, time, mDecelerateInterpolator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
|
||||
return PagerConfig.getMillisecondsPreInch() / displayMetrics.densityDpi;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2017 GcsSloop
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Last modified 2017-09-20 16:32:43
|
||||
*
|
||||
* GitHub: https://github.com/GcsSloop
|
||||
* WeiBo: http://weibo.com/GcsSloop
|
||||
* WebSite: http://www.gcssloop.com
|
||||
*/
|
||||
|
||||
package com.shayu.onetoone.widget;
|
||||
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SnapHelper;
|
||||
|
||||
import static com.shayu.onetoone.widget.PagerConfig.Loge;
|
||||
|
||||
|
||||
/**
|
||||
* 作用:分页居中工具
|
||||
* 作者:GcsSloop
|
||||
* 摘要:每次只滚动一个页面
|
||||
*/
|
||||
public class PagerGridSnapHelper extends SnapHelper {
|
||||
private RecyclerView mRecyclerView; // RecyclerView
|
||||
|
||||
/**
|
||||
* 用于将滚动工具和 Recycler 绑定
|
||||
*
|
||||
* @param recyclerView RecyclerView
|
||||
* @throws IllegalStateException 状态异常
|
||||
*/
|
||||
@Override
|
||||
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws
|
||||
IllegalStateException {
|
||||
super.attachToRecyclerView(recyclerView);
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算需要滚动的向量,用于页面自动回滚对齐
|
||||
*
|
||||
* @param layoutManager 布局管理器
|
||||
* @param targetView 目标控件
|
||||
* @return 需要滚动的距离
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
|
||||
@NonNull View targetView) {
|
||||
int pos = layoutManager.getPosition(targetView);
|
||||
Loge("findTargetSnapPosition, pos = " + pos);
|
||||
int[] offset = new int[2];
|
||||
if (layoutManager instanceof PagerGridLayoutManager) {
|
||||
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
|
||||
offset = manager.getSnapOffset(pos);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得需要对齐的View,对于分页布局来说,就是页面第一个
|
||||
*
|
||||
* @param layoutManager 布局管理器
|
||||
* @return 目标控件
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
|
||||
if (layoutManager instanceof PagerGridLayoutManager) {
|
||||
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
|
||||
return manager.findSnapView();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目标控件的位置下标
|
||||
* (获取滚动后第一个View的下标)
|
||||
*
|
||||
* @param layoutManager 布局管理器
|
||||
* @param velocityX X 轴滚动速率
|
||||
* @param velocityY Y 轴滚动速率
|
||||
* @return 目标控件的下标
|
||||
*/
|
||||
@Override
|
||||
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager,
|
||||
int velocityX, int velocityY) {
|
||||
int target = RecyclerView.NO_POSITION;
|
||||
Loge("findTargetSnapPosition, velocityX = " + velocityX + ", velocityY" + velocityY);
|
||||
if (null != layoutManager && layoutManager instanceof PagerGridLayoutManager) {
|
||||
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
|
||||
if (manager.canScrollHorizontally()) {
|
||||
if (velocityX > PagerConfig.getFlingThreshold()) {
|
||||
target = manager.findNextPageFirstPos();
|
||||
} else if (velocityX < -PagerConfig.getFlingThreshold()) {
|
||||
target = manager.findPrePageFirstPos();
|
||||
}
|
||||
} else if (manager.canScrollVertically()) {
|
||||
if (velocityY > PagerConfig.getFlingThreshold()) {
|
||||
target = manager.findNextPageFirstPos();
|
||||
} else if (velocityY < -PagerConfig.getFlingThreshold()) {
|
||||
target = manager.findPrePageFirstPos();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loge("findTargetSnapPosition, target = " + target);
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一扔(快速滚动)
|
||||
*
|
||||
* @param velocityX X 轴滚动速率
|
||||
* @param velocityY Y 轴滚动速率
|
||||
* @return 是否消费该事件
|
||||
*/
|
||||
@Override
|
||||
public boolean onFling(int velocityX, int velocityY) {
|
||||
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
|
||||
if (layoutManager == null) {
|
||||
return false;
|
||||
}
|
||||
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
|
||||
if (adapter == null) {
|
||||
return false;
|
||||
}
|
||||
int minFlingVelocity = PagerConfig.getFlingThreshold();
|
||||
Loge("minFlingVelocity = " + minFlingVelocity);
|
||||
return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
|
||||
&& snapFromFling(layoutManager, velocityX, velocityY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速滚动的具体处理方案
|
||||
*
|
||||
* @param layoutManager 布局管理器
|
||||
* @param velocityX X 轴滚动速率
|
||||
* @param velocityY Y 轴滚动速率
|
||||
* @return 是否消费该事件
|
||||
*/
|
||||
private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,
|
||||
int velocityY) {
|
||||
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
|
||||
if (smoothScroller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
|
||||
if (targetPosition == RecyclerView.NO_POSITION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
smoothScroller.setTargetPosition(targetPosition);
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过自定义 LinearSmoothScroller 来控制速度
|
||||
*
|
||||
* @param layoutManager 布局故哪里去
|
||||
* @return 自定义 LinearSmoothScroller
|
||||
*/
|
||||
protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
|
||||
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
|
||||
return null;
|
||||
}
|
||||
return new PagerGridSmoothScroller(mRecyclerView);
|
||||
}
|
||||
|
||||
//--- 公开方法 ----------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 设置滚动阀值
|
||||
* @param threshold 滚动阀值
|
||||
*/
|
||||
public void setFlingThreshold(int threshold) {
|
||||
PagerConfig.setFlingThreshold(threshold);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user