android 自定义 控件 旋转菜单 转盘 rotatemenu java 源码

话不多说看效果

在这里插入图片描述

代码在这里

public class YPPercentRotateMenuView extends View {
    
    

    private static final String TAG = "YPPercentRotateMenuView";

    Context context;

    public YPPercentRotateMenuView(Context context) {
    
    
        super(context);
        init(context);
    }

    public YPPercentRotateMenuView(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
        init(context);
    }

    public YPPercentRotateMenuView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
        init(context);
    }

    Paint paint;
    ArrayList<Item> items;

    //默认item显示个数:7
    int itemCount = 7;

    //默认item尺寸与控件高度比值:.1
    float itemWidthR = .1f;

    //默认item最小最大尺寸比值:.5f
    float itemZoomR = .5f;

    //默认转盘中心点到左边缘距离与高度的比值:.1
    float centerOffsetR = .1f;

    //默认最靠近左边缘的item中心到左边缘距离与高度的比值:.1;(全称:offset from center of item near edge to edge ratio)
    float oFCOINE2ER = .1f;

    //默认转盘半径与高度的比值:.3
    float radiusR = .3f;

    //默认文字尺寸与高度的比值:.04
    float textSizeR = .04f;

    //默认文字最小最大尺寸的比值:.7
    float textZoomR = .7f;

    //默认文字到图标的距离与高度的比值:.015;(全称:offset from text to item ratio)
    float oFT2I = .015f;

    //默认惯性起始速度倍率:16;(全称:inertia origin speed ratio)
    float iOSR = 16;

    //默认惯性速度和修正角度衰减:.95
    float reduction = .95f;

    //默认惯性速度最小阈值:0.18°
    double inertiaThreshold = Math.PI / 100;

    //默认修正角度最小阈值:0.018°
    double correctThreshold = Math.PI / 1000;

    //默认惯性和修正绘制的间隔时间(毫秒):20
    long drawInterval = 20;

    void init(Context context) {
    
    
        this.context = context;
        handler = new Handler(context.getMainLooper());

        paint = new Paint();
        paint.setAntiAlias(true);

        paintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    }

    PaintFlagsDrawFilter paintFlagsDrawFilter;

    int width, height;

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    
    
        super.onLayout(changed, left, top, right, bottom);
        width = getWidth();
        height = getHeight();
        if (items == null) {
    
    
            return;
        }
        invalidateValue();
    }

    @Override
    protected void onDraw(Canvas canvas) {
    
    
        if (items == null) {
    
    
            return;
        }
        canvas.setDrawFilter(paintFlagsDrawFilter);
        //item和文字尺寸偏移量
        double itemSizeOffset = offsetRadian / intervalRadian * itemZoomGrad;
        double textSizeOffset = offsetRadian / intervalRadian * textZoomGrad;
        if (itemCountIsOdd) {
    
    
            //画下半部分的item
            for (int i = 0; i < halfCount + 1; i++) {
    
    
                Item item = items.get(((i + index) % itemTotalCount + itemTotalCount) % itemTotalCount);
                Bitmap bitmap = item.getBitmap();
                String text = item.getText();
                int bw = bitmap.getWidth();
                int bh = bitmap.getHeight();

                //计算item的中心位置
                int x = (int) (radius * Math.cos(i * intervalRadian + offsetRadian) + centerOffset);
                int y = (int) (radius * Math.sin(i * intervalRadian + offsetRadian) + height / 2);

                //计算当前item大小的一半
                int currentHalfWidth = (int) (halfItemWidth * (1 - itemZoomGrad * i - (i == 0 ? Math.abs(itemSizeOffset) : itemSizeOffset)));
                if (isSelectItem) {
    
    
                    if (actionIndex - halfCount == i) {
    
    
                        currentHalfWidth = (int) (currentHalfWidth * .8);
                    }
                }

                //计算当前文字大小
                int currentTextSize = (int) (textSize * (1 - textZoomGrad * i - (i == 0 ? Math.abs(textSizeOffset) : textSizeOffset)));

                //画item
                canvas.drawBitmap(bitmap, new Rect(0, 0, bw, bh), new Rect(x - currentHalfWidth, y - currentHalfWidth, x + currentHalfWidth, y + currentHalfWidth), paint);

                //画文字
                paint.setTextSize(currentTextSize);
                canvas.drawText(text, x + currentHalfWidth + oft2i, y + currentTextSize / 2f, paint);
            }

            //画上半部分item
            for (int i = 1; i < halfCount + 1; i++) {
    
    

                Item item = items.get(Math.abs((itemTotalCount - i + index % itemTotalCount) % itemTotalCount));
                Bitmap bitmap = item.getBitmap();
                String text = item.getText();
                int bw = bitmap.getWidth();
                int bh = bitmap.getHeight();

                //计算item的中心位置
                int x = (int) (radius * Math.cos(i * intervalRadian - offsetRadian) + centerOffset);
                int y = (int) (height / 2 - radius * Math.sin(i * intervalRadian - offsetRadian));

                //计算当前item大小的一半
                int currentHalfWidth = (int) (halfItemWidth * (1 - itemZoomGrad * i + itemSizeOffset));
                if (isSelectItem) {
    
    
                    if (actionIndex == halfCount - i) {
    
    
                        currentHalfWidth = (int) (currentHalfWidth * .8);
                    }
                }

                //计算当前文字大小
                int currentTextSize = (int) (textSize * (1 - textZoomGrad * i + textSizeOffset));

                //画item
                canvas.drawBitmap(bitmap, new Rect(0, 0, bw, bh), new Rect(x - currentHalfWidth, y - currentHalfWidth, x + currentHalfWidth, y + currentHalfWidth), paint);

                //画文字
                paint.setTextSize(currentTextSize);
                canvas.drawText(text, x + currentHalfWidth + oft2i, y + currentTextSize / 2f, paint);
            }
        } else {
    
    

            //画下半部分的item
            for (int i = 0; i < halfCount; i++) {
    
    
                Item item = items.get(((i + index) % itemTotalCount + itemTotalCount) % itemTotalCount);
                Bitmap bitmap = item.getBitmap();
                String text = item.getText();
                int bw = bitmap.getWidth();
                int bh = bitmap.getHeight();

                //计算item的中心位置
                int x = (int) (radius * Math.cos((i + .5) * intervalRadian + offsetRadian) + centerOffset);
                int y = (int) (radius * Math.sin((i + .5) * intervalRadian + offsetRadian) + height / 2);

                //计算当前item大小的一半
                int currentHalfWidth = (int) (halfItemWidth * (1 - itemZoomGrad * i - itemSizeOffset));

                //计算当前文字大小
                int currentTextSize = (int) (textSize * (1 - textZoomGrad * i - textSizeOffset));
                if (isSelectItem) {
    
    
                    if (actionIndex - halfCount == i) {
    
    
                        currentHalfWidth = (int) (currentHalfWidth * .8);
                    }
                }

                //画item
                canvas.drawBitmap(bitmap, new Rect(0, 0, bw, bh), new Rect(x - currentHalfWidth, y - currentHalfWidth, x + currentHalfWidth, y + currentHalfWidth), paint);

                //画文字
                paint.setTextSize(currentTextSize);
                canvas.drawText(text, x + currentHalfWidth + oft2i, y + currentTextSize / 2f, paint);
            }

            //画上半部分的item
            for (int i = 0; i < halfCount; i++) {
    
    

                Item item = items.get(Math.abs((itemTotalCount - i - 1 + index % itemTotalCount) % itemTotalCount));
                Bitmap bitmap = item.getBitmap();
                String text = item.getText();
                int bw = bitmap.getWidth();
                int bh = bitmap.getHeight();

                //计算item的中心位置
                int x = (int) (radius * Math.cos((i + .5) * intervalRadian - offsetRadian) + centerOffset);
                int y = (int) (height / 2 - radius * Math.sin((i + .5) * intervalRadian - offsetRadian));

                //计算当前item大小的一半
                int currentHalfWidth = (int) (halfItemWidth * (1 - itemZoomGrad * i + itemSizeOffset));
                if (isSelectItem) {
    
    
                    if (actionIndex == halfCount - i - 1) {
    
    
                        currentHalfWidth = (int) (currentHalfWidth * .8);
                    }
                }

                //计算当前文字大小
                int currentTextSize = (int) (textSize * (1 - textZoomGrad * i + textSizeOffset));

                //画item
                canvas.drawBitmap(bitmap, new Rect(0, 0, bw, bh), new Rect(x - currentHalfWidth, y - currentHalfWidth, x + currentHalfWidth, y + currentHalfWidth), paint);

                //画文字
                paint.setTextSize(currentTextSize);
                canvas.drawText(text, x + currentHalfWidth + oft2i, y + currentTextSize / 2f, paint);
            }
        }
    }

    boolean itemCountIsOdd;
    int itemTotalCount, halfItemWidth, centerOffset, radius, oFCOINE2E, textSize, oft2i, halfCount;
    float itemZoomGrad, textZoomGrad, intervalRadian;
    double aCos, radianOfItemNearEdge, halfItemSpanRadian;

    void invalidateValue() {
    
    
        itemTotalCount = items.size();//item集合总数
        itemCount = Math.min(itemCount, itemTotalCount);//避免item集合总输比应显示item数少
        itemCountIsOdd = itemCount % 2 == 1;//判断显示item数奇偶性
        halfItemWidth = (int) (height * itemWidthR / 2);//item原始宽度
        centerOffset = (int) (height * centerOffsetR);//转盘中心偏移量
        radius = (int) (height * radiusR);//转盘半径
        oFCOINE2E = (int) (height * oFCOINE2ER);//最靠边item中心偏移量
        textSize = (int) (height * textSizeR);//原始text尺寸
        oft2i = (int) (height * oFT2I);//text与item间距离
        aCos = Math.acos((oFCOINE2E - centerOffset) / (float) radius);//最靠边item中心与左边0°夹角
        radianOfItemNearEdge = Math.PI - aCos;//最靠边item中心与右边0°夹角
        halfItemSpanRadian = Math.sin((double) halfItemWidth / radius);//半个item弧度跨度

        if (itemCountIsOdd) {
    
    
            halfCount = (itemCount - 1) / 2;//显示item数一半
            intervalRadian = (float) (aCos / halfCount);//单个间隔弧度
        } else {
    
    
            halfCount = itemCount / 2;
            intervalRadian = (float) (aCos / (halfCount - .5));
        }

        itemZoomGrad = (1 - itemZoomR) / (halfCount);//item缩放梯度
        textZoomGrad = (1 - textZoomR) / (halfCount);//text缩放梯度
    }

    double moveRadian_before, moveRadian_after, speed;

    double downRadian;

    int index = 0;
    int actionIndex = -1;
    boolean isSelectItem = false;

    double currentRadian = 0;
    double markRadian = 0;

    double offsetRadian = 0;

    Handler handler;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
    
        handler.removeCallbacks(inertiaNCorrectTask);
        switch (event.getAction()) {
    
    
            case MotionEvent.ACTION_DOWN:
                //记录按下位置
                downRadian = calculateRadian(event.getX(), event.getY());

                actionIndex = getActionItem(event.getX(), event.getY());
                if (actionIndex != -1 && offsetRadian == 0) {
    
    
                    isSelectItem = true;
                    invalidate();
                }
                return true;
            case MotionEvent.ACTION_MOVE:
                //当前弧度数 = 标记弧度 + 滑动弧度
                currentRadian = downRadian - calculateRadian(event.getX(), event.getY()) + markRadian;

                calculateNInvalidate();

                if (actionIndex != getActionItem(event.getX(), event.getY())) {
    
    
                    isSelectItem = false;
                }

                //记录滑动位置
                moveRadian_before = moveRadian_after;
                moveRadian_after = calculateRadian(event.getX(), event.getY());
                break;

            case MotionEvent.ACTION_UP:
                //更新标记弧度数
                markRadian = downRadian - calculateRadian(event.getX(), event.getY()) + markRadian;

                if (isSelectItem) {
    
    
                    isSelectItem = false;
                    invalidate();
                    int i = (actionIndex - halfCount + index % itemTotalCount + itemTotalCount) % itemTotalCount;
                    Log.e(TAG, "onTouchEvent: 点击了:" + i);
                    if (listener != null) {
    
    
                        listener.onSelect(i);
                    }
                }
                currentRadian = markRadian;
                speed = (moveRadian_before - moveRadian_after) * iOSR;
                moveRadian_before = 0;
                moveRadian_after = 0;
                handler.post(inertiaNCorrectTask);

                break;
        }
        return super.onTouchEvent(event);
    }

    Runnable inertiaNCorrectTask = new Runnable() {
    
    
        @Override
        public void run() {
    
    
            if (Math.abs(speed) > inertiaThreshold && !isSelectItem) {
    
    
                currentRadian = currentRadian + speed;
                speed = speed * reduction;
                calculateNInvalidate();
                markRadian = currentRadian;
                handler.postDelayed(this, drawInterval);
            } else {
    
    
                if (offsetRadian != 0) {
    
    
                    if (Math.abs(offsetRadian) < correctThreshold) {
    
    
                        currentRadian = currentRadian + offsetRadian;
                    } else {
    
    
                        currentRadian = currentRadian + offsetRadian * (1 - reduction);
                    }
                    markRadian = currentRadian;
                    calculateNInvalidate();
                    postDelayed(this, drawInterval);
                }
            }
        }
    };

    int getActionItem(float x, float y) {
    
    
        if (isInCircle(x, y, radius + halfItemWidth) && !isInCircle(x, y, radius - halfItemWidth)) {
    
    
            double actionRadian = calculateRadian(x, y);
            int i = (int) ((actionRadian - (radianOfItemNearEdge - halfItemSpanRadian)) / intervalRadian);
            if (actionRadian < radianOfItemNearEdge + intervalRadian * i + halfItemSpanRadian && actionRadian > radianOfItemNearEdge + intervalRadian * i - halfItemSpanRadian) {
    
    
                return i;
            }
        }
        return -1;
    }

    boolean isInCircle(float x, float y, float radius) {
    
    
        return Math.sqrt(Math.pow((x - centerOffset), 2) + Math.pow((y - height / 2), 2)) <= radius;
    }

    void calculateNInvalidate() {
    
    
        //计算居中item序号
        double a = currentRadian > 0 ? .5 * intervalRadian : -.5f * intervalRadian;
        index = (int) ((currentRadian + a) / intervalRadian);

        //计算item偏移弧度
        double tempOffsetRadian = BigDecimal.valueOf(currentRadian % intervalRadian).doubleValue();
        if (Math.abs(tempOffsetRadian) < intervalRadian / 2) {
    
    
            offsetRadian = -tempOffsetRadian;
        } else {
    
    
            if (tempOffsetRadian > 0) {
    
    
                offsetRadian = intervalRadian - tempOffsetRadian;
            } else {
    
    
                offsetRadian = -tempOffsetRadian - intervalRadian;
            }
        }
        invalidate();
    }

    double calculateRadian(float x, float y) {
    
    
        double chord = Math.sqrt(Math.pow(x - centerOffset, 2) + Math.pow(y - height / 2, 2));
        if (y > height / 2) {
    
    
            return Math.acos((x - centerOffset) / chord) + Math.PI;
        } else {
    
    
            return Math.PI - Math.acos((x - centerOffset) / chord);
        }
    }


    public void setItems(ArrayList<Item> items) {
    
    
        this.items = items;
        invalidateValue();
        invalidate();
    }

    private OnSelectInterface listener;

    public void setListener(OnSelectInterface listener) {
    
    
        this.listener = listener;
    }

    public interface OnSelectInterface {
    
    
        void onSelect(int i);
    }

    public static class Item {
    
    
        Bitmap bitmap;
        String text;

        public Item(Bitmap bitmap, String text) {
    
    
            this.bitmap = bitmap;
            this.text = text;
        }

        public Bitmap getBitmap() {
    
    
            return bitmap;
        }

        public String getText() {
    
    
            return text;
        }
    }
}

