前些天看到有人在论坛上问这种效果怎么实现,没写过也没用过这个功能,网上查了一下,大多是使用ViewPager+GridView或者HorizontalScrollView+GridView实现,不过貌似有点复杂,太懒,没仔细看。这两天学习RecyclerView的使用(网上有很多文章,建议大家阅读本博客的时候先去了解一下),发现RecyclerView可以实现GridView 的横向滚动效果,不过没有分页,因此决定自己写一个。

Demo已上传到GitHubCSDN下载频道,是AS项目,使用AS的同学可以直接下载或者clone,博文最后也有贴出完整代码,使用Eclipse的同学可以自己新建项目并Copy代码。

效果图:

(由于这里每个Item都很相像,所以效果看起来不是很好,请见谅) 
图1:

图2: 
(删除的操作是在长按事件中写的) 
图1是带页码指示器的多行横向分页的GridView效果,拖动距离不足时,还可以滚动回原来的位置(类似于ViewPager拖动距离不足的效果); 
图2是和ViewPager一模一样的效果,实现此效果只要设置行数和列数都为1即可。

  1. 使用以下代码,需要导入RecyclerViewjar包或者依赖

代码结构:

  • AutoGridLayoutManager继承自GridLayoutManager并重写了onMeasure方法,目的是使RecyclerView的高度自适应内容高度。
  • DimensionConvert是一个用来转换px和pd的工具类。
  • MainActivity是一个使用示例。
  • PageIndicatorView继承自LinearLayout,存放一些小圆点作为页码指示器。
  • PageRecyclerView继承自RecyclerView,用来完成分页等功能。

先简单讲一下实现步骤,之后贴完整的代码

第一步: 实现横向滚动的GridView效果

    这个很简单,只要给RecyclerView设置横向的GridLayoutManager就可以了。但是使用过程中发现,RecyclerView并不会自适应内容的高度,因此重写了GridLayoutManager的onMeasure方法(MyGridLayoutManager.java);

  1. @Override
  2. public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
  3. View view = recycler.getViewForPosition();
  4. if (view != null) {
  5. measureChild(view, widthSpec, heightSpec);
  6. int measuredWidth = View.MeasureSpec.getSize(widthSpec);
  7. int measuredHeight = view.getMeasuredHeight() * getSpanCount();
  8. setMeasuredDimension(measuredWidth, measuredHeight);
  9. }
  10. }

第二步:实现自定义行数和列数功能

    实现此功能需要重写RecyclerView(MyRecyclerView.java),并添加两个成员变量spanRowspanColumn和一个设置行数列数的方法setPageSize(int spanRow, int spanColumn)。 
    之后,在Adapter中生成Item的时候就可以根据设置好的PageRecyclerView的宽度和列数计算单个Item的宽度,以达到一页正好显示固定列数的目的:

  1. @Override
  2. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  3. if (itemWidth <= ) {
  4. // 计算Item的宽度
  5. itemWidth = (parent.getWidth() - pageMargin * ) / spanColumn;
  6. }
  7.  
  8. RecyclerView.ViewHolder holder = mCallBack.onCreateViewHolder(parent, viewType);
  9.  
  10. holder.itemView.measure(, );
  11. holder.itemView.getLayoutParams().width = itemWidth;
  12. holder.itemView.getLayoutParams().height = holder.itemView.getMeasuredHeight();
  13.  
  14. return holder;
  15. }

可以看到上面代码中有一个mCallBack变量,这是一个接口的实现类的实例,我们需要创建Adapter实例的时候传入一个此接口的子类实例。

  1. public interface CallBack {
  2.  
  3. /**
  4. * 创建VieHolder
  5. *
  6. * @param parent
  7. * @param viewType
  8. */
  9. RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
  10.  
  11. /**
  12. * 绑定数据到ViewHolder
  13. *
  14. * @param holder
  15. * @param position
  16. */
  17. void onBindViewHolder(RecyclerView.ViewHolder holder, int position);
  18.  
  19. }

此接口共有两个方法,这两个方法和Adapter中需要重写的两个方法一样,用法也一样,分别用来创建ViewHolder实例和给ViewHolder中的控件绑定数据。

第三步:开始分页滚动

1> 分页:

    完成第二步之后,布局就调整好了,之后我们实现分页滚动的功能。要分页就肯定需要总页数(totalPage)和当前页码(currentPage),我们需要在设置Adapter适配器之后根据Item的总数和每页的Item数计算总页数:

  1. @Override
  2. public void setAdapter(Adapter adapter) {
  3. super.setAdapter(adapter);
  4. // 计算总页数
  5. totalPage = ((int) Math.ceil(adapter.getItemCount() / (double) (spanRow * spanColumn)));
  6. mIndicatorView.initIndicator(totalPage);
  7. }

