1.横向滑动,并且只显示一个item
LinePagerIndicatorDecoration
package com.jacksean.home.view;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class LinePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
private int colorActive = 0xFFA0522D;
private int colorInactive = 0x66FFFFFF;
private static final float DP = Resources.getSystem().getDisplayMetrics().density;
/**
* Height of the space the indicator takes up at the bottom of the view.
*/
private final int mIndicatorHeight = (int) (DP * 16);
/**
* Indicator stroke width.
*/
private final float mIndicatorStrokeWidth = DP * 2;
/**
* Indicator width.
*/
private final float mIndicatorItemLength = DP * 16;
/**
* Padding between indicators.
*/
private final float mIndicatorItemPadding = DP * 4;
/**
* Some more natural animation interpolation
*/
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private final Paint mPaint = new Paint();
public LinePagerIndicatorDecoration() {
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mIndicatorStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = parent.getAdapter().getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = mIndicatorItemLength * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2F;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - mIndicatorHeight / 2F;
drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount);
// find active page (which should be highlighted)
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
int activePosition = layoutManager.findFirstVisibleItemPosition();
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page (if the user is scrolling)
final View activeChild = layoutManager.findViewByPosition(activePosition);
int left = activeChild.getLeft();
int width = activeChild.getWidth();
// on swipe the active item will be positioned from [-width, 0]
// interpolate offset for smooth animation
float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount);
}
private void drawInactiveIndicators(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
mPaint.setColor(colorInactive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
float start = indicatorStartX;
for (int i = 0; i < itemCount; i++) {
// draw the line for every item
c.drawLine(start, indicatorPosY, start + mIndicatorItemLength, indicatorPosY, mPaint);
start += itemWidth;
}
}
private void drawHighlights(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition, float progress, int itemCount) {
mPaint.setColor(colorActive);
// width of item indicator including padding
final float itemWidth = mIndicatorItemLength + mIndicatorItemPadding;
if (progress == 0F) {
// no swipe, draw a normal indicator
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
c.drawLine(highlightStart, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
} else {
float highlightStart = indicatorStartX + itemWidth * highlightPosition;
// calculate partial highlight
float partialLength = mIndicatorItemLength * progress;
// draw the cut off highlight
c.drawLine(highlightStart + partialLength, indicatorPosY,
highlightStart + mIndicatorItemLength, indicatorPosY, mPaint);
// draw the highlight overlapping to the next item as well
if (highlightPosition < itemCount - 1) {
highlightStart += itemWidth;
c.drawLine(highlightStart, indicatorPosY,
highlightStart + partialLength, indicatorPosY, mPaint);
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = mIndicatorHeight;
}
}
参考:【译】RecyclerView+ItemDecorations实现带指示器ViewPager效果
2.条形指示器

如果只有2页数据可以像淘宝一样,但是多页数据时效果上感觉不太好
实现:
布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rlv"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<RelativeLayout
android:id="@+id/rl_indicator"
android:layout_width="60dp"
android:layout_height="4dp"
android:layout_marginBottom="10dp"
android:layout_gravity="center_horizontal|bottom"
android:background="@drawable/indicator_bg_normal">
<View
android:id="@+id/main_line"
android:layout_width="30dp"
android:layout_height="4dp"
android:layout_centerVertical="true"
android:background="@drawable/indicator_bg_select"/>
</RelativeLayout>
</FrameLayout>
package com.jacksean.home.view;
import android.graphics.PointF;
import android.util.DisplayMetrics;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.OrientationHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SnapHelper;
/**
* 网格布局分页效果
* 仅限布局为GridLayoutManager使用
*/
public class GridPagerSnapHelper extends SnapHelper {
@Nullable
private OrientationHelper mVerticalHelper;
@Nullable
private OrientationHelper mHorizontalHelper;
private RecyclerView recyclerView;
private int rowCount = 1;
private int columCount = 1;
/**
* @param row 行
* @param column 列
*/
public GridPagerSnapHelper(int row,int column) {
this.rowCount = row;
this.columCount = column;
}
@Override
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {
super.attachToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}
@Nullable
@Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = this.distanceToStart(layoutManager, targetView, this.getHorizontalHelper(layoutManager));
} else {
out[0] = 0;
}
if (layoutManager.canScrollVertically()) {
out[1] = this.distanceToStart(layoutManager, targetView, this.getVerticalHelper(layoutManager));
} else {
out[1] = 0;
}
return out;
}
@Nullable
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager.canScrollVertically()) {
return this.findStartSnapView(layoutManager, this.getVerticalHelper(layoutManager));
} else {
return layoutManager.canScrollHorizontally() ? this.findStartSnapView(layoutManager, this.getHorizontalHelper(layoutManager)) : null;
}
}
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return -1;
} else {
View mStartMostChildView = null;
if (layoutManager.canScrollVertically()) {
mStartMostChildView = this.findStartView(layoutManager, this.getVerticalHelper(layoutManager));
} else if (layoutManager.canScrollHorizontally()) {
mStartMostChildView = this.findStartView(layoutManager, this.getHorizontalHelper(layoutManager));
}
if (mStartMostChildView == null) {
return -1;
} else {
int centerPosition = layoutManager.getPosition(mStartMostChildView);
if (centerPosition == -1) {
return -1;
} else {
// 计算当前页面索引
int pagerIndex = centerPosition / (rowCount * columCount);
// 是否滑向下一页
boolean forwardDirection;
if (layoutManager.canScrollHorizontally()) {
forwardDirection = velocityX > 0;
} else {
forwardDirection = velocityY > 0;
}
// 条目是否是翻转模式
boolean reverseLayout = false;
if (layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider) {
RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager;
PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1);
if (vectorForEnd != null) {
reverseLayout = vectorForEnd.x < 0.0F || vectorForEnd.y < 0.0F;
}
}
int targetPosition = -1;
if (reverseLayout) {
targetPosition = (forwardDirection ? (pagerIndex - 1) * (rowCount * columCount) : (pagerIndex) * (rowCount * columCount));
} else {
targetPosition = (forwardDirection ? (pagerIndex + 1) * (rowCount * columCount) : (pagerIndex) * (rowCount * columCount));
}
return targetPosition;
}
}
}
}
@Override
protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
return !(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider) ? null : new LinearSmoothScroller(this.recyclerView.getContext()) {
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
int[] snapDistances = GridPagerSnapHelper.this.calculateDistanceToFinalSnap(GridPagerSnapHelper.this.recyclerView.getLayoutManager(), targetView);
int dx = snapDistances[0];
int dy = snapDistances[1];
int time = this.calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, this.mDecelerateInterpolator);
}
}
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 100.0F / (float) displayMetrics.densityDpi;
}
protected int calculateTimeForScrolling(int dx) {
return Math.min(100, super.calculateTimeForScrolling(dx));
}
};
}
private int distanceToStart(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) {
int childStart = helper.getDecoratedStart(targetView);
int containerStart;
if (layoutManager.getClipToPadding()) {
containerStart = helper.getStartAfterPadding();
} else {
containerStart = 0;
}
return childStart - containerStart;
}
@Nullable
private View findStartSnapView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
int childCount = layoutManager.getChildCount();
if (childCount == 0) {
return null;
} else {
View closestChild = null;
int start;
if (layoutManager.getClipToPadding()) {
start = helper.getStartAfterPadding();
} else {
start = 0;
}
int absClosest = 2147483647;
for (int i = 0; i < childCount; ++i) {
View child = layoutManager.getChildAt(i);
int childStart = helper.getDecoratedStart(child);
int absDistance = Math.abs(childStart - start);
if (absDistance < absClosest) {
absClosest = absDistance;
closestChild = child;
}
}
return closestChild;
}
}
@Nullable
private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
int childCount = layoutManager.getChildCount();
if (childCount == 0) {
return null;
} else {
View closestChild = null;
int startest = 2147483647;
for (int i = 0; i < childCount; ++i) {
View child = layoutManager.getChildAt(i);
int childStart = helper.getDecoratedStart(child);
if (childStart < startest) {
startest = childStart;
closestChild = child;
}
}
return closestChild;
}
}
@NonNull
private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
if (this.mVerticalHelper == null || this.mVerticalHelper.getLayoutManager() != layoutManager) {
this.mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
}
return this.mVerticalHelper;
}
@NonNull
private OrientationHelper getHorizontalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
if (this.mHorizontalHelper == null || this.mHorizontalHelper.getLayoutManager() != layoutManager) {
this.mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
}
return this.mHorizontalHelper;
}
}
GridLayoutManager gridLayoutManager=new GridLayoutManager(context, 2, LinearLayoutManager.HORIZONTAL, false);
viewHolder.rlv.setLayoutManager(gridLayoutManager);
PopularAdapter popularAdapter=new PopularAdapter(context,popularBeanList);
GridPagerSnapHelper snapHelper = new GridPagerSnapHelper(2, 5);
//必须添加,否则会报异常
viewHolder.rlv.setOnFlingListener(null);
snapHelper.attachToRecyclerView(viewHolder.rlv);
viewHolder.rlv.setAdapter(popularAdapter);
viewHolder.rlv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//整体的总宽度,注意是整体,包括在显示区域之外的
//滚动条表示的总范围
int range=0;
int temp = viewHolder.rlv.computeHorizontalScrollRange();
if (temp > range) {
range = temp;
}
//滑块的偏移量
int offset = viewHolder.rlv.computeHorizontalScrollOffset();
//可视区域长度
int extent = viewHolder.rlv.computeHorizontalScrollExtent();
//滑出部分在剩余范围的比例
float proportion = (float) (offset * 1.0 / (range - extent));
//计算滚动条宽度
float transMaxRange = viewHolder.rl_indicator.getWidth() - viewHolder.main_line.getWidth();
//设置滚动条移动
viewHolder.main_line.setTranslationX(transMaxRange * proportion);
}
});
3.原点指示器