怎么用

1.先把上面的类添加到你的项目里

在这里插入图片描述

2.实例一个转盘控件

  • 在xml里面实例:

	<com.ns.myrotatemenuapplication.YPPercentRotateMenuView
			android:id="@+id/ypprmv"
	        android:layout_width="match_parent"
	        android:layout_height="match_parent"/>
        
  • 在代码中实例:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        YPPercentRotateMenuView ypprmv = new YPPercentRotateMenuView(this);
        ypprmv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        setContentView(ypprmv);
    }
    
  • 注意:无论在哪里实例,必须要指定宽高,所有属性都是根据控件高度决定

3.定义转盘属性


public class YPPercentRotateMenuView extends View {
    
    

	···

	//默认item显示个数:7
    int itemCount = 7;

    //默认item尺寸与控件高度比值:.1
    float itemWidthR = .1f;

    //默认item最小最大尺寸比值:.5f
    float itemZoomR = .5f;

    //默认转盘中心点到左边缘距离与高度的比值:.1
    float centerOffsetR = .1f;

    //默认最靠近左边缘的item中心到左边缘距离与高度的比值:.1;(全称:offset from center of item near edge to edge ratio)
    float oFCOINE2ER = .1f;

    //默认转盘半径与高度的比值:.3
    float radiusR = .3f;

