話不多說看效果

代碼在這里
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){
···
}
}
});
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/265948.html
標籤:其他