然后就可以重写RecyclerView的onTouchEvent方法实现分页,根据ACTION_DOWNACTION_UP时候的坐标计算滑动方向,在ACTION_UP的时候根据滑动的方向使用smoothScrollBy方法向左或向右滑动一个MyRecyclerView的宽度就可以了。 
    不过这种切换页面的方式很生硬,我们要实现的ViewPager的滑动效果:要滑动超过一定的距离才能切换页码,否则滚回原来的位置。实现此功能需要一个常量,不过为了适应各种宽度的MyRecyclerView,这里根据MyRecyclerView的宽度动态设置最小滚动距离:

  1. private int shortestDistance; // 超过此距离的滑动才有效
  2.  
  3. @Override
  4. protected void onMeasure(int widthSpec, int heightSpec) {
  5. super.onMeasure(widthSpec, heightSpec);
  6. shortestDistance = getMeasuredWidth() / ;
  7. }

还需要其他的几个变量:

  1. private float downX = ; // 手指按下的X轴坐标
  2. private float slideDistance = ; // 滑动的距离
  3. private float scrollX = ; // X轴当前的位置

scrollX为当前滚动的位置,重写onScrolled计算滚动到的位置:

  1. @Override
  2. public void onScrolled(int dx, int dy) {
  3. scrollX += dx;
  4. super.onScrolled(dx, dy);
  5. }

之后就可以编写完整的onTouchEvent方法:

  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3.  
  4. switch (event.getAction()) {
  5. case MotionEvent.ACTION_DOWN:
  6. downX = event.getX();
  7. break;
  8. case MotionEvent.ACTION_UP:
  9. slideDistance = event.getX() - downX;
  10. if (Math.abs(slideDistance) > shortestDistance) {
  11. // 滑动距离足够,执行翻页
  12. if (slideDistance > ) {
  13. // 上一页
  14. currentPage = currentPage == ? : currentPage - ;
  15. } else {
  16. // 下一页
  17. currentPage = currentPage == totalPage ? totalPage : currentPage + ;
  18. }
  19. }
  20. // 执行滚动
  21. smoothScrollBy((int) ((currentPage - ) * getWidth() - scrollX), );
  22. return true;
  23. default:
  24. break;
  25. }
  26.  
  27. return super.onTouchEvent(event);

2> 页间距

为了分页更加清晰,还需要给页与页添加间距: 
    首先添加一个成员变量,和set方法

  1. private int pageMargin = ; // 页间距
  2. /**
  3. * 设置页间距
  4. *
  5. * @param pageMargin 间距(px)
  6. */
  7. public void setPageMargin(int pageMargin) {
  8. this.pageMargin = pageMargin;
  9. }

然后重写Adapter的onBindViewHolder方法调整页间距:

  1. @Override
  2. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
  3. if (spanColumn == ) {
  4. // 每个Item距离左右两侧各pageMargin
  5. holder.itemView.getLayoutParams().width = itemWidth + pageMargin * ;
  6. holder.itemView.setPadding(pageMargin, , pageMargin, );
  7. } else {
  8. int m = position % (spanRow * spanColumn);
  9. if (m < spanRow) {
  10. // 每页左侧的Item距离左边pageMargin
  11. holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
  12. holder.itemView.setPadding(pageMargin, , , );
  13. } else if (m >= spanRow * spanColumn - spanRow) {
  14. // 每页右侧的Item距离右边pageMargin
  15. holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
  16. holder.itemView.setPadding(, , pageMargin, );
  17. } else {
  18. // 中间的正常显示
  19. holder.itemView.getLayoutParams().width = itemWidth;
  20. holder.itemView.setPadding(, , , );
  21. }
  22. }
  23.  
  24. }

3> 占位Item

为了最后不足一页时也能完整显示,还需要在最后不足一页时,生成占位的View,因此修改Adapter的onBindViewHolder方法和getItemCount方法:

  1. @Override
  2. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
  3.  
  4. ...
  5.  
  6. if (position < dataList.size()) {
  7. holder.itemView.setAlpha();
  8. mCallBack.onBindViewHolder(holder, position);
  9. } else {
  10. holder.itemView.setAlpha();
  11. }
  12. }
  13.  
  14. @Override
  15. public int getItemCount() {
  16. int m = dataList.size() % (spanRow * spanColumn);
  17. if (m == ) {
  18. return dataList.size();
  19. } else {
  20. return dataList.size() + (spanRow * spanColumn - m);
  21. }
  22. }