    //默认文字尺寸与高度的比值:.04
    float textSizeR = .04f;

    //默认文字最小最大尺寸的比值:.7
    float textZoomR = .7f;

    //默认文字到图标的距离与高度的比值:.015;(全称:offset from text to item ratio)
    float oFT2I = .015f;

    //默认惯性起始速度倍率:16;(全称:inertia origin speed ratio)
    float iOSR = 16;

    //默认惯性速度和修正角度衰减:.95
    float reduction = .95f;

    //默认惯性速度最小阈值:0.18°
    double inertiaThreshold = Math.PI / 100;

    //默认修正角度最小阈值:0.018°
    double correctThreshold = Math.PI / 1000;

    //默认惯性和修正绘制的间隔时间(毫秒):20
    long drawInterval = 20;
    
    ···
}
  • 把上面代码的数字改改,适应你们的设计图
  • 当然,源码都在这了,想怎么改就怎么改,主要是改paint的属性,其他的绘制最好还是不要动,我自己写的代码,都已经看不懂了,可见计算有多复杂

4.设置转盘数据

  • 设置item和文字

      ArrayList<YPPercentRotateMenuView.Item> items = new ArrayList<>();
      Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.ic);
      for (int i = 0; i < 20; i++) {
    
    
          items.add(new YPPercentRotateMenuView.Item(bitmap, "item" + i));
      }
      ypprmv.setItems(items);
    
  • 设置点击事件

      ypprmv.setListener(new YPPercentRotateMenuView.OnSelectInterface() {
    
    
          @Override
          public void onSelect(int i) {
    
    
              switch (i){
    
    
                  ···
              }
          }
      });

猜你喜欢

转载自blog.csdn.net/stickyourass/article/details/114324249