分享

Android自定义view

 DeepReading 2019-03-27

滚动选择器主要用于在数据列表中快速选择其中一条数据,比如日期选择.首先给大伙看看最终的实现效果:


实现原理

  • 绘制
代码中定义了几个重要的变量:
  1. private int mVisibleItemCount = 3; // 可见的item数量
  2. private int mSelected; // 当前选中的item下标
  3. private int mItemHeight = 0; // 每个条目的高度 = mMeasureHeight/mVisibleItemCount
  4. private int mCenterY; // 中间item的起始坐标y = mCenterPosition*mItemHeight
  5. private float mMoveLength = 0; // item移动长度,负数表示向上移动,正数表示向下移动



通过上图可发现,这几个变量决定了滚动选择器中每个条目的绘制位置

  1. protected void onDraw(Canvas canvas) {
  2. // 中间item
  3. mPaint.setColor(mCenterItemBackground);
  4. float y = mCenterY;
  5. canvas.drawRect(0, y, getWidth(), y + mItemHeight, mPaint);
  6. // 传入位置信息,绘制item
  7. drawItem(canvas, mData, mSelected, 0, mMoveLength, mCenterY + mMoveLength);
  8. int length = Math.max(mCenterPosition, mVisibleItemCount - mCenterPosition);
  9. int positon;
  10. // 上下两边
  11. for (int i = 1; i <= length && i <= mData.size(); i++) {
  12. if (i <= mCenterPosition + 1) { // 上面的items,相对位置为 -i
  13. positon = mSelected - i < 0 ? mData.size() + mSelected - i
  14. : mSelected - i;
  15.                 // 传入位置信息,绘制item
  16. if (mIsCirculation) {
  17. drawItem(canvas, mData, positon, -i, mMoveLength, mCenterY + mMoveLength - i * mItemHeight);
  18. } else if (mSelected - i >= 0) { // 非循环滚动
  19. drawItem(canvas, mData, positon, -i, mMoveLength, mCenterY + mMoveLength - i * mItemHeight);
  20. }
  21. }
  22. if (i <= mVisibleItemCount - mCenterPosition) { // 下面的items,相对位置为 i
  23. positon = mSelected + i >= mData.size() ? mSelected + i
  24. - mData.size() : mSelected + i;
  25.                 // 传入位置信息,绘制item
  26. if (mIsCirculation) {
  27. drawItem(canvas, mData, positon, i, mMoveLength, mCenterY + mMoveLength + i * mItemHeight);
  28. } else if (mSelected + i < mData.size()) { // 非循环滚动
  29. drawItem(canvas, mData, positon, i, mMoveLength, mCenterY + mMoveLength + i * mItemHeight);
  30. }
  31. }
  32. }
  33. }

为了便以拓展,drawItem()负责每个item的绘制.接下来只要改变上面几个重要变量的值就可以实现滚动的效果.

  • Scroller滚动类

系统自带的android.widget.Scroller 类封装了滚动相关的操作。可以使用Scroller获取可以产生滚动效果的数据;例如,在响应一个滑动手势时,Scroller会帮你计算
滚动偏移量,你可以根据获取的偏移量来设置你的view的位置,从而实现滚动效果。 主要方法有:

  1. startScroll(int startX, int startY, int dx, int dy) //开始计算平滑滚动,dx、dy为滚动的距离,dx = finalX - startX;
  2. fling(int startX, int startY, int velocityX, int velocityY,
  3. int minX, int maxX, int minY, int maxY) //根据滑动的速度,开始计算惯性滚动
  4. computeScrollOffset()//计算当前时间滚动的偏移量,如果返回true,则滚动还未结束
  5. getCurrY() //获取当前Y轴坐标的偏移量
  6. getCurrX() //获取当前X轴坐标的偏移量
  7. mScroller.abortAnimation() //停止滚动

在自定义View中常用的方法为:

  1. // 开始计算平滑滚动
  2. mScroller.startScroll(startX, startY, dx, dy); 
  3. // 调用invalidate()方法刷新view时,在绘制(onDraw)之前会调用view.computeScroll()方法,
  4. // 所以在此方法内计算偏移量
  5. public void computeScroll() {
  6. if (mScroller.computeScrollOffset()) { // 正在滚动
  7.     int curY = mScroller.getCurrY();
  8. int curX = mScroller.getCurrX();
  9. // do something
  10. invalidate(); // 刷新
  11. }else{ // 滚动结束
  12. // do something
  13. }
  14. }