至此,分页功能就完成了,为了功能更丰满,还需要添加一个分页指示器(就是效果图中的小圆点),这个功能还是很简单的,新建一个类继承LinearLayout并根据总页数生成一些小圆点的View,然后提供一个修改当前页码的方法就OK啦。

第四步:删除Item

最后还有一个删除Item的功能,实现方式还是使用系统的Adapter的notifyItemRemoved(int position);方法,由于前面分页时给部分Item设置了padding,所以为了布局不会错乱,还需要更新其他改变的Item:

  1. // 删除Item
  2. notifyItemRemoved(position);
  3. // 更新界面上发生改变的Item
  4. notifyItemRangeChanged(position, currentPage * spanRow * spanColumn);

然后还要更新页码指示器,这里就不贴代码了,直接看下面的类就可以了。 
使用的时候只要把指示器和MyRecyclerView按照自己的需求布局,并在切换页面的时候更新指示器就完成了。

改: 
1. 上面分页滑动是在onTouchEvent()方法中实现的,但是后来发现,这种实现方式会导致给Item添加onClickListeneronLongClickListeneronTouchListener的时候会产生事件冲突,因此修改为在onScrollStateChanged()方法中实现,代码如下:

  1. /*
  2. * 0: 停止滚动且手指移开; 1: 开始滚动; 2: 手指做了抛的动作(手指离开屏幕前,用力滑了一下)
  3. */
  4. private int scrollState = ; // 滚动状态
  5. @Override
  6. public void onScrollStateChanged(int state) {
  7. switch (state) {
  8. case :
  9. scrollState = ;
  10. break;
  11. case :
  12. scrollState = ;
  13. break;
  14. case :
  15. if (slideDistance == ) {
  16. break;
  17. }
  18. scrollState = ;
  19. if (slideDistance < ) { // 上页
  20. currentPage = (int) Math.ceil(scrollX / getWidth());
  21. if (currentPage * getWidth() - scrollX < shortestDistance) {
  22. currentPage += ;
  23. }
  24. } else { // 下页
  25. currentPage = (int) Math.ceil(scrollX / getWidth()) + ;
  26. if (currentPage <= totalPage) {
  27. if (scrollX - (currentPage - ) * getWidth() < shortestDistance) {
  28. // 如果这一页滑出距离不足,则定位到前一页
  29. currentPage -= ;
  30. }
  31. } else {
  32. currentPage = totalPage;
  33. }
  34. }
  35. // 执行自动滚动
  36. smoothScrollBy((int) ((currentPage - ) * getWidth() - scrollX), );
  37. // 修改指示器选中项
  38. mIndicatorView.setSelectedPage(currentPage - );
  39. slideDistance = ;
  40. break;
  41. }
  42. super.onScrollStateChanged(state);
  43. }
  44.  
  45. @Override
  46. public void onScrolled(int dx, int dy) {
  47. scrollX += dx;
  48. if (scrollState == ) {
  49. slideDistance += dx;
  50. }
  51.  
  52. super.onScrolled(dx, dy);
  53. }
  1. RecyclerView的GridLayoutManager是从上到下从左到右排列的,而我们分页时大多需要的是从左到右从上到下排列,因此增加一个方法调整位置(此方法只适用于3*3排列的,还没有找到通用的方法,如果那位同学有方法,麻烦分享一下,先谢过)
  1. private void countRealPosition(int position) {
  2. // 为了使Item从左到右从上到下排列,需要position的值
  3. int m = position % (spanRow * spanColumn);
  4. switch (m) {
  5. case :
  6. case :
  7. realPosition = position + ;
  8. break;
  9. case :
  10. case :
  11. realPosition = position - ;
  12. break;
  13. case :
  14. realPosition = position + ;
  15. break;
  16. case :
  17. realPosition = position - ;
  18. break;
  19. case :
  20. case :
  21. case :
  22. realPosition = position;
  23. break;
  24. }
  25. }

<<<<<<<<<<<<<<<<<<<<<<使用方法参考MainActivity.java>>>>>>>>>>>>>>>>>>>>

上面讲的不够详细,具体见代码>>>>>>>>>>>>>>

完整代码:

