话不多说看效果

代码在这里
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;
int itemCount = 7;
float itemWidthR = .1f;
float itemZoomR = .5f;
float centerOffsetR = .1f;
float oFCOINE2ER = .1f;
float radiusR = .3f;
float textSizeR = .04f;
float textZoomR = .7f;
float oFT2I = .015f;
float iOSR = 16;
float reduction = .95f;
double inertiaThreshold = Math.PI / 100;
double correctThreshold = Math.PI / 1000;
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);
double itemSizeOffset = offsetRadian / intervalRadian * itemZoomGrad;
double textSizeOffset = offsetRadian / intervalRadian * textZoomGrad;
if (itemCountIsOdd) {
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();
int x = (int) (radius * Math.cos(i * intervalRadian + offsetRadian) + centerOffset);
int y = (int) (radius * Math.sin(i * intervalRadian + offsetRadian) + height / 2);
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)));
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);
}
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();
int x = (int) (radius * Math.cos(i * intervalRadian - offsetRadian) + centerOffset);
int y = (int) (height / 2 - radius * Math.sin(i * intervalRadian - offsetRadian));
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));
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 {
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();
int x = (int) (radius * Math.cos((i + .5) * intervalRadian + offsetRadian) + centerOffset);
int y = (int) (radius * Math.sin((i + .5) * intervalRadian + offsetRadian) + height / 2);
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);
}
}
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);
}
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();
int x = (int) (radius * Math.cos((i + .5) * intervalRadian - offsetRadian) + centerOffset);
int y = (int) (height / 2 - radius * Math.sin((i + .5) * intervalRadian - offsetRadian));
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));
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();
itemCount = Math.min(itemCount, itemTotalCount);
itemCountIsOdd = itemCount % 2 == 1;
halfItemWidth = (int) (height * itemWidthR / 2);
centerOffset = (int) (height * centerOffsetR);
radius = (int) (height * radiusR);
oFCOINE2E = (int) (height * oFCOINE2ER);
textSize = (int) (height * textSizeR);
oft2i = (int) (height * oFT2I);
aCos = Math.acos((oFCOINE2E - centerOffset) / (float) radius);
radianOfItemNearEdge = Math.PI - aCos;
halfItemSpanRadian = Math.sin((double) halfItemWidth / radius);
if (itemCountIsOdd) {
halfCount = (itemCount - 1) / 2;
intervalRadian = (float) (aCos / halfCount);
} else {
halfCount = itemCount / 2;
intervalRadian = (float) (aCos / (halfCount - .5));
}
itemZoomGrad = (1 - itemZoomR) / (halfCount);
textZoomGrad = (1 - textZoomR) / (halfCount);
}
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() {
double a = currentRadian > 0 ? .5 * intervalRadian : -.5f * intervalRadian;
index = (int) ((currentRadian + a) / intervalRadian);
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.实例一个转盘控件
<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 {
···
int itemCount = 7;
float itemWidthR = .1f;
float itemZoomR = .5f;
float centerOffsetR = .1f;
float oFCOINE2ER = .1f;
float radiusR = .3f;
float textSizeR = .04f;
float textZoomR = .7f;
float oFT2I = .015f;
float iOSR = 16;
float reduction = .95f;
double inertiaThreshold = Math.PI / 100;
double correctThreshold = Math.PI / 1000;
long drawInterval = 20;
···
}
- 把上面代码的数字改改,适应你们的设计图
- 当然,源码都在这了,想怎么改就怎么改,主要是改paint的属性,其他的绘制最好还是不要动,我自己写的代码,都已经看不懂了,可见计算有多复杂
4.设置转盘数据
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){
···
}
}
});