在滚动选择器中,当手指滑动时,把滑动的距离赋值给mMoveLength,当手指抬起时,则调用Scroller.startScroll()方法,将当前item移动到中间位置;如果是快速滑动后抬起手指,则调用Scroller.fling()方法惯性滑动一段距离,惯性结束后再调用Scroller.startScroll()方法,将当前item移动到中间位置.
  1. public void computeScroll() {
  2. if (mScroller.computeScrollOffset()) { // 正在滚动
  3. // 可以把scroller看做模拟的触屏滑动操作,mLastScrollY为上次滑动的坐标
  4. mMoveLength = mMoveLength + mScroller.getCurrY() - mLastScrollY;
  5. mLastScrollY = mScroller.getCurrY();
  6. checkCirculation(); // 检测当前选中的item
  7. invalidate();
  8. } else { // 滚动完毕
  9. if (mIsFling) {
  10. mIsFling = false;
  11. moveToCenter(); // 滚动到中间位置
  12. } else if (mIsMovingCenter) { // 选择完成,回调给监听器
  13. mMoveLength = 0;
  14. mIsMovingCenter = false;
  15. mLastScrollY = 0;
  16. notifySelected();
  17. }
  18. }
  19. }

  1. // 检测当前选择的item位置
  2. private void checkCirculation() {
  3. if (mMoveLength >= mItemHeight) { // 向下滑动
  4. // 该次滚动距离中越过的item数量
  5. int span = (int) (mMoveLength / mItemHeight);
  6. mSelected -= span;
  7. if (mSelected < 0) { // 滚动顶部,判断是否循环滚动
  8. if (mIsCirculation) {
  9. do {
  10. mSelected = mData.size() + mSelected;
  11. } while (mSelected < 0); // 当越过的item数量超过一圈时
  12. mMoveLength = (mMoveLength - mItemHeight) % mItemHeight;
  13. } else { // 非循环滚动
  14. mSelected = 0;
  15. mMoveLength = mItemHeight;
  16. if (mIsFling) { // 停止惯性滑动,根据computeScroll()中的逻辑,下一步将调用moveToCenter()
  17. mScroller.forceFinished(true);
  18. }
  19. if (mIsMovingCenter) { // 移回中间位置
  20. scroll(mMoveLength, 0);
  21. }
  22. }
  23. } else {
  24. mMoveLength = (mMoveLength - mItemHeight) % mItemHeight;
  25. }
  26. } else if (mMoveLength <= -mItemHeight) { // 向上滑动
  27. // 该次滚动距离中越过的item数量
  28. int span = (int) (-mMoveLength / mItemHeight);
  29. mSelected += span;
  30. if (mSelected >= mData.size()) { // 滚动末尾,判断是否循环滚动
  31. if (mIsCirculation) {
  32. do {
  33. mSelected = mSelected - mData.size();
  34. } while (mSelected >= mData.size()); // 当越过的item数量超过一圈时
  35. mMoveLength = (mMoveLength + mItemHeight) % mItemHeight;
  36. } else { // 非循环滚动
  37. mSelected = mData.size() - 1;
  38. mMoveLength = -mItemHeight;
  39. if (mIsFling) { // 停止惯性滑动,根据computeScroll()中的逻辑,下一步将调用moveToCenter()
  40. mScroller.forceFinished(true);
  41. }
  42. if (mIsMovingCenter) { // 移回中间位置
  43. scroll(mMoveLength, 0);
  44. }
  45. }
  46. } else {
  47. mMoveLength = (mMoveLength + mItemHeight) % mItemHeight;
  48. }
  49. }
  50. }


类的定义
为了拓展方便,这里把滚动选择器定义为抽象类,文章最开始的效果图是用继承自ScrollPickerView的字符串滚动选择器实现.

  1. public abstract class ScrollPickerView<T> extends View{
  2. /**
  3. * 绘制item
  4. * @param canvas
  5. * @param data  数据集
  6. * @param position 在data数据集中的位置
  7. * @param relative 相对中间item的位置,relative=position-getSelected()
  8. * @param moveLength 中间item滚动的距离,moveLength<0则表示向上滚动的距离,moveLength>0则表示向上滚动
  9. * @param top 当前绘制item的顶部坐标
  10. */
  11. public abstract void drawItem(Canvas canvas, List<T> data, int position, int relative, float moveLength, float top);
  12. }

文章开头的演示图中的控件为字符串选择器,主要继承了ScrollPickerView,并在drawItem方法中绘制字符串。
  1. /**
  2. * 字符串滚动选择器
  3. */
  4. public class StringScrollPicker extends ScrollPickerView<String>;

如果要实现图片滚动选择器,只有继承ScrollPickerView,实现drawItem()方法即可.

完整的代码放在了github上:https://github.com/1993hzw/Androids

       

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多