AutoGridLayoutManager.java

    使用这个类替代GridLayoutManager是为了使RecyclerView及其子类能够自适应内容的高度。

  1. import android.content.Context;
  2. import android.support.v7.widget.GridLayoutManager;
  3. import android.support.v7.widget.RecyclerView;
  4. import android.util.AttributeSet;
  5. import android.view.View;
  6.  
  7. /**
  8. * Created by shichaohui on 2015/7/9 0009.
  9. * <p>
  10. * 重写GridLayoutManager,在{@link RecyclerView#setLayoutManager(RecyclerView.LayoutManager)}使用
  11. * 此类替换{@link GridLayoutManager},使{@link RecyclerView}能够自使用内容的高度
  12. * </p>
  13. */
  14. public class AutoGridLayoutManager extends GridLayoutManager {
  15.  
  16. private int measuredWidth = ;
  17. private int measuredHeight = ;
  18.  
  19. public AutoGridLayoutManager(Context context, AttributeSet attrs,
  20. int defStyleAttr, int defStyleRes) {
  21. super(context, attrs, defStyleAttr, defStyleRes);
  22. }
  23.  
  24. public AutoGridLayoutManager(Context context, int spanCount) {
  25. super(context, spanCount);
  26. }
  27.  
  28. public AutoGridLayoutManager(Context context, int spanCount,
  29. int orientation, boolean reverseLayout) {
  30. super(context, spanCount, orientation, reverseLayout);
  31. }
  32.  
  33. @Override
  34. public void onMeasure(RecyclerView.Recycler recycler,
  35. RecyclerView.State state, int widthSpec, int heightSpec) {
  36. if (measuredHeight <= ) {
  37. View view = recycler.getViewForPosition();
  38. if (view != null) {
  39. measureChild(view, widthSpec, heightSpec);
  40. measuredWidth = View.MeasureSpec.getSize(widthSpec);
  41. measuredHeight = view.getMeasuredHeight() * getSpanCount();
  42. }
  43. }
  44. setMeasuredDimension(measuredWidth, measuredHeight);
  45. }
  46.  
  47. }