实现:
注意要设置高度
布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rlv"
android:layout_width="match_parent"
android:layout_height="240dp"/>
<LinearLayout
android:layout_gravity="center_horizontal|bottom"
android:id="@+id/point_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:orientation="horizontal"></LinearLayout>
</FrameLayout>
PopularAdapter popularAdapter=new PopularAdapter(context,popularBeanList);
viewHolder.rlv.setAdapter(popularAdapter);
PagingScrollHelper scrollHelper = new PagingScrollHelper();
HorizontalPageLayoutManager horizontalPageLayoutManager = new HorizontalPageLayoutManager(2, 4);//这里两个参数是行列,这里实现的是一行三列
scrollHelper.setUpRecycleView(viewHolder.rlv);//将横向布局管理器和recycler view绑定到一起
scrollHelper.setOnPageChangeListener(new PagingScrollHelper.onPageChangeListener() {
@Override
public void onPageChange(int index) {
refreshPoint(viewHolder,scrollHelper.getPageCount(),index);
}
});//设置滑动监听
viewHolder.rlv.setLayoutManager(horizontalPageLayoutManager);//设置为横向
scrollHelper.updateLayoutManger();
scrollHelper.scrollToPosition(0);//默认滑动到第一页
viewHolder.rlv.setHorizontalScrollBarEnabled(true);
//获取总页数,采用这种方法才能获得正确的页数。否则会因为RecyclerView.State 缓存问题,页数不正确。
viewHolder.rlv.post(new Runnable() {
@Override
public void run() {
refreshPoint(viewHolder,scrollHelper.getPageCount(),0);
}
});
//动态生成指示器
private void refreshPoint(PopularViewHolder viewHolder, int pageSize, int pos) {
if (pos<0 || pos>pageSize){
return;
}
viewHolder.point_group.removeAllViews();
for (int i = 0; i < pageSize; i++) {
ImageView pointImage = new ImageView(context);
pointImage.setImageResource(R.drawable.pointgroup);
int PointSize = context.getResources().getDimensionPixelSize(R.dimen.point_size);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(PointSize, PointSize);
if (i > 0) {
params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.point_margin);
// pointImage.setSelected(false);
} else {
// pointImage.setSelected(true);
}
if (i==pos){
pointImage.setSelected(true);
}else {
pointImage.setSelected(false);
}
pointImage.setLayoutParams(params);
viewHolder.point_group.addView(pointImage);
}
}
网友评论