为了实现下图中的效果
描述一下需求:
1.根据后台返回的菜单数量,每页展示10条 不够10条也要占用一页 比如20条是两页,21条是3页
2.可以左右滑动,下方指示器跟着左右动
下面开始表演
1.首先自定义一个Helper,用于分页
public class PagingScrollHelper {
RecyclerView mRecyclerView= null;
private MyOnScrollListener mOnScrollListener= new MyOnScrollListener();
private MyOnFlingListener mOnFlingListener= new MyOnFlingListener();
private int offsetY= 0;
private int offsetX= 0;
int startY= 0;
int startX= 0;
enum ORIENTATION {
HORIZONTAL, VERTICAL, NULL
}
private ORIENTATION mOrientation= ORIENTATION.HORIZONTAL;
public void setUpRecycleView(RecyclerView recycleView) {
if (recycleView == null) {
throw new IllegalArgumentException("recycleView must be not null");
}
mRecyclerView= recycleView;
//处理滑动
recycleView.setOnFlingListener(mOnFlingListener);
//设置滚动监听,记录滚动的状态,和总的偏移量
recycleView.setOnScrollListener(mOnScrollListener);
//记录滚动开始的位置
recycleView.setOnTouchListener(mOnTouchListener);
//获取滚动的方向
updateLayoutManger();
}
public void updateLayoutManger() {
RecyclerView.LayoutManager layoutManager= mRecyclerView.getLayoutManager();
if (layoutManager!= null) {
if (layoutManager.canScrollVertically()) {
mOrientation= ORIENTATION.VERTICAL;
} else if (layoutManager.canScrollHorizontally()) {
mOrientation= ORIENTATION.HORIZONTAL;
} else {
mOrientation= ORIENTATION.NULL;
}
if (mAnimator!= null) {
mAnimator.cancel();
}
startX= 0;
startY= 0;
offsetX= 0;
offsetY= 0;
}
}
/**
* 获取总共的页数
*/
public int getPageCount() {
if (mRecyclerView!= null) {
if (mOrientation== ORIENTATION.NULL) {
return 0;
}
if (mOrientation== ORIENTATION.VERTICAL && mRecyclerView.computeVerticalScrollExtent() != 0) {
return mRecyclerView.computeVerticalScrollRange() / mRecyclerView.computeVerticalScrollExtent();
} else if (mRecyclerView.computeHorizontalScrollExtent() != 0) {
return mRecyclerView.computeHorizontalScrollRange() / mRecyclerView.computeHorizontalScrollExtent();
}
}
return 0;
}
ValueAnimator mAnimator= null;
public void scrollToPosition(int position) {
if (mAnimator== null) {
mOnFlingListener.onFling(0, 0);
}
if (mAnimator!= null) {
int startPoint= mOrientation== ORIENTATION.VERTICAL ? offsetY: offsetX, endPoint= 0;
if (mOrientation== ORIENTATION.VERTICAL) {
endPoint= mRecyclerView.getHeight() * position;
} else {
endPoint= mRecyclerView.getWidth() * position;
}
if (startPoint!= endPoint) {
mAnimator.setIntValues(startPoint, endPoint);
mAnimator.start();
}
}
}
public class MyOnFlingListener extends RecyclerView.OnFlingListener {
@Override
public boolean onFling(int velocityX, int velocityY) {
if (mOrientation== ORIENTATION.NULL) {
return false;
}
//获取开始滚动时所在页面的index
int p= getStartPageIndex();
//记录滚动开始和结束的位置
int endPoint= 0;
int startPoint= 0;
//如果是垂直方向
if (mOrientation== ORIENTATION.VERTICAL) {
startPoint= offsetY;
if (velocityY < 0) {
p--;
} else if (velocityY > 0) {
p++;
}
//更具不同的速度判断需要滚动的方向
//注意,此处有一个技巧,就是当速度为0的时候就滚动会开始的页面,即实现页面复位
endPoint= p* mRecyclerView.getHeight();
} else {
startPoint= offsetX;
if (velocityX < 0) {
p--;
} else if (velocityX > 0) {
p++;
}
endPoint= p* mRecyclerView.getWidth();
}
if (endPoint< 0) {
endPoint= 0;
}
//使用动画处理滚动
if (mAnimator== null) {
mAnimator= new ValueAnimator().ofInt(startPoint, endPoint);
mAnimator.setDuration(300);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int nowPoint= (int) animation.getAnimatedValue();
if (mOrientation== ORIENTATION.VERTICAL) {
int dy= nowPoint- offsetY;
//这里通过RecyclerView的scrollBy方法实现滚动。
mRecyclerView.scrollBy(0, dy);
} else {
int dx= nowPoint- offsetX;
mRecyclerView.scrollBy(dx, 0);
}
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//回调监听
if (null != mOnPageChangeListener) {
mOnPageChangeListener.onPageChange(getPageIndex());
}
//修复双击item bug
mRecyclerView.stopScroll();
startY= offsetY;
startX= offsetX;
}
});
} else {
mAnimator.cancel();
mAnimator.setIntValues(startPoint, endPoint);
}
mAnimator.start();
return true;
}
}
public class MyOnScrollListener extends RecyclerView.OnScrollListener {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
//newState==0表示滚动停止,此时需要处理回滚
if (newState == 0 && mOrientation!= ORIENTATION.NULL) {
boolean move;
int vX= 0, vY= 0;
if (mOrientation== ORIENTATION.VERTICAL) {
int absY= Math.abs(offsetY- startY);
//如果滑动的距离超过屏幕的一半表示需要滑动到下一页
move= absY> recyclerView.getHeight() / 2;
vY= 0;
if (move) {
vY= offsetY- startY< 0 ? -1000 : 1000;
}
} else {
int absX= Math.abs(offsetX- startX);
move= absX> recyclerView.getWidth() / 2;
if (move) {
vX= offsetX- startX< 0 ? -1000 : 1000;
}
}
mOnFlingListener.onFling(vX, vY);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
//滚动结束记录滚动的偏移量
offsetY+= dy;
offsetX+= dx;
}
}
private MyOnTouchListener mOnTouchListener= new MyOnTouchListener();
private boolean firstTouch= true;
public class MyOnTouchListener implements View.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
//手指按下的时候记录开始滚动的坐标
if (firstTouch) {
//第一次touch可能是ACTION_MOVE或ACTION_DOWN,所以使用这种方式判断
firstTouch= false;
startY= offsetY;
startX= offsetX;
}
if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
firstTouch= true;
}
return false;
}
}
private int getPageIndex() {
int p= 0;
if (mRecyclerView.getHeight() == 0 || mRecyclerView.getWidth() == 0) {
return p;
}
if (mOrientation== ORIENTATION.VERTICAL) {
p= offsetY/ mRecyclerView.getHeight();
} else {
p= offsetX/ mRecyclerView.getWidth();
}
return p;
}
private int getStartPageIndex() {
int p= 0;
if (mRecyclerView.getHeight() == 0 || mRecyclerView.getWidth() == 0) {
//没有宽高无法处理
return p;
}
if (mOrientation== ORIENTATION.VERTICAL) {
p= startY/ mRecyclerView.getHeight();
} else {
p= startX/ mRecyclerView.getWidth();
}
return p;
}
onPageChangeListener mOnPageChangeListener;
public void setOnPageChangeListener(onPageChangeListener listener) {
mOnPageChangeListener= listener;
}
public interface onPageChangeListener {
void onPageChange(int index);
}
}
2.定义一个接口(用于我们自定义LayoutMAnager)
public interface PageDecorationLastJudge {
boolean isLastRow(int position);
boolean isLastColumn(int position);
boolean isPageLast(int position);
}
3.自定义LayoutManager
public class HorizontalPageLayoutManager extends RecyclerView.LayoutManager implements PageDecorationLastJudge {
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return null;
}
int totalHeight= 0;
int totalWidth= 0;
int offsetY= 0;
int offsetX= 0;
public HorizontalPageLayoutManager(int rows, int columns) {
this.rows= rows;
this.columns= columns;
this.onePageSize= rows * columns;
}
@Override
public boolean canScrollHorizontally() {
return true;
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler);
int newX= offsetX+ dx;
int result= dx;
if (newX> totalWidth) {
result= totalWidth- offsetX;
} else if (newX< 0) {
result= 0 - offsetX;
}
offsetX+= result;
offsetChildrenHorizontal(-result);
recycleAndFillItems(recycler, state);
return result;
}
private SparseArray<Rect> allItemFrames= new SparseArray<>();
private int getUsableWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
private int getUsableHeight() {
return getHeight() - getPaddingTop() - getPaddingBottom();
}
int rows= 0;
int columns= 0;
int pageSize= 0;
int itemWidth= 0;
int itemHeight= 0;
int onePageSize= 0;
int itemWidthUsed;
int itemHeightUsed;
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
if (state.isPreLayout()) {
return;
}
//获取每个Item的平均宽高
itemWidth= getUsableWidth() / columns;
itemHeight= getUsableHeight() / rows;
//计算宽高已经使用的量,主要用于后期测量
itemWidthUsed= (columns- 1) * itemWidth;
itemHeightUsed= (rows- 1) * itemHeight;
//计算总的页数
// pageSize = state.getItemCount() / onePageSize + (state.getItemCount() % onePageSize == 0 ? 0 : 1);
computePageSize(state);
Log.i("zzz", "itemCount=" + getItemCount() + " state itemCount=" + state.getItemCount() + " pageSize=" + pageSize);
//计算可以横向滚动的最大值
totalWidth= (pageSize- 1) * getWidth();
//分离view
detachAndScrapAttachedViews(recycler);
int count= getItemCount();
for (int p= 0; p< pageSize; p++) {
for (int r= 0; r< rows; r++) {
for (int c= 0; c< columns; c++) {
int index= p* onePageSize+ r* columns+ c;
if (index== count) {
//跳出多重循环
c= columns;
r= rows;
p= pageSize;
break;
}
View view= recycler.getViewForPosition(index);
addView(view);
//测量item
measureChildWithMargins(view, itemWidthUsed, itemHeightUsed);
int width= getDecoratedMeasuredWidth(view);
int height= getDecoratedMeasuredHeight(view);
//记录显示范围
Rect rect= allItemFrames.get(index);
if (rect== null) {
rect= new Rect();
}
int x= p* getUsableWidth() + c* itemWidth;
int y= r* itemHeight;
rect.set(x, y, width+ x, height+ y);
allItemFrames.put(index, rect);
}
}
//每一页循环以后就回收一页的View用于下一页的使用
removeAndRecycleAllViews(recycler);
}
recycleAndFillItems(recycler, state);
}
private void computePageSize(RecyclerView.State state) {
pageSize= state.getItemCount() / onePageSize+ (state.getItemCount() % onePageSize== 0 ? 0 : 1);
}
@Override
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
super.onDetachedFromWindow(view, recycler);
offsetX= 0;
offsetY= 0;
}
private void recycleAndFillItems(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.isPreLayout()) {
return;
}
Rect displayRect= new Rect(getPaddingLeft() + offsetX, getPaddingTop(), getWidth() - getPaddingLeft() - getPaddingRight() + offsetX, getHeight() - getPaddingTop() - getPaddingBottom());
Rect childRect= new Rect();
for (int i= 0; i< getChildCount(); i++) {
View child= getChildAt(i);
childRect.left= getDecoratedLeft(child);
childRect.top= getDecoratedTop(child);
childRect.right= getDecoratedRight(child);
childRect.bottom= getDecoratedBottom(child);
if (!Rect.intersects(displayRect, childRect)) {
removeAndRecycleView(child, recycler);
}
}
for (int i= 0; i< getItemCount(); i++) {
if (Rect.intersects(displayRect, allItemFrames.get(i))) {
View view= recycler.getViewForPosition(i);
addView(view);
measureChildWithMargins(view, itemWidthUsed, itemHeightUsed);
Rect rect= allItemFrames.get(i);
layoutDecorated(view, rect.left- offsetX, rect.top, rect.right- offsetX, rect.bottom);
}
}
}
@Override
public boolean isLastRow(int index) {
if (index >= 0 && index < getItemCount()) {
int indexOfPage= index % onePageSize;
indexOfPage++;
if (indexOfPage> (rows- 1) * columns&& indexOfPage<= onePageSize) {
return true;
}
}
return false;
}
@Override
public boolean isLastColumn(int position) {
if (position >= 0 && position < getItemCount()) {
position++;
if (position % columns== 0) {
return true;
}
}
return false;
}
@Override
public boolean isPageLast(int position) {
position++;
return position % onePageSize== 0;
}
@Override
public int computeHorizontalScrollRange(RecyclerView.State state) {
computePageSize(state);
return pageSize* getWidth();
}
@Override
public int computeHorizontalScrollOffset(RecyclerView.State state) {
return offsetX;
}
@Override
public int computeHorizontalScrollExtent(RecyclerView.State state) {
return getWidth();
}
}
4.下面开始调用
4.1首先是布局
//RecyclerView 要定义高度
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_menu"
android:layout_marginLeft="@dimen/dp_12"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_150"
android:layout_marginTop="@dimen/dp_12" />
//指示器
<LinearLayout
android:id="@+id/indicator_layout"
android:layout_marginTop="@dimen/dp_8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:layout_marginBottom="@dimen/dp_10"
android:gravity="center_horizontal"
android:orientation="horizontal" />
4.2然后造数据
mItemBeans= new ArrayList<>();
for (int i= 0; i< 28; i++) {
if (i== 28) {
MenuBean.ItemBean itemBean= new MenuBean.ItemBean();
itemBean.setTitle("全部");
mItemBeans.add(itemBean);
} else {
MenuBean.ItemBean itemBean= new MenuBean.ItemBean();
itemBean.setTitle("菜单" + i);
mItemBeans.add(itemBean);
}
}
4.3 调用
PagingScrollHelper scrollHelper= new PagingScrollHelper();//初始化横向管理器
HorizontalPageLayoutManager horizontalPageLayoutManager
= new HorizontalPageLayoutManager(2, 5);//这里两个参数是行列,这里实现的是三行四列
scrollHelper.setUpRecycleView(rvMenu);//将横向布局管理器和recycler view绑定到一起
scrollHelper.setOnPageChangeListener(this);//设置滑动监听
rvMenu.setLayoutManager(horizontalPageLayoutManager);//设置为横向
scrollHelper.updateLayoutManger();
scrollHelper.scrollToPosition(0);//默认滑动到第一页
rvMenu.setHorizontalScrollBarEnabled(true);
HorizontalScrollItemAdapter itemAdapter= new HorizontalScrollItemAdapter(context, R.layout.item_menu);
itemAdapter.setNewData(mItemBeans);
rvMenu.setAdapter(itemAdapter);
if (mItemBeans.size() % 10 > 0) {
//一共的页数等于 总数/每页数量,并取整。
page= (int) Math.ceil(mItemBeans.size() * 1.0 / 10);
} else if (mItemBeans.size() % 10 == 0){
page= mItemBeans.size() % 10;
}
setIndicatorLayout(page);
4.4 指示器
/*
page 是页数 需要自己算 比如我是三行四列 就是每页12个item
用我们数据的size%12 如不不等于0 就是 (size/12)+1
如果等于0 就是 size/12
*/
private void setIndicatorLayout(int page) {
//生成相应数量的导航小圆点
LinearLayout.LayoutParams params= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
//设置小圆点左右之间的间隔
params.setMargins(10, 0, 10, 0);
//得到页面个数
dotViews= new ImageView[page];//此处传进来的6是页面数,通常要遍历得到页面的数量来创建图片集合
for (int i= 0; i< page; i++) {//这里也是循环页面数量
ImageView imageView= new ImageView(context);
imageView.setLayoutParams(params);
imageView.setImageResource(R.drawable.shape_circle);//设置默认没有选中时所有图片为灰色
if (i== 0) {
//默认启动时,选中第一个小圆点
imageView.setSelected(true);
} else {
imageView.setSelected(false);
}
//得到每个小圆点的引用,用于滑动页面时,更改它们的状态。
dotViews[i] = imageView;
dotViews[0].setImageResource(R.drawable.shape_sel_circle);//设置第一个页面选择为嗨丝
//添加到布局里面显示
indicatorLayout.addView(imageView);
}
}
4.5指示器样式
//未选中状态
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#e5e5e5" />
<size
android:width="@dimen/dp_3"
android:height="@dimen/dp_3" />
</shape>
//选中状态
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#EA3721" />
<corners android:radius="@dimen/dp_3"/>
<size
android:width="@dimen/dp_10"
android:height="@dimen/dp_3" />
</shape>
5.表演结束 谢谢大家