PageRecyclerView.java

    重写RecyclerView实现分页

  1. import android.content.Context;
  2. import android.graphics.Color;
  3. import android.support.v4.view.PagerAdapter;
  4. import android.support.v7.widget.RecyclerView;
  5. import android.util.AttributeSet;
  6. import android.view.MotionEvent;
  7. import android.view.View;
  8. import android.view.ViewGroup;
  9.  
  10. import java.util.List;
  11. import java.util.Objects;
  12.  
  13. /**
  14. * Created by shichaohui on 2015/7/9 0009.
  15. * <p>
  16. * 横向分页的GridView效果
  17. * </p>
  18. * <p>
  19. * 默认为1行,每页3列,如果要自定义行数和列数,请在调用{@link PageRecyclerView#setAdapter(Adapter)}方法前调用
  20. * {@link PageRecyclerView#setPageSize(int, int)}方法自定义行数
  21. * </p>
  22. */
  23. public class PageRecyclerView extends RecyclerView {
  24.  
  25. private Context mContext = null;
  26.  
  27. private PageAdapter myAdapter = null;
  28.  
  29. private int shortestDistance; // 超过此距离的滑动才有效
  30. private float downX = ; // 手指按下的X轴坐标
  31. private float slideDistance = ; // 滑动的距离
  32. private float scrollX = ; // X轴当前的位置
  33.  
  34. private int spanRow = ; // 行数
  35. private int spanColumn = ; // 每页列数
  36. private int totalPage = ; // 总页数
  37. private int currentPage = ; // 当前页
  38.  
  39. private int pageMargin = ; // 页间距
  40.  
  41. private PageIndicatorView mIndicatorView = null; // 指示器布局
  42.  
  43. public PageRecyclerView(Context context) {
  44. this(context, null);
  45. }
  46.  
  47. public PageRecyclerView(Context context, AttributeSet attrs) {
  48. this(context, attrs, );
  49. }
  50.  
  51. public PageRecyclerView(Context context, AttributeSet attrs, int defStyle) {
  52. super(context, attrs, defStyle);
  53. defaultInit(context);
  54. }
  55.  
  56. // 默认初始化
  57. private void defaultInit(Context context) {
  58. this.mContext = context;
  59. setLayoutManager(new AutoGridLayoutManager(
  60. mContext, spanRow, AutoGridLayoutManager.HORIZONTAL, false));
  61. setOverScrollMode(OVER_SCROLL_NEVER);
  62. }
  63.  
  64. /**
  65. * 设置行数和每页列数
  66. *
  67. * @param spanRow 行数,<=0表示使用默认的行数
  68. * @param spanColumn 每页列数,<=0表示使用默认每页列数
  69. */
  70. public void setPageSize(int spanRow, int spanColumn) {
  71. this.spanRow = spanRow <= ? this.spanRow : spanRow;
  72. this.spanColumn = spanColumn <= ? this.spanColumn : spanColumn;
  73. setLayoutManager(new AutoGridLayoutManager(
  74. mContext, this.spanRow, AutoGridLayoutManager.HORIZONTAL, false));
  75. }
  76.  
  77. /**
  78. * 设置页间距
  79. *
  80. * @param pageMargin 间距(px)
  81. */
  82. public void setPageMargin(int pageMargin) {
  83. this.pageMargin = pageMargin;
  84. }
  85.  
  86. /**
  87. * 设置指示器
  88. *
  89. * @param indicatorView 指示器布局
  90. */
  91. public void setIndicator(PageIndicatorView indicatorView) {
  92. this.mIndicatorView = indicatorView;
  93. }
  94.  
  95. @Override
  96. protected void onMeasure(int widthSpec, int heightSpec) {
  97. super.onMeasure(widthSpec, heightSpec);
  98. shortestDistance = getMeasuredWidth() / ;
  99. }
  100.  
  101. @Override
  102. public void setAdapter(Adapter adapter) {
  103. super.setAdapter(adapter);
  104. this.myAdapter = (PageAdapter) adapter;
  105. update();
  106. }
  107.  
  108. // 更新页码指示器和相关数据
  109. private void update() {
  110. // 计算总页数
  111. int temp = ((int) Math.ceil(myAdapter.dataList.size() / (double) (spanRow * spanColumn)));
  112. if (temp != totalPage) {
  113. mIndicatorView.initIndicator(temp);
  114. // 页码减少且当前页为最后一页
  115. if (temp < totalPage && currentPage == totalPage) {
  116. currentPage = temp;
  117. // 执行滚动
  118. smoothScrollBy(-getWidth(), );
  119. }
  120. mIndicatorView.setSelectedPage(currentPage - );
  121. totalPage = temp;
  122. }
  123. }
  124.  
  125. @Override
  126. public boolean onTouchEvent(MotionEvent event) {
  127.  
  128. switch (event.getAction()) {
  129. case MotionEvent.ACTION_MOVE:
  130. if (currentPage == totalPage && downX - event.getX() > ) {
  131. return true;
  132. }
  133. break;
  134. case MotionEvent.ACTION_DOWN:
  135. downX = event.getX();
  136. break;
  137. case MotionEvent.ACTION_UP:
  138. slideDistance = event.getX() - downX;
  139. if (Math.abs(slideDistance) > shortestDistance) {
  140. // 滑动距离足够,执行翻页
  141. if (slideDistance > ) {
  142. // 上一页
  143. currentPage = currentPage == ? : currentPage - ;
  144. } else {
  145. // 下一页
  146. currentPage = currentPage == totalPage ? totalPage : currentPage + ;
  147. }
  148. // 修改指示器选中项
  149. mIndicatorView.setSelectedPage(currentPage - );
  150. }
  151. // 执行滚动
  152. smoothScrollBy((int) ((currentPage - ) * getWidth() - scrollX), );
  153. return true;
  154. default:
  155. break;
  156. }
  157.  
  158. return super.onTouchEvent(event);
  159. }
  160.  
  161. @Override
  162. public void onScrolled(int dx, int dy) {
  163. scrollX += dx;
  164. super.onScrolled(dx, dy);
  165. }
  166.  
  167. /**
  168. * 数据适配器
  169. */
  170. public class PageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
  171.  
  172. private List<?> dataList = null;
  173. private CallBack mCallBack = null;
  174. private int itemWidth = ;
  175. private int itemCount = ;
  176.  
  177. /**
  178. * 实例化适配器
  179. *
  180. * @param data
  181. * @param callBack
  182. */
  183. public PageAdapter(List<?> data, CallBack callBack) {
  184. this.dataList = data;
  185. this.mCallBack = callBack;
  186. itemCount = dataList.size() + spanRow * spanColumn;
  187. }
  188.  
  189. @Override
  190. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  191. if (itemWidth <= ) {
  192. // 计算Item的宽度
  193. itemWidth = (parent.getWidth() - pageMargin * ) / spanColumn;
  194. }
  195.  
  196. RecyclerView.ViewHolder holder = mCallBack.onCreateViewHolder(parent, viewType);
  197.  
  198. holder.itemView.measure(, );
  199. holder.itemView.getLayoutParams().width = itemWidth;
  200. holder.itemView.getLayoutParams().height = holder.itemView.getMeasuredHeight();
  201.  
  202. return holder;
  203. }
  204.  
  205. @Override
  206. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
  207. if (spanColumn == ) {
  208. // 每个Item距离左右两侧各pageMargin
  209. holder.itemView.getLayoutParams().width = itemWidth + pageMargin * ;
  210. holder.itemView.setPadding(pageMargin, , pageMargin, );
  211. } else {
  212. int m = position % (spanRow * spanColumn);
  213. if (m < spanRow) {
  214. // 每页左侧的Item距离左边pageMargin
  215. holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
  216. holder.itemView.setPadding(pageMargin, , , );
  217. } else if (m >= spanRow * spanColumn - spanRow) {
  218. // 每页右侧的Item距离右边pageMargin
  219. holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
  220. holder.itemView.setPadding(, , pageMargin, );
  221. } else {
  222. // 中间的正常显示
  223. holder.itemView.getLayoutParams().width = itemWidth;
  224. holder.itemView.setPadding(, , , );
  225. }
  226. }
  227.  
  228. if (position < dataList.size()) {
  229. holder.itemView.setVisibility(View.VISIBLE);
  230. mCallBack.onBindViewHolder(holder, position);
  231. } else {
  232. holder.itemView.setVisibility(View.INVISIBLE);
  233. }
  234.  
  235. }
  236.  
  237. @Override
  238. public int getItemCount() {
  239. return itemCount;
  240. }
  241.  
  242. /**
  243. * 删除Item
  244. * @param position 位置
  245. */
  246. public void remove(int position) {
  247. if (position < dataList.size()) {
  248. // 删除数据
  249. dataList.remove(position);
  250. itemCount--;
  251. // 删除Item
  252. notifyItemRemoved(position);
  253. // 更新界面上发生改变的Item
  254. notifyItemRangeChanged(position, currentPage * spanRow * spanColumn);
  255. // 更新页码指示器
  256. update();
  257. }
  258. }
  259.  
  260. }
  261.  
  262. public interface CallBack {
  263.  
  264. /**
  265. * 创建VieHolder
  266. *
  267. * @param parent
  268. * @param viewType
  269. */
  270. RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
  271.  
  272. /**
  273. * 绑定数据到ViewHolder
  274. *
  275. * @param holder
  276. * @param position
  277. */
  278. void onBindViewHolder(RecyclerView.ViewHolder holder, int position);
  279.  
  280. }
  281.  
  282. }
  283. PageIndicatorView.java
  284.     页码指示器 ,此类可以作为一个工具类,在ViewPager做的轮播图上也可以使用
  285.  
  286. import android.content.Context;
  287. import android.util.AttributeSet;
  288. import android.view.Gravity;
  289. import android.view.View;
  290. import android.widget.LinearLayout;
  291.  
  292. import java.util.ArrayList;
  293. import java.util.List;
  294.  
  295. /**
  296. * Created by shichaohui on 2015/7/10 0010.
  297. * <p/>
  298. * 页码指示器类,获得此类实例后,可通过{@link PageIndicatorView#initIndicator(int)}方法初始化指示器
  299. * </P>
  300. */
  301. public class PageIndicatorView extends LinearLayout {
  302.  
  303. private Context mContext = null;
  304. private int dotSize = ; // 指示器的大小(dp)
  305. private int margins = ; // 指示器间距(dp)
  306. private List<View> indicatorViews = null; // 存放指示器
  307.  
  308. public PageIndicatorView(Context context) {
  309. this(context, null);
  310. }
  311.  
  312. public PageIndicatorView(Context context, AttributeSet attrs) {
  313. this(context, attrs, );
  314. }
  315.  
  316. public PageIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
  317. super(context, attrs, defStyleAttr);
  318. init(context);
  319. }
  320.  
  321. private void init(Context context) {
  322. this.mContext = context;
  323.  
  324. setGravity(Gravity.CENTER);
  325. setOrientation(HORIZONTAL);
  326.  
  327. dotSize = DimensionConvert.dip2px(context, dotSize);
  328. margins = DimensionConvert.dip2px(context, margins);
  329. }
  330.  
  331. /**
  332. * 初始化指示器,默认选中第一页
  333. *
  334. * @param count 指示器数量,即页数
  335. */
  336. public void initIndicator(int count) {
  337.  
  338. if (indicatorViews == null) {
  339. indicatorViews = new ArrayList<>();
  340. } else {
  341. indicatorViews.clear();
  342. removeAllViews();
  343. }
  344. View view;
  345. LayoutParams params = new LayoutParams(dotSize, dotSize);
  346. params.setMargins(margins, margins, margins, margins);
  347. for (int i = ; i < count; i++) {
  348. view = new View(mContext);
  349. view.setBackgroundResource(android.R.drawable.presence_invisible);
  350. addView(view, params);
  351. indicatorViews.add(view);
  352. }
  353. if (indicatorViews.size() > ) {
  354. indicatorViews.get().setBackgroundResource(android.R.drawable.presence_online);
  355. }
  356. }
  357.  
  358. /**
  359. * 设置选中页
  360. *
  361. * @param selected 页下标,从0开始
  362. */
  363. public void setSelectedPage(int selected) {
  364. for (int i = ; i < indicatorViews.size(); i++) {
  365. if (i == selected) {
  366. indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_online);
  367. } else {
  368. indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_invisible);
  369. }
  370. }
  371. }
  372.  
  373. }

