免费资源网 – https://freexyz.cn/

目录
  • 一、前言
  • 二、实现
    • 最终目标
      • 2.1 定义菜单项
      • 2.2 半径计算
      • 2.3 通过位置偏移进行复用和回收
      • 2.4 防止靠近中心的View被绘制
      • 2.5 获取离中心点最近的Item的y值
      • 2.6 根据滑动方向重新计算每个item的偏移
      • 2.7 事件处理
  • 三、使用方法
    • 四、总结
      • 4.1 整体效果
        • 4.2 点击事件处理
          • 4.3 全部代码

          一、前言

          循环菜单有很多种自定义方式,我们可以利用ViewPager或者RecyclerView + CarouselLayoutManager 或者RecyclerView + PageSnapHelper来实现这种效果,今天我们使用Canvas 2D来实现这种效果。

          Android使用Canvas 2D实现循环菜单效果

          二、实现

          LoopView 是常见的循环 View,一般应用于循环展示菜单项目,本次实现的是一组循环菜单,按照垂直方向,实际上,如果把某些变量互换,可以实现轮播图效果。

          最终目标

          • 在滑动过程中记录偏移的位置,将画出界面的从列表中移除,分别向两端添加。
          • 离中心点越近,半径就会越大
          • 模仿Recyler机制,偏移到界面以外的item回收利用

          2.1 定义菜单项

          首先这里定义一下菜单Item,主要标记颜色和文本内容

          public static class LoopItem {
              private int color;
              private String text;
          
              public LoopItem(String text, int color) {
                  this.color = color;
                  this.text = text;
              }
          
              public int getColor() {
                  return color;
              }
          
              public void setColor(int color) {
                  this.color = color;
              }
          
              public String getText() {
                  return text;
              }
          
              public void setText(String text) {
                  this.text = text;
              }
          }
          

          接下来需要定义绘制任务,将菜单数据和绘制任务解耦。

          我们这里需要

          • 半径
          • x,y坐标
          • 半径缩放增量
          public static class DrawTask<T extends LoopItem> {
          
              private T loopItem;
              private float radius; //半径,定值
              private float x;
              private float y;
              private float scaleOffset = 0;  // 半径缩放偏移量,离中心越远,此值就会越小
          
          
              public DrawTask(float x, float y, float radius) {
                  this.radius = radius;
                  this.x = x;
                  this.y = y;
              }
          
              public void setLoopItem(T loopItem) {
                  this.loopItem = loopItem;
              }
          
              public void draw(Canvas canvas, TextPaint textPaint) {
                  if (loopItem == null) return;
          
                  textPaint.setColor(loopItem.getColor());
                  textPaint.setStyle(Paint.Style.FILL);
                  textPaint.setShadowLayer(10, 0, 5, 0x99444444);
                  
                  //绘制圆
                  canvas.drawCircle(x, y, radius + scaleOffset, textPaint);
          
                  textPaint.setColor(Color.WHITE);
                  textPaint.setStyle(Paint.Style.FILL);
          
                  //绘制文本
                  String text = loopItem.getText();
                  float textWidth = textPaint.measureText(text);
                  float baseline = getTextPaintBaseline(textPaint);
                  
                  canvas.drawText(text, -textWidth / 2, y + baseline, textPaint);
          
              }
          
              public T getLoopItem() {
                  return loopItem;
              }
          }
          

          2.2 半径计算

          半径计算其实只需要按默的最小边的一半除以要展示的数量,为什么要这样计算呢?因为这样可以保证圆心等距,我们这里实现的效果其实是放大圆而不是缩小圆的方式,因此,默认情况

          int MAX_VISIBLE_COUNT = 5 //这个值建议是奇数
          circleRadius = Math.min(w / 2F, h / 2F) / MAX_VISIBLE_COUNT;
          

          2.3 通过位置偏移进行复用和回收

          这里主要是模仿Recycler机制,对DrawTask回收和复用

          //回收前处理,保证偏移连续
          private void recyclerBefore(int height) {
              if (isTouchEventUp) {
                  float centerOffset = getMinYOffset();
                  resetItemYOffset(height, centerOffset);
              } else {
                  resetItemYOffset(height, offsetY);
              }
              isTouchEventUp = false;
          }
          //回收后处理,保证Item连续
          private void recyclerAfter(int height) {
              if (isTouchEventUp) {
                  float centerOffset = getMinYOffset();
                  resetItemYOffset(height, centerOffset);
              } else {
                  resetItemYOffset(height, 0);
              }
          }
          
          //进行回收和复用,用head和tail指针对两侧外的Item移除和复用
          private void recycler() {
          
              if (drawTasks.size() < (MAX_VISIBLE_COUNT - 2)) return;
          
              Collections.sort(drawTasks, drawTaskComparatorY);
          
              DrawTask head = drawTasks.get(0);  //head 指针
              DrawTask tail = drawTasks.get(drawTasks.size() - 1); //尾指针
              int height = getHeight();
          
              if (head.y < -(height / 2F + circleRadius)) {
                  drawTasks.remove(head);
                  addToCachePool(head);
                  head.setLoopItem(null);  //回收
              } else {
                  DrawTask drawTask = getCachePool();  //复用
          
                  LoopItem loopItem = head.getLoopItem();
                  LoopItem preLoopItem = getPreLoopItem(loopItem);
                  drawTask.setLoopItem(preLoopItem);
          
                  drawTask.y = head.y - circleRadius * 2;
                  drawTasks.add(0, drawTask);
              }
          
              if (tail.y > (height / 2F + circleRadius)) {
                  drawTasks.remove(tail);
                  addToCachePool(tail);
                  tail.setLoopItem(null);
              } else {
                  DrawTask drawTask = getCachePool();
          
                  LoopItem loopItem = tail.getLoopItem();
                  LoopItem nextLoopItem = getNextLoopItem(loopItem);
                  drawTask.setLoopItem(nextLoopItem);
                  drawTask.y = tail.y + circleRadius * 2;
                  drawTasks.add(drawTask);
              }
          }
          

          2.4 防止靠近中心的View被绘制

          远离中心的Item要先绘制,意味着靠近边缘的要优先绘制,防止盖住中心的Item,因此每次都需要排序 这里的outOffset半径偏移值,半径越小的就会排在前面

            Collections.sort(drawTasks, new Comparator<DrawTask>() {
                      @Override
                      public int compare(DrawTask left, DrawTask right) {
                          float dx = Math.abs(left.y) -  Math.abs(right.y);
                          if (dx > 0) {
                              return 1;
                          }
                          if (dx == 0) {
                              return 0;
                          }
                          return -1;
                      }
                  });
          

          2.5 获取离中心点最近的Item的y值

          scaleOffset越大,离圆心越近,通过这种方式就能筛选出靠近圆心的Item Y坐标

          private float getMinYOffset() {
              float minY = 0;
              float offset = 0;
              for (int i = 0; i < drawTasks.size(); i++) {
                  DrawTask drawTask = drawTasks.get(i);
                  if (Math.abs(drawTask.scaleOffset) > offset) {  
                      minY = -drawTask.y;
                      offset = drawTask.scaleOffset;
                  }
              }
              return minY;
          }
          

          Android使用Canvas 2D实现循环菜单效果

          2.6 根据滑动方向重新计算每个item的偏移

          Item是需要移动的,因此在事件处理的时候一定要进行偏移处理,因此滑动过程需要对Y值进行有效处理,当然要避免为1,防止View出现缩小而不是滑动的效果。

              private void resetItemYOffset(int height, float centerOffset) {
                  for (int i = 0; i < drawTasks.size(); i++) {
          
                      DrawTask task = drawTasks.get(i);
                      task.y = (task.y + centerOffset);
                      float ratio = Math.abs(task.y) / (height / 2F);
                      if (ratio > 1f) {
                          ratio = 1f; 
                      }
                      task.outOffset = ((10 + circleRadius) * 3 / 4f) * (1 - ratio);
                  }
              }
          

          2.7 事件处理

          我们要支持Item移动,因此必然要处理TouchEvent,首先我们需要在ACTION_DOWN时拦截事件,其次需要处理ACTION_MOVE事件和ACTION_UP事件中产生的位置偏移。

          另外,我们保留系统内默认View对事件处理的方式,具体原理就是在onTouchEvent返回之前调用super.onTouchEvent方法

          super.onTouchEvent(event);
          return true;
          

          下面是事件处理完整的方法,基本是常规操作

          @Override
          public boolean onTouchEvent(MotionEvent event) {
          
              int action = event.getActionMasked();
              isTouchEventUp = false;
              switch (action) {
                  case MotionEvent.ACTION_DOWN:
                      offsetY = 0;
                      startEventX = event.getX() - getWidth() / 2F;
                      startEventY = event.getY() - getHeight() / 2F;
                      super.onTouchEvent(event);
                      return true;
                  case MotionEvent.ACTION_MOVE:
                      float eventX = event.getX();
                      float eventY = event.getY();
          
                      if (eventY < 0) {
                          eventY = 0;
                      }
                      if (eventX < 0) {
                          eventX = 0;
                      }
                      if (eventY > getWidth()) {
                          eventX = getWidth();
                      }
                      if (eventY > getHeight()) {
                          eventY = getHeight();
                      }
          
                      float currentX = eventX - getWidth() / 2F;
                      float currentY = eventY - getHeight() / 2F;
          
                      float dx = currentX - startEventX;
                      float dy = currentY - startEventY;
          
                      if (Math.abs(dx) < Math.abs(dy) && Math.abs(dy) >= slopTouch) {
                          isTouchMove = true;
                      }
          
                      if (!isTouchMove) {
                          break;
                      }
                      offsetY = dy;
                      startEventX = currentX;
                      startEventY = currentY;
                      postInvalidate();
                      super.onTouchEvent(event);
                      return true;
          
                  case MotionEvent.ACTION_CANCEL:
                  case MotionEvent.ACTION_OUTSIDE:
                  case MotionEvent.ACTION_UP:
                      isTouchMove = false;
                      isTouchEventUp = true;
                      offsetY = 0;
                      Log.d("eventup", "offsetY=" + offsetY);
                      postInvalidate();
                      break;
              }
          
              return super.onTouchEvent(event);
          }
          

          三、使用方法

          使用方法

                 LoopView loopView = findViewById(R.id.loopviews);
          
                 final List<LoopView.LoopItem> loopItems = new ArrayList<>();
          
                  int[] colors = {
                          Color.RED,
                          Color.CYAN,
                          Color.GRAY,
                          Color.GREEN,
                          Color.BLACK,
                          Color.MAGENTA,
                          0xffff9922,
                          0xffFF4081,
                          0xffFFEAC4
                  };
                  String[] items = {
                          "新闻",
                          "科技",
                          "历史",
                          "军事",
                          "小说",
                          "娱乐",
                          "电影",
                          "电视剧",
                  };
          
                  for (int i = 0; i < items.length; i++) {
                      LoopView.LoopItem loopItem = new LoopView.LoopItem(items[i], colors[i % colors.length]);
                      loopItems.add(loopItem);
                  }
                  loopView.setLoopItems(loopItems);
              }
          
              LoopView loopView = new LoopView(this);
              loopView.setLoopItems(loopItems);
              FrameLayout frameLayout = new FrameLayout(this);
              FrameLayout.MarginLayoutParams layoutParams = new FrameLayout.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,720);
              layoutParams.topMargin = 100;
              layoutParams.leftMargin = 50;
              layoutParams.rightMargin = 50;
             
             frameLayout.addView(loopView,layoutParams);
             setContentView(frameLayout);
          

          四、总结

          4.1 整体效果

          其实效果上还是可以的,本质上和ListView和RecyclerView思想类似,但是循环这一块儿其实和WheelView 思想类似。

          4.2 点击事件处理

          实际上本篇的View市支持点击事件的,当时点击区域没有判断,不过也是比较好处理,只要对DrawTask排序,保证最中间的Item可以点击即可,篇幅有限,这里就不处理了。

          4.3 全部代码

          public class LoopView extends View {
              private static final int MAX_VISIBLE_COUNT = 5;
          
              private TextPaint mTextPaint = null;
              private DisplayMetrics displayMetrics = null;
              private int mLineWidth = 1;
              private int mTextSize = 14;
              private int slopTouch = 0;
              private float circleRadius;
          
              private final List<DrawTask> drawTasks = new ArrayList<>();
              private final List<DrawTask> cacheDrawTasks = new ArrayList<>();
              private final List<LoopItem> loopItems = new ArrayList<>();
              boolean isInit = false;
          
              private float startEventX = 0;
              private float startEventY = 0;
              private boolean isTouchMove = false;
              private float offsetY = 0;
              boolean isTouchEventUp = false;
          
              public LoopView(Context context) {
                  this(context, null);
              }
          
              public LoopView(Context context, AttributeSet attrs) {
                  this(context, attrs, 0);
              }
          
              public LoopView(Context context, AttributeSet attrs, int defStyleAttr) {
                  super(context, attrs, defStyleAttr);
                  setClickable(true);
                  setFocusable(true);
                  setFocusableInTouchMode(true);
                  displayMetrics = context.getResources().getDisplayMetrics();
                  mTextPaint = createPaint();
                  slopTouch = ViewConfiguration.get(context).getScaledTouchSlop();
          
                  setLayerType(LAYER_TYPE_SOFTWARE, null);
          
                  initDesignEditMode();
              }
          
              private void initDesignEditMode() {
                  if (!isInEditMode()) return;
                  int[] colors = {
                          Color.RED,
                          Color.CYAN,
                          Color.YELLOW,
                          Color.GRAY,
                          Color.GREEN,
                          Color.BLACK,
                          Color.MAGENTA,
                          0xffff9922,
          
                  };
                  String[] items = {
                          "新闻",
                          "科技",
                          "历史",
                          "军事",
                          "小说",
                          "娱乐",
                          "电影",
                          "电视剧",
                  };
          
                  for (int i = 0; i < items.length; i++) {
          
                      LoopItem loopItem = new LoopItem(items[i], colors[i % colors.length]);
                      loopItems.add(loopItem);
                  }
              }
          
          
              @Override
              protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          
                  int widthMode = MeasureSpec.getMode(widthMeasureSpec);
                  int widthSize = MeasureSpec.getSize(widthMeasureSpec);
          
                  if (widthMode != MeasureSpec.EXACTLY) {
                      widthSize = displayMetrics.widthPixels;
                  }
          
                  int heightMode = MeasureSpec.getMode(heightMeasureSpec);
                  int heightSize = MeasureSpec.getSize(heightMeasureSpec);
          
                  if (heightMode != MeasureSpec.EXACTLY) {
                      heightSize = (int) (displayMetrics.widthPixels * 0.9f);
                  }
          
                  setMeasuredDimension(widthSize, heightSize);
              }
          
          
              private TextPaint createPaint() {
                  // 实例化画笔并打开抗锯齿
                  TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
                  paint.setAntiAlias(true);
                  paint.setStrokeWidth(dpTopx(mLineWidth));
                  paint.setTextSize(dpTopx(mTextSize));
                  return paint;
              }
          
              private float dpTopx(float dp) {
                  return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
              }
          
          
              /**
               * 基线到中线的距离=(Descent+Ascent)/2-Descent
               * 注意,实际获取到的Ascent是负数。公式推导过程如下:
               * 中线到BOTTOM的距离是(Descent+Ascent)/2,这个距离又等于Descent+中线到基线的距离,即(Descent+Ascent)/2=基线到中线的距离+Descent。
               */
              public static float getTextPaintBaseline(Paint p) {
                  Paint.FontMetrics fontMetrics = p.getFontMetrics();
                  return (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;
              }
          
              @Override
              protected void onSizeChanged(int w, int h, int oldw, int oldh) {
                  super.onSizeChanged(w, h, oldw, oldh);
          
                  circleRadius = Math.min(w / 2F, h / 2F) / MAX_VISIBLE_COUNT;
          
              }
          
          
              Comparator<DrawTask> drawTaskComparator = new Comparator<DrawTask>() {
                  @Override
                  public int compare(DrawTask left, DrawTask right) {
                      float dx = Math.abs(right.y) - Math.abs(left.y);
                      if (dx > 0) {
                          return 1;
                      }
                      if (dx == 0) {
                          return 0;
                      }
                      return -1;
                  }
              };
          
              Comparator<DrawTask> drawTaskComparatorY = new Comparator<DrawTask>() {
                  @Override
                  public int compare(DrawTask left, DrawTask right) {
                      float dx = left.y - right.y;
                      if (dx > 0) {
                          return 1;
                      }
                      if (dx == 0) {
                          return 0;
                      }
                      return -1;
                  }
              };
          
              @Override
              protected void onDraw(Canvas canvas) {
                  super.onDraw(canvas);
          
                  int width = getWidth();
                  int height = getHeight();
          
          
                  int id = canvas.save();
                  canvas.translate(width / 2F, height / 2F);
          
                  initCircle();
          
                  //前期重置,以便recycler复用
                  recyclerBefore(height);
                  //复用和移除
                  recycler();
                  //再次处理,防止view复用之后产生其他位移
                  recyclerAfter(height);
          
                  Collections.sort(drawTasks, drawTaskComparator);
          
                  for (int i = 0; i < drawTasks.size(); i++) {
                      drawTasks.get(i).draw(canvas, mTextPaint);
                  }
                  drawGuideline(canvas, width);
          
                  canvas.restoreToCount(id);
          
              }
          
              private float getMinYOffset() {
                  float minY = 0;
                  float offset = 0;
                  for (int i = 0; i < drawTasks.size(); i++) {
                      DrawTask drawTask = drawTasks.get(i);
                      if (Math.abs(drawTask.scaleOffset) > offset) {
                          minY = -drawTask.y;
                          offset = drawTask.scaleOffset;
                      }
                  }
                  return minY;
              }
          
              private void recyclerAfter(int height) {
                  if (isTouchEventUp) {
                      float centerOffset = getMinYOffset();
                      resetItemYOffset(height, centerOffset);
                  } else {
                      resetItemYOffset(height, 0);
                  }
              }
          
              private void recyclerBefore(int height) {
                  if (isTouchEventUp) {
                      float centerOffset = getMinYOffset();
                      resetItemYOffset(height, centerOffset);
                  } else {
                      resetItemYOffset(height, offsetY);
                  }
                  isTouchEventUp = false;
              }
          
              private void recycler() {
          
                  if (drawTasks.size() < (MAX_VISIBLE_COUNT - 2)) return;
          
                  Collections.sort(drawTasks, drawTaskComparatorY);
          
                  DrawTask head = drawTasks.get(0);
                  DrawTask tail = drawTasks.get(drawTasks.size() - 1);
                  int height = getHeight();
          
                  if (head.y < -(height / 2F + circleRadius)) {
                      drawTasks.remove(head);
                      addToCachePool(head);
                      head.setLoopItem(null);
                  } else {
                      DrawTask drawTask = getCachePool();
          
                      LoopItem loopItem = head.getLoopItem();
                      LoopItem preLoopItem = getPreLoopItem(loopItem);
                      drawTask.setLoopItem(preLoopItem);
          
                      drawTask.y = head.y - circleRadius * 2;
                      drawTasks.add(0, drawTask);
                  }
          
                  if (tail.y > (height / 2F + circleRadius)) {
                      drawTasks.remove(tail);
                      addToCachePool(tail);
                      tail.setLoopItem(null);
                  } else {
                      DrawTask drawTask = getCachePool();
          
                      LoopItem loopItem = tail.getLoopItem();
                      LoopItem nextLoopItem = getNextLoopItem(loopItem);
                      drawTask.setLoopItem(nextLoopItem);
                      drawTask.y = tail.y + circleRadius * 2;
                      drawTasks.add(drawTask);
                  }
              }
          
              private void resetItemYOffset(int height, float scaleOffset) {
                  for (int i = 0; i < drawTasks.size(); i++) {
                      DrawTask task = drawTasks.get(i);
                      task.y = (task.y + scaleOffset);
                      float ratio = Math.abs(task.y) / (height / 2F);
                      if (ratio > 1f) {
                          ratio = 1f;
                      }
                      task.scaleOffset = ((10 + circleRadius) * 3 / 4f) * (1 - ratio);
                  }
              }
          
              RectF guideRect = new RectF();
          
              private void drawGuideline(Canvas canvas, int width) {
          
                  if (!isInEditMode()) return;
          
                  mTextPaint.setColor(Color.BLACK);
                  mTextPaint.setStyle(Paint.Style.FILL);
                  int i = 0;
                  int counter = 0;
                  while (counter < MAX_VISIBLE_COUNT) {
                      float topY = i * 2 * circleRadius;
          
                      guideRect.left = -width / 2f;
                      guideRect.right = width / 2f;
          
                      guideRect.top = topY - 0.5f;
                      guideRect.bottom = topY + 0.5f;
          
                      canvas.drawRect(guideRect, mTextPaint);
                      counter++;
          
                      float bottomY = -i * 2 * circleRadius;
          
                      if (topY == bottomY) {
                          i++;
                          continue;
                      }
          
                      guideRect.top = bottomY - 0.5f;
                      guideRect.bottom = bottomY + 0.5f;
          
                      canvas.drawRect(guideRect, mTextPaint);
                      counter++;
                      i++;
                  }
              }
          
          
              private LoopItem getNextLoopItem(LoopItem loopItem) {
                  int index = loopItems.indexOf(loopItem);
          
                  if (index < loopItems.size() - 1) {
                      return loopItems.get(index + 1);
                  }
          
                  return loopItems.get(0);
              }
          
              private LoopItem getPreLoopItem(LoopItem loopItem) {
          
                  int index = loopItems.indexOf(loopItem);
          
                  if (index > 0) {
                      return loopItems.get(index - 1);
                  }
          
                  return loopItems.get(loopItems.size() - 1);
              }
          
              private DrawTask getCachePool() {
                  if (cacheDrawTasks.size() > 0) {
                      return cacheDrawTasks.remove(0);
                  }
          
                  DrawTask drawTask = createDrawTask();
                  return drawTask;
              }
          
              private void addToCachePool(DrawTask top) {
                  cacheDrawTasks.add(top);
              }
          
          
              private void initCircle() {
                  if (isInit) {
                      return;
                  }
                  isInit = true;
                  List<DrawTask> drawTaskList = new ArrayList<>();
                  int i = 0;
                  while (drawTaskList.size() < MAX_VISIBLE_COUNT) {
                      float topY = i * 2 * circleRadius;
          
                      DrawTask drawTask = new DrawTask(0, topY, circleRadius);
                      drawTaskList.add(drawTask);
                      float bottomY = -i * 2 * circleRadius;
                      if (topY == bottomY) {
                          i++;
                          continue;
                      }
                      drawTask = new DrawTask(0, bottomY, circleRadius);
                      drawTaskList.add(drawTask);
                      i++;
                  }
          
                  Collections.sort(drawTaskList, new Comparator<DrawTask>() {
                      @Override
                      public int compare(DrawTask left, DrawTask right) {
                          float dx = left.y - right.y;
                          if (dx > 0) {
                              return 1;
                          }
                          if (dx == 0) {
                              return 0;
                          }
                          return -1;
                      }
                  });
          
                  drawTasks.clear();
                  if (loopItems.size() == 0) return;
          
                  for (int j = 0; j < drawTaskList.size(); j++) {
                      drawTaskList.get(j).setLoopItem(loopItems.get(j % loopItems.size()));
                  }
                  drawTasks.addAll(drawTaskList);
          
              }
          
              private DrawTask createDrawTask() {
                  DrawTask drawTask = new DrawTask(0, 0, circleRadius);
                  return drawTask;
              }
          
              @Override
              public boolean onTouchEvent(MotionEvent event) {
          
                  int action = event.getActionMasked();
                  isTouchEventUp = false;
                  switch (action) {
                      case MotionEvent.ACTION_DOWN:
                          offsetY = 0;
                          startEventX = event.getX() - getWidth() / 2F;
                          startEventY = event.getY() - getHeight() / 2F;
          
                          return true;
                      case MotionEvent.ACTION_MOVE:
                          float eventX = event.getX();
                          float eventY = event.getY();
          
                          if (eventY < 0) {
                              eventY = 0;
                          }
                          if (eventX < 0) {
                              eventX = 0;
                          }
                          if (eventY > getWidth()) {
                              eventX = getWidth();
                          }
                          if (eventY > getHeight()) {
                              eventY = getHeight();
                          }
          
                          float currentX = eventX - getWidth() / 2F;
                          float currentY = eventY - getHeight() / 2F;
          
                          float dx = currentX - startEventX;
                          float dy = currentY - startEventY;
          
                          if (Math.abs(dx) < Math.abs(dy) && Math.abs(dy) >= slopTouch) {
                              isTouchMove = true;
                          }
          
                          if (!isTouchMove) {
                              break;
                          }
                          offsetY = dy;
                          startEventX = currentX;
                          startEventY = currentY;
                          postInvalidate();
          
                          return true;
          
                      case MotionEvent.ACTION_CANCEL:
                      case MotionEvent.ACTION_OUTSIDE:
                      case MotionEvent.ACTION_UP:
                          isTouchMove = false;
                          isTouchEventUp = true;
                          offsetY = 0;
                          Log.d("eventup", "offsetY=" + offsetY);
                          invalidate();
                          break;
                  }
          
                  return super.onTouchEvent(event);
              }
          
          
              public void setLoopItems(List<LoopItem> loopItems) {
                  this.loopItems.clear();
                  this.drawTasks.clear();
                  this.cacheDrawTasks.clear();
                  this.isInit = false;
          
                  if (loopItems != null) {
                      this.loopItems.addAll(loopItems);
                  }
          
                  postInvalidate();
              }
          
          
              public static class DrawTask<T extends LoopItem> {
          
                  private T loopItem;
                  private float radius;
                  private float x;
                  private float y;
                  private float scaleOffset = 0;
          
          
                  public DrawTask(float x, float y, float radius) {
                      this.radius = radius;
                      this.x = x;
                      this.y = y;
                  }
          
                  public void setLoopItem(T loopItem) {
                      this.loopItem = loopItem;
                  }
          
                  public void draw(Canvas canvas, TextPaint textPaint) {
                      if (loopItem == null) return;
          
                      textPaint.setColor(loopItem.getColor());
                      textPaint.setStyle(Paint.Style.FILL);
                      textPaint.setShadowLayer(10, 0, 5, 0x99444444);
                      canvas.drawCircle(x, y, radius + scaleOffset, textPaint);
          
                      textPaint.setColor(Color.WHITE);
                      textPaint.setStyle(Paint.Style.FILL);
          
                      String text = loopItem.getText();
                      float textWidth = textPaint.measureText(text);
                      float baseline = getTextPaintBaseline(textPaint);
                      textPaint.setShadowLayer(0, 0, 0, Color.TRANSPARENT);
                      canvas.drawText(text, -textWidth / 2, y + baseline, textPaint);
          
                  }
          
                  public T getLoopItem() {
                      return loopItem;
                  }
              }
          
              public static class LoopItem {
                  private int color;
                  private String text;
          
                  public LoopItem(String text, int color) {
                      this.color = color;
                      this.text = text;
                  }
          
                  public int getColor() {
                      return color;
                  }
          
                  public void setColor(int color) {
                      this.color = color;
                  }
          
                  public String getText() {
                      return text;
                  }
          
                  public void setText(String text) {
                      this.text = text;
                  }
              }
          
          }
          

          以上就是Android使用Canvas 2D实现循环菜单效果的详细内容,更多关于Android Canvas 2D循环菜单的资料请关注其它相关文章!

          免费资源网 – https://freexyz.cn/

          声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。