DimensionConvert.java

    用来转换dip和px的工具类

  1. import android.content.Context;
  2.  
  3. /**
  4. * Created by shichaohui on 2015/7/10 0010.
  5. */
  6. public class DimensionConvert {
  7.  
  8. /**
  9. * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
  10. *
  11. * @param context
  12. * @param dpValue 要转换的dp值
  13. */
  14. public static int dip2px(Context context, float dpValue) {
  15. final float scale = context.getResources().getDisplayMetrics().density;
  16. return (int) (dpValue * scale + 0.5f);
  17. }
  18.  
  19. /**
  20. * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
  21. *
  22. * @param context
  23. * @param pxValue 要转换的px值
  24. */
  25. public static int px2dip(Context context, float pxValue) {
  26. final float scale = context.getResources().getDisplayMetrics().density;
  27. return (int) (pxValue / scale + 0.5f);
  28. }
  29. }

MainActivity.java

  1. import android.app.Activity;
  2. import android.os.Bundle;
  3. import android.support.v7.widget.RecyclerView;
  4. import android.view.LayoutInflater;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.widget.TextView;
  9. import android.widget.Toast;
  10.  
  11. import java.util.ArrayList;
  12. import java.util.List;
  13.  
  14. public class MainActivity extends Activity {
  15.  
  16. private PageRecyclerView mRecyclerView = null;
  17. private List<String> dataList = null;
  18. private PageRecyclerView.PageAdapter myAdapter = null;
  19.  
  20. @Override
  21. protected void onCreate(Bundle savedInstanceState) {
  22. super.onCreate(savedInstanceState);
  23.  
  24. setContentView(R.layout.activity_main);
  25.  
  26. initData();
  27.  
  28. mRecyclerView = (PageRecyclerView) findViewById(R.id.cusom_swipe_view);
  29. // 设置指示器
  30. mRecyclerView.setIndicator((PageIndicatorView) findViewById(R.id.indicator));
  31. // 设置行数和列数
  32. mRecyclerView.setPageSize(, );
  33. // 设置页间距
  34. mRecyclerView.setPageMargin();
  35. // 设置数据
  36. mRecyclerView.setAdapter(myAdapter = mRecyclerView.new PageAdapter(dataList, new PageRecyclerView.CallBack() {
  37. @Override
  38. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  39. View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item, parent, false);
  40. return new MyHolder(view);
  41. }
  42.  
  43. @Override
  44. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
  45. ((MyHolder)holder).tv.setText(dataList.get(position));
  46. }
  47. }));
  48.  
  49. }
  50.  
  51. private void initData() {
  52. dataList = new ArrayList<>();
  53. for (int i = ; i < ; i++) {
  54. dataList.add(String.valueOf(i));
  55. }
  56. }
  57.  
  58. public class MyHolder extends RecyclerView.ViewHolder {
  59.  
  60. public TextView tv = null;
  61.  
  62. public MyHolder(View itemView) {
  63. super(itemView);
  64. tv = (TextView) itemView.findViewById(R.id.text);
  65. tv.setOnClickListener(new View.OnClickListener() {
  66. @Override
  67. public void onClick(View v) {
  68. Toast.makeText(MainActivity.this, getAdapterPosition() + "", Toast.LENGTH_SHORT).show();
  69. }
  70. });
  71. tv.setOnLongClickListener(new View.OnLongClickListener() {
  72. @Override
  73. public boolean onLongClick(View v) {
  74. myAdapter.remove(getAdapterPosition());
  75. return true;
  76. }
  77. });
  78. }
  79. }
  80.  
  81. }

最后是两个布局文件:

activity_main.xml

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:orientation="vertical"
  4. android:layout_height="match_parent">
  5.  
  6. <com.example.sch.myapplication.PageRecyclerView
  7. android:id="@+id/cusom_swipe_view"
  8. android:layout_width="match_parent"
  9. android:layout_height="match_parent" />
  10.  
  11. <com.example.sch.myapplication.PageIndicatorView
  12. android:id="@+id/indicator"
  13. android:layout_width="match_parent"
  14. android:layout_marginBottom="20dp"
  15. android:layout_height="wrap_content"
  16. android:layout_gravity="bottom"/>
  17.  
  18. </LinearLayout>

item.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5.  
  6. <TextView
  7. android:id="@+id/text"
  8. android:layout_width="match_parent"
  9. android:layout_height="50dp"
  10. android:layout_margin="10dp"
  11. android:background="#770000ff"
  12. android:gravity="center" />
  13.  
  14. </LinearLayout>

Android 使用RecyclerView实现多行水平分页的GridView效果和ViewPager效果的更多相关文章

  1. Android 自定义View修炼-自定义HorizontalScrollView视图实现仿ViewPager效果

    开发过程中,需要达到 HorizontalScrollView和ViewPager的效果,于是直接重写了HorizontalScrollView来达到实现ViewPager的效果. 实际效果图如下: ...

  2. Android中如何实现多行、水平滚动的分页的Gridview?

    功能要求: (1)比如每页显示2X2,总共2XN,每个item显示图片+文字(点击有链接). 如果单行水平滚动,可以用Horizontalscrollview实现. 如果是多行水平滚动,则结合Grid ...

  3. Android Studio 单刷《第一行代码》系列 02 —— 日志工具 LogCat

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  4. Android Studio 单刷《第一行代码》系列 01 —— 第一战 HelloWorld

    前言(Prologue) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Android ...

  5. Android Studio 单刷《第一行代码》系列 07 —— Broadcast 广播

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  6. Android Studio 单刷《第一行代码》系列 06 —— Fragment 生命周期

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  7. Android Studio 单刷《第一行代码》系列 05 —— Fragment 基础

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  8. Android Studio 单刷《第一行代码》系列 04 —— Activity 相关

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  9. Android Studio 单刷《第一行代码》系列 03 —— Activity 基础

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

随机推荐

  1. Jasper-Api:接口测试

    ylbtech-Jasper-Api:接口测试 1. EditTerminal返回顶部 1. /// <remarks/> [System.Web.Services.Protocols.S ...

  2. 面向对象(static关键字)

    static关键字:用于修饰成员(成员变量和成员函数) 被修饰后的成员具备以下特点: 随着类的加载而加载 优先于对象存在 被所有的对象共享 可以直接被类名调用 使用注意: 静态方法只能访问静态成员 静 ...

  3. python2 + selenium + eclipse 中,通过django生产数据库表的时候报错

    python2 + selenium + eclipse 中,通过django生产数据库表的时候报错 解决: 1.查看自己电脑中,“开始-->控制面板-->管理工具-->服务--&g ...

  4. web前端之Html和Css应用中的细节问题

    1.居中的n种方法:①.margin: 0 20%; ——设置margin上下外边距的值设置为0,左右外边距设置成相同的百分比,既可将盒子居中. ②.margin: 0 auto;width: 100 ...

  5. 《剑指offer》面试题22—栈的压入、弹出序列

    <程序员面试宝典>上也有经典的火车进站问题,类似. 如果12345是压栈顺序,序列45321可能是出栈顺序,但序列43512不可能. 规律:对序列中任意元素n,排在n后且比n小的元素一定是 ...

  6. 深入剖析ASP.NET Core2.1部署模型,你会大吃一惊

    ----------------------------   以下内容针对 ASP.NET Core2.1版本,2.2推出windows IIS进程内寄宿 暂不展开讨论---------------- ...

  7. 无监督学习:Deep Generative Mode(深度生成模型)

    一 前言 1.1 Creation 据说在费曼死后,人们在他生前的黑板上拍到如图画片,在左上角有道:What i cannot create ,I do not understand. Generat ...

  8. [转] 深度探索Hyperledger技术与应用之超级账本的典型交易流程

    转自: https://blog.csdn.net/HiBlock/article/details/80212499 个人感觉对交易流程描述的比较清楚,转载以备查看. 1 典型交易流程 下图所示为Hy ...

  9. [Xcode 实际操作]六、媒体与动画-(13)使用UIImageView制作帧动画

    目录:[Swift]Xcode实际操作 本文将演示如何将导入的序列图片,转换为帧动画. 在项目导航区打开资源文件夹[Assets.xcassets] [+]->[Import]->选择图片 ...

  10. java利用URL发送get和post请求

    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import ...