├── README.md └── RoundMenuView ├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── roundmenuview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── roundmenuview │ │ │ ├── MainActivity.java │ │ │ └── PieChart │ │ │ ├── MenuChart.java │ │ │ ├── PieData.java │ │ │ └── utils │ │ │ └── PieUtils.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ ├── btm_chuan.png │ │ ├── btm_hui.png │ │ ├── btm_jiang.png │ │ ├── btm_lu.png │ │ ├── btm_min.png │ │ ├── btm_xiang.png │ │ ├── btm_yue.png │ │ ├── ic_launcher.png │ │ └── menu.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── roundmenuview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | ### MenuChart 2 | 3 | ##### 可以设置不同角度,起始角度,不同的模块间比例。 4 | 具体请移步博客地址:[http://blog.csdn.net/qian520ao/article/details/60509040](http://blog.csdn.net/qian520ao/article/details/60509040)   5 | 6 | 7 | ### 二、效果图 8 | 9 | ![这里写图片描述](http://img.blog.csdn.net/20170305180056125?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 10 | -------------------------------------------------------------------------------- /RoundMenuView/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /RoundMenuView/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /RoundMenuView/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /RoundMenuView/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /RoundMenuView/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /RoundMenuView/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /RoundMenuView/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RoundMenuView/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /RoundMenuView/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /RoundMenuView/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.0" 6 | defaultConfig { 7 | applicationId "com.example.roundmenuview" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.0.0' 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /RoundMenuView/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidStudio\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /RoundMenuView/app/src/androidTest/java/com/example/roundmenuview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.roundmenuview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.roundmenuview", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RoundMenuView/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /RoundMenuView/app/src/main/java/com/example/roundmenuview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.roundmenuview; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | 9 | import com.example.roundmenuview.PieChart.MenuChart; 10 | import com.example.roundmenuview.PieChart.PieData; 11 | import com.example.roundmenuview.PieChart.utils.PieUtils; 12 | 13 | import java.util.ArrayList; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | private ArrayList mPieDatas = new ArrayList<>(); 18 | // 颜色表 19 | private int[] mColors = {0xffff6944, 0xff63ff9e, 0xff38474e, 0xff5effdd, 0xffb579ff, 0xfff4ea2a}; 20 | private int[] pieLocation; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | 27 | initData(); 28 | final MenuChart menuChart = (MenuChart) findViewById(R.id.menuChart); 29 | menuChart.setPieData(mPieDatas); 30 | menuChart.setStartAngle(180); //设置起始角度 31 | menuChart.setPieShowAngle(180);//设置总共角度 32 | menuChart.setCenterBitmap(R.mipmap.menu, PieUtils.dp2px(MainActivity.this, 60), PieUtils.dp2px(MainActivity.this, 60)); 33 | menuChart.setTouchCenterTextSize(30); 34 | pieLocation = new int[2]; 35 | findViewById(R.id.btn_click).setOnTouchListener(new View.OnTouchListener() { 36 | @Override 37 | public boolean onTouch(View v, MotionEvent event) { 38 | switch (event.getAction()) { 39 | case MotionEvent.ACTION_DOWN: 40 | if (menuChart.getVisibility() == View.GONE) { 41 | menuChart.showStartAnim(); 42 | } else { 43 | menuChart.showEndAnim(); 44 | } 45 | return true; 46 | 47 | case MotionEvent.ACTION_MOVE: 48 | case MotionEvent.ACTION_UP: 49 | 50 | menuChart.getLocationOnScreen(pieLocation); 51 | menuChart.onPieTouchEvent(event, event.getRawX() - pieLocation[0], event.getRawY() - pieLocation[1]); 52 | 53 | return true; 54 | } 55 | 56 | 57 | return false; 58 | } 59 | }); 60 | 61 | 62 | } 63 | 64 | 65 | private void initData() { 66 | PieData pieData = new PieData(); 67 | pieData.setName("不辣,川菜一点都不辣 T_T"); 68 | pieData.setWeight(1); 69 | pieData.setName_label("CHUAN"); 70 | pieData.setLabelColor(0xffd81e06); 71 | pieData.setDrawableId(R.mipmap.btm_chuan); 72 | mPieDatas.add(pieData); 73 | 74 | 75 | PieData pieData2 = new PieData(); 76 | pieData2.setName("咸,鲜,清淡"); 77 | pieData2.setWeight(1); 78 | pieData2.setName_label("YUE"); 79 | pieData2.setDrawableId(R.mipmap.btm_yue); 80 | pieData2.setLabelColor(0xff90f895); 81 | mPieDatas.add(pieData2); 82 | 83 | 84 | PieData pieData5 = new PieData(); 85 | pieData5.setName("重油盐辣,腊味"); 86 | pieData5.setWeight(1); 87 | pieData5.setName_label("XIANG"); 88 | pieData5.setDrawableId(R.mipmap.btm_xiang); 89 | pieData5.setLabelColor(0xffe16531); 90 | mPieDatas.add(pieData5); 91 | 92 | PieData pieData4 = new PieData(); 93 | pieData4.setName("咸甜"); 94 | pieData4.setWeight(1); 95 | pieData4.setName_label("MIN"); 96 | pieData4.setDrawableId(R.mipmap.btm_min); 97 | pieData4.setLabelColor(0xfff5c9cb); 98 | mPieDatas.add(pieData4); 99 | 100 | 101 | PieData pieData3 = new PieData(); 102 | pieData3.setName("甜,黄酒味"); 103 | pieData3.setName_label("SU"); 104 | pieData3.setWeight(1); 105 | pieData3.setDrawableId(R.mipmap.btm_jiang); 106 | pieData3.setLabelColor(0xfff4ed61); 107 | mPieDatas.add(pieData3); 108 | 109 | PieData pieData6 = new PieData(); 110 | pieData6.setName("鲜,浓油赤酱"); 111 | pieData6.setWeight(1); 112 | pieData6.setName_label("LU"); 113 | pieData6.setDrawableId(R.mipmap.btm_lu); 114 | pieData6.setLabelColor(0xff944a48); 115 | mPieDatas.add(pieData6); 116 | 117 | } 118 | 119 | 120 | // 闽 f5c9cb 川d81e06 粤90f895 鲁90f895 苏#f4ed61 湘菜#e16531 121 | } 122 | -------------------------------------------------------------------------------- /RoundMenuView/app/src/main/java/com/example/roundmenuview/PieChart/MenuChart.java: -------------------------------------------------------------------------------- 1 | package com.example.roundmenuview.PieChart; 2 | 3 | import android.animation.TimeInterpolator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Matrix; 11 | import android.graphics.Paint; 12 | import android.graphics.PorterDuff; 13 | import android.graphics.PorterDuffXfermode; 14 | import android.graphics.RectF; 15 | import android.support.annotation.Nullable; 16 | import android.util.AttributeSet; 17 | import android.util.Log; 18 | import android.view.MotionEvent; 19 | import android.view.View; 20 | import android.view.WindowManager; 21 | import android.view.animation.AccelerateDecelerateInterpolator; 22 | import android.widget.Toast; 23 | 24 | import com.example.roundmenuview.PieChart.utils.PieUtils; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | 31 | public class MenuChart extends View { 32 | public static final String TAG = "QDX"; 33 | //默认的宽高都为屏幕宽度/高度 34 | private int DEFAULT_HEIGHT; 35 | private int DEFAULT_WIDTH; 36 | //宽高 37 | private int mWidth; 38 | private int mHeight; 39 | //数据 40 | private ArrayList mPieData = new ArrayList<>(); 41 | // 每块模块的 间隔 以下简称饼状图像为模块 42 | private float PIE_SPACING = 2f; 43 | //整个view展示的角度, 360为圆 44 | private int PIE_VIEW_ANGLE = 360; 45 | /** 46 | * animator 动画时候绘制的角度,最大为 PIE_VIEW_ANGLE 47 | */ 48 | private float animatedValue; 49 | //绘制模块 以及 弹出模块上的中心文字 50 | private Paint mPaint = new Paint(); 51 | //画在边缘上的 缩写 52 | private Paint mLabelPaint = new Paint(); 53 | //XferMode御用的笔 54 | private Paint mXPaint = new Paint(); 55 | //饼状图初始绘制角度 56 | private float startAngle = 0; 57 | /** 58 | * rectF 显示bitMap模块 59 | * rectFGold 白金色圈 60 | * rectFIn 白色内圈 61 | * rectFLabel 标签外圈 62 | */ 63 | private RectF rectF, rectFGold, rectFIn, rectFLabel; 64 | /** 65 | * rectFF 弹出的最外圈, 66 | * rectFGlodF 弹出的白金层 67 | * reatFInF 弹出的透明层 68 | * rShadowFF 弹出的最外圈的阴影 69 | */ 70 | private RectF rectFF, rectFGlodF, rectFInF, rShadowFF; 71 | /** 72 | * rLabel rectFLabel 未弹出时,最外层标签 73 | * r rectF 最外圈的半径 74 | * rGold rectFGold 白金的半径 75 | * rIn reatFInF 透明内圈 76 | * rF rectF 弹出的最外圈的半径 77 | * rGlodF rectFGold 弹出白金的半径 78 | */ 79 | private float rLabel, r, rGold, rIn, rF, rGlodF, rInF; 80 | 81 | //展示动画 82 | private ValueAnimator animatorStart; 83 | //end动画 84 | private ValueAnimator animatorEnd; 85 | //动画时间 86 | private long animatorDuration = 500; 87 | private TimeInterpolator timeInterpolator = new AccelerateDecelerateInterpolator(); 88 | /** 89 | * 每一个模块占有的总角度 (前几模块角度之和) 90 | */ 91 | private float[] pieAngles; 92 | /** 93 | * 选中位置,对应触摸的模块位置 94 | */ 95 | private int touchSeleteId; 96 | //点触扇形 偏移比例 97 | private double touchRatioRectFF = 1.4; 98 | //最外层标签 99 | private double labelRadioRectF = 1.2; 100 | //绘制bitmap圆环半径比例 101 | private double widthBmpRadioRectF = 0.8; 102 | //白金层 103 | private double goldRadioRectF = 0.4; 104 | //最里面透明层 105 | private double insideRadiusScale = 0.3; 106 | /** 107 | * 中间bitmap收缩倍数 108 | */ 109 | private float bmpScale = 1f; 110 | //文字颜色 111 | private int touchTextColor = Color.BLACK; 112 | //标签文字颜色 113 | private int labelTextColor = Color.WHITE; 114 | //阴影颜色 115 | private int shadowColor = 0x22000000; 116 | //白金区域颜色 117 | private int goldColor = 0x66b8e0e0; 118 | 119 | /** 120 | * 弹出的 模块 中心文字 121 | */ 122 | private int touchCenterTextSize = 50; 123 | /** 124 | * 写在边缘的标签文字大小 125 | */ 126 | private int labelTextSize = 40; 127 | /** 128 | * 绘制每个模块里的图片 129 | */ 130 | private List bmpList = new ArrayList<>(); 131 | 132 | 133 | public MenuChart(Context context) { 134 | this(context, null); 135 | } 136 | 137 | public MenuChart(Context context, @Nullable AttributeSet attrs) { 138 | this(context, attrs, 0); 139 | } 140 | 141 | public MenuChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 142 | super(context, attrs, defStyleAttr); 143 | init(); 144 | } 145 | 146 | @Override 147 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 148 | if (DEFAULT_WIDTH == 0 || DEFAULT_HEIGHT == 0) { 149 | WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 150 | int displayWidth = wm.getDefaultDisplay().getWidth(); 151 | int displayHeight = wm.getDefaultDisplay().getHeight(); 152 | DEFAULT_WIDTH = displayWidth; 153 | DEFAULT_HEIGHT = displayHeight; 154 | } 155 | 156 | 157 | int width = measureSize(1, DEFAULT_WIDTH, widthMeasureSpec); 158 | int height = measureSize(1, DEFAULT_HEIGHT, heightMeasureSpec); 159 | int measureSize = Math.min(width, height); //取最小的 宽|高 160 | setMeasuredDimension(measureSize, measureSize); 161 | 162 | } 163 | 164 | @Override 165 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 166 | super.onSizeChanged(w, h, oldw, oldh); 167 | mWidth = w; 168 | mHeight = h; 169 | 170 | rLabel = (float) (Math.max(mWidth, mHeight) / 2 * widthBmpRadioRectF * labelRadioRectF); 171 | rectFLabel = new RectF(-rLabel, -rLabel, rLabel, rLabel); 172 | 173 | //标签层内绘制bmp 174 | r = (float) (Math.max(mWidth, mHeight) / 2 * widthBmpRadioRectF);// 饼状图半径 175 | rectF = new RectF(-r, -r, r, r); 176 | 177 | //白金圆弧 178 | rGold = (float) (r * goldRadioRectF); 179 | rectFGold = new RectF(-rGold, -rGold, rGold, rGold); 180 | 181 | //透明层圆 182 | rIn = (float) (r * insideRadiusScale); 183 | rectFIn = new RectF(-rIn, -rIn, rIn, rIn); 184 | 185 | 186 | /************************以下为弹出模块的RECTF************************************/ 187 | //弹出的圆弧 188 | rF = (float) (Math.max(mWidth, mHeight) / 2 * widthBmpRadioRectF * touchRatioRectFF);// 饼状图半径 189 | // 饼状图绘制区域 190 | rectFF = new RectF(-rF, -rF, rF, rF); 191 | 192 | rShadowFF = new RectF(-rF - PIE_SPACING * 10, -rF - PIE_SPACING * 10, rF + PIE_SPACING * 10, rF + PIE_SPACING * 10); 193 | 194 | //弹出的白金层圆弧 195 | rGlodF = (float) (rF * goldRadioRectF); 196 | rectFGlodF = new RectF(-rGlodF, -rGlodF, rGlodF, rGlodF); 197 | 198 | //弹出的透明圆 199 | rInF = (float) (rF * insideRadiusScale); 200 | rectFInF = new RectF(-rInF, -rInF, rInF, rInF); 201 | 202 | 203 | initDate(mPieData); 204 | // showStartAnim(); 205 | } 206 | 207 | 208 | private Bitmap bmpCenter; 209 | private Matrix bmpMatrix; 210 | 211 | /** 212 | * 设置居中旋转的bitmap 213 | */ 214 | public void setCenterBitmap(int resourceId, int width, int heigth) { 215 | bmpCenter = BitmapFactory.decodeResource(getResources(), resourceId); 216 | bmpCenter = PieUtils.zoomImage(bmpCenter, width, heigth); 217 | bmpMatrix = new Matrix(); 218 | } 219 | 220 | 221 | @Override 222 | protected void onDraw(Canvas canvas) { 223 | super.onDraw(canvas); 224 | if (mPieData == null) 225 | return; 226 | 227 | canvas.translate(mWidth / 2, mHeight / 2);// 将画板坐标原点移动到中心位置 228 | 229 | if (bmpCenter != null) { //展示的时候旋转中心图片 230 | bmpMatrix.reset(); 231 | bmpMatrix.preTranslate(-bmpCenter.getWidth() / 2, -bmpCenter.getHeight() / 2); //将bitmap移动中心点(原本中心点位于bitmap坐上角(0,0)) 232 | bmpMatrix.preRotate(360 / PIE_VIEW_ANGLE * animatedValue * 2, bmpCenter.getWidth() / 2, bmpCenter.getHeight() / 2);//以bitmap中心点为中心 闭着眼 旋转 跳跃 233 | canvas.drawBitmap(bmpCenter, bmpMatrix, mPaint); 234 | } 235 | 236 | // canvas.drawLine(-mWidth / 2, 0, mWidth / 2, 0, mPaint); //绘制一下中心坐标 x,y 237 | // canvas.drawLine(0, mHeight / 2, 0, -mHeight / 2, mPaint); 238 | 239 | /** 240 | * 先画扇形的每一模块 241 | */ 242 | drawPieRectF(canvas); 243 | 244 | /** 245 | * 再绘制扇形上的文字/图片 246 | */ 247 | drawTextAndBmp(canvas); 248 | } 249 | 250 | /** 251 | * 负责绘制扇形的所有 Rect , Arc 252 | * 253 | * @param canvas 画板 254 | */ 255 | private void drawPieRectF(Canvas canvas) { 256 | float currentStartAngle = 0;// 当前已经绘制的角度,我们从0开始,直到animatedValue 257 | canvas.save(); 258 | canvas.rotate(startAngle); 259 | float sweepAngle; //当前要绘制的角度 260 | 261 | for (int i = 0; i < mPieData.size(); i++) { 262 | PieData pie = mPieData.get(i); 263 | 264 | sweepAngle = Math.min(pie.getAngle() - PIE_SPACING, animatedValue - currentStartAngle); //-1 是为了显示每个模块之间的空隙 265 | 266 | if (currentStartAngle + sweepAngle == PIE_VIEW_ANGLE - PIE_SPACING) { //防止最后一个模块缺角 ,如果是 360° 且最后一块希望是缺觉,可以注释这段 267 | sweepAngle = pie.getAngle(); 268 | } 269 | 270 | if (sweepAngle > 0) { 271 | Log.i(TAG, "绘制角度" + (sweepAngle + currentStartAngle)); 272 | Log.i(TAG, "绘制角度 animatedValue" + animatedValue); 273 | if (i == touchSeleteId) { //弹出触摸的板块 274 | drawArc(canvas, currentStartAngle, sweepAngle, pie, rectFF, rectFGlodF, rectFInF, true); 275 | } else { 276 | drawArc(canvas, currentStartAngle, sweepAngle, pie, rectF, rectFGold, rectFIn, false); 277 | } 278 | 279 | } 280 | currentStartAngle += pie.getAngle(); 281 | } 282 | 283 | canvas.restore(); 284 | } 285 | 286 | /** 287 | * 绘制所有的文字 | bitmap 288 | * 绘制文字和绘制扇形的RectF/Arc 有所不同,绘制文字是当animatedValue达到某一值的时候讲文字绘制 289 | * 而绘制扇形的时候是根据animatedValue一个角度一个角度绘制的 290 | * 291 | * @param canvas 画板 292 | */ 293 | private void drawTextAndBmp(Canvas canvas) { 294 | float currentStartAngle = startAngle; //当前绘制的起始角度,用户设定 。绘制文字我们直接加上其实角度的值即可(更便捷计算) 295 | 296 | for (int i = 0; i < mPieData.size(); i++) { 297 | PieData pie = mPieData.get(i); 298 | 299 | int pivotX; //中心点X 300 | int pivotY; //中心点Y 301 | 302 | if (animatedValue > pieAngles[i] - pie.getAngle() / 3) { //当要绘制的角度 >当前模块的 2/3 的时候才绘制。 增强界面动画绘制效果 303 | 304 | if (i == touchSeleteId) { //如果当前模块是触摸的模块,绘制文字 305 | pivotX = (int) (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (rF + rGlodF) / 2); //获取当前模块中心点x,y轴坐标 306 | pivotY = (int) (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (rF + rGlodF) / 2); 307 | textCenter(pie.getName(), mPaint, canvas, pivotX, pivotY); 308 | } else { 309 | pivotX = (int) (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rLabel) / 2); 310 | pivotY = (int) (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rLabel) / 2); 311 | 312 | canvas.save(); 313 | canvas.rotate((currentStartAngle + pie.getAngle() / 2) + 90, pivotX, pivotY); //+90° 原因是 0°的时候文字竖直绘制 314 | textCenter(pie.getName_label(), mLabelPaint, canvas, pivotX, pivotY); //画边缘标签的文字 315 | canvas.restore(); 316 | 317 | pivotX = (int) (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rGold) / 2); 318 | pivotY = (int) (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rGold) / 2); 319 | if (bmpList != null && bmpList.size() > 0 && bmpList.get(i) != null) { //每次invalidate() 都要重新绘制一遍图片在画板 320 | Log.d(TAG, "绘制图片" + i); 321 | canvas.drawBitmap(bmpList.get(i), (pivotX - (int) (pie.getMax_drawable_size() / 2)), (pivotY - (int) (pie.getMax_drawable_size() / 2)), mPaint); 322 | } 323 | 324 | } 325 | currentStartAngle += pie.getAngle(); 326 | } 327 | } 328 | } 329 | 330 | /** 331 | * 画出扇形 ,两种不同情况 展示|触摸弹出 332 | * 333 | * @param canvas 画板 334 | * @param currentStartAngle 当前的扇形总角度 335 | * @param sweepAngle 要绘制扇形(模块)的角度 336 | * @param pie 数据,获取其颜色 337 | * @param outRectF 最外圈的矩形 338 | * @param midRectF 白金的矩形 339 | * @param inRectF 透明区域的矩形 340 | */ 341 | private void drawArc(Canvas canvas, float currentStartAngle, float sweepAngle, PieData pie, RectF outRectF, RectF midRectF, RectF inRectF, boolean isTouch) { 342 | int layerID; 343 | if (!isTouch) { //没有touch情况 .显示标签 344 | layerID = canvas.saveLayer(rectFLabel, mPaint, Canvas.ALL_SAVE_FLAG); 345 | 346 | mXPaint.setColor(pie.getLabelColor()); 347 | //drawArc里 useCenter的意思是画出的弧形是否连接中心点,否则就连接头尾两点 348 | canvas.drawArc(rectFLabel, currentStartAngle, sweepAngle, true, mXPaint); //标签层写文字 349 | 350 | } else { //touch 情况 351 | layerID = canvas.saveLayer(rShadowFF, mPaint, Canvas.ALL_SAVE_FLAG); 352 | 353 | mXPaint.setColor(shadowColor); 354 | canvas.drawArc(rShadowFF, currentStartAngle - PIE_SPACING, sweepAngle + PIE_SPACING * 2, true, mXPaint); //画个阴影,左右边填充满间隙 355 | 356 | 357 | } 358 | mXPaint.setColor(Color.WHITE); 359 | canvas.drawArc(outRectF, currentStartAngle, sweepAngle, true, mXPaint); //touch情况该层写文字 360 | 361 | 362 | mXPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 363 | canvas.drawArc(midRectF, currentStartAngle - PIE_SPACING, sweepAngle + PIE_SPACING * 2, true, mXPaint); //先把白金区域 擦拭干净。再绘制 364 | mXPaint.setXfermode(null); 365 | 366 | 367 | if (sweepAngle <= (pie.getAngle() - PIE_SPACING) && !isTouch) { //不要间隔 368 | sweepAngle += PIE_SPACING; 369 | } 370 | 371 | mXPaint.setColor(goldColor); 372 | canvas.drawArc(midRectF, currentStartAngle, sweepAngle, true, mXPaint); //白金区域 373 | 374 | mXPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 375 | canvas.drawArc(inRectF, -1f, PIE_VIEW_ANGLE + 2f, true, mXPaint); //透明圈 376 | 377 | Log.d(TAG, "drawArc currentStartAngle==" + currentStartAngle + "+sweepAngle==" + sweepAngle + " ==" + (currentStartAngle + sweepAngle)); 378 | mXPaint.setXfermode(null); 379 | 380 | canvas.restoreToCount(layerID); 381 | } 382 | 383 | 384 | @Override 385 | public boolean onTouchEvent(MotionEvent event) { 386 | return onPieTouchEvent(event, event.getX(), event.getY()); 387 | } 388 | 389 | /** 390 | * 该方法可提供给外界使用 391 | * 即设置当前屏幕触控点对应 我们控件的坐标,可实现功能: 触摸-->(显示我们控件的btn,我们控件显示)-->手指触摸不放,移动手指弹出 模块 392 | * 使用方法为传入 event, event.getRawX() - pieLocation[0], event.getRawY() - pieLocation[1] 393 | * 394 | * @param eventPivotX 在我们控件内对应的 x 坐标 395 | * @param eventPivotY 在我们控件内对应的 y 坐标 396 | */ 397 | public boolean onPieTouchEvent(MotionEvent event, float eventPivotX, float eventPivotY) { 398 | if (mPieData.size() > 0) { 399 | switch (event.getAction()) { 400 | case MotionEvent.ACTION_DOWN: 401 | case MotionEvent.ACTION_MOVE: 402 | 403 | getParent().requestDisallowInterceptTouchEvent(true); 404 | float x = eventPivotX - (mWidth / 2); //因为我们已经将坐标中心点(0,0)移动到 mWidth / 2,mHeight / 2 405 | float y = eventPivotY - (mHeight / 2); 406 | 407 | float touchAngle = 0; //Android 绘图坐标和我们的数学认知不同,安卓由第4 - 3 - 2 -1 象限绘制角度 408 | if (x < 0) { //x<0 的时候 ,求出的角度实为 [0°,-90°) ,此时我们给它掰正 409 | touchAngle += 180; 410 | } 411 | touchAngle += Math.toDegrees(Math.atan(y / x)); 412 | touchAngle = touchAngle - startAngle; 413 | if (touchAngle < 0) { 414 | touchAngle = touchAngle + 360; 415 | } 416 | float touchRadius = (float) Math.sqrt(y * y + x * x);//求出触摸的半径 417 | 418 | //我们用touchSeleteId来表示当前选中的模块 419 | if (rIn < touchRadius && touchRadius < rLabel) {//触摸的范围在绘制的区域 420 | if (-Arrays.binarySearch(pieAngles, touchAngle) - 1 == touchSeleteId) { //如果模块有变动(手指挪到其他模块)再绘制 421 | return true; //如果触摸的地方是已经展开的,那么就不再重复绘制 422 | } 423 | touchSeleteId = -Arrays.binarySearch(pieAngles, touchAngle) - 1; 424 | invalidate(); 425 | Log.d(TAG, " ACTION_DOWN MOVE invalidate()"); 426 | 427 | } else if (0 < touchRadius && touchRadius < rIn) { //触摸的范围在原点bmp范围 428 | 429 | if (animatedValue == PIE_VIEW_ANGLE && event.getAction() == MotionEvent.ACTION_DOWN) { //如果已经全展开 430 | showEndAnim(); 431 | } else if (event.getAction() == MotionEvent.ACTION_MOVE) { //有展开模块时候,手指移动到开关,收起模块 432 | touchSeleteId = -1; 433 | invalidate(); 434 | } 435 | } 436 | 437 | return true; 438 | 439 | 440 | case MotionEvent.ACTION_UP: 441 | 442 | if (animatedValue == PIE_VIEW_ANGLE) { //如果有触摸且已经显示了 那么隐藏 443 | showEndAnim(); 444 | try { 445 | Toast.makeText(getContext(), mPieData.get(touchSeleteId).getName(), Toast.LENGTH_SHORT).show(); 446 | } catch (Exception e) { 447 | e.printStackTrace(); 448 | } 449 | } 450 | getParent().requestDisallowInterceptTouchEvent(false); 451 | 452 | touchSeleteId = -1; 453 | invalidate(); 454 | Log.d(TAG, " ACTION_UP invalidate()"); 455 | return true; 456 | } 457 | } 458 | return super.onTouchEvent(event); 459 | } 460 | 461 | 462 | private void init() { 463 | mPaint.setStyle(Paint.Style.FILL); 464 | mPaint.setAntiAlias(true);//抗锯齿 465 | mPaint.setColor(touchTextColor); 466 | mPaint.setTextSize(touchCenterTextSize); 467 | mPaint.setTextAlign(Paint.Align.CENTER); 468 | 469 | 470 | mLabelPaint.setStyle(Paint.Style.FILL); 471 | mLabelPaint.setAntiAlias(true); 472 | mLabelPaint.setColor(labelTextColor); 473 | mLabelPaint.setTextSize(labelTextSize); 474 | mLabelPaint.setTextAlign(Paint.Align.CENTER); 475 | 476 | mXPaint.setStyle(Paint.Style.FILL); 477 | } 478 | 479 | 480 | /** 481 | * 在onSizeChanged 后调用,这样才可以拿到 r 和 rGold 的值。否则无法测量 扇形内最大的bitmap 482 | */ 483 | private void initDate(ArrayList mPieData) { 484 | 485 | if (mPieData == null || mPieData.size() == 0) 486 | return; 487 | pieAngles = new float[mPieData.size()]; 488 | float sumValue = 0; //总 权重 489 | for (int i = 0; i < mPieData.size(); i++) { 490 | PieData pie = mPieData.get(i); 491 | sumValue += pie.getWeight(); 492 | } 493 | 494 | float sumAngle = 0; 495 | for (int i = 0; i < mPieData.size(); i++) { 496 | PieData pie = mPieData.get(i); 497 | float percentage = pie.getWeight() / sumValue; 498 | float angle = percentage * PIE_VIEW_ANGLE; 499 | pie.setAngle(angle); 500 | sumAngle += angle; 501 | pieAngles[i] = sumAngle; 502 | 503 | /****************防止cos90° 取值极其小*********************/ 504 | 505 | double centerR = (r + rGold) / 2; 506 | double maxH = r - rGold; 507 | double maxW = Math.sin(Math.toRadians(angle / 2)) * centerR * Math.sqrt(2); 508 | maxW = maxW < 1 ? maxH : maxW; 509 | maxH = maxH < 1 ? maxW : maxH; 510 | /****************防止cos90° 取值极其小*********************/ 511 | 512 | pie.setMax_drawable_size(Math.min(maxW, maxH) * bmpScale); 513 | //设置 扇形内最大的bitmap size 514 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), pie.getDrawableId()); 515 | bitmap = PieUtils.zoomImage(bitmap, pie.getMax_drawable_size(), pie.getMax_drawable_size()); 516 | bmpList.add(bitmap); 517 | 518 | } 519 | touchSeleteId = -1; 520 | } 521 | 522 | 523 | public void showStartAnim() { 524 | setVisibility(View.VISIBLE); 525 | startAnimator(); 526 | } 527 | 528 | 529 | public void showEndAnim() { 530 | endAnimator(); 531 | animatedValue = 0; 532 | 533 | } 534 | 535 | 536 | private void endAnimator() { 537 | if (animatorEnd != null) { 538 | if (animatorStart.isRunning() || animatorEnd.isRunning()) { 539 | return; 540 | } 541 | animatorEnd.start(); 542 | } else { 543 | animatorEnd = ValueAnimator.ofFloat(PIE_VIEW_ANGLE, 0).setDuration(animatorDuration); 544 | animatorEnd.setInterpolator(timeInterpolator); 545 | animatorEnd.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 546 | @Override 547 | public void onAnimationUpdate(ValueAnimator animation) { 548 | animatedValue = (float) animation.getAnimatedValue(); 549 | invalidate(); 550 | Log.v(TAG, "animatorEnd invalidate()"); 551 | 552 | if (animatedValue == 0) { 553 | setVisibility(View.GONE); 554 | } 555 | } 556 | }); 557 | animatorEnd.start(); 558 | } 559 | } 560 | 561 | 562 | private void startAnimator() { 563 | if (animatorStart != null) { 564 | if (animatorStart.isRunning() || (animatorEnd != null && animatorEnd.isRunning())) { 565 | return; 566 | } 567 | animatorStart.start(); 568 | } else { 569 | animatorStart = ValueAnimator.ofFloat(0, PIE_VIEW_ANGLE).setDuration(animatorDuration); 570 | animatorStart.setInterpolator(timeInterpolator); 571 | animatorStart.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 572 | @Override 573 | public void onAnimationUpdate(ValueAnimator animation) { 574 | animatedValue = (float) animation.getAnimatedValue(); 575 | invalidate(); 576 | Log.v(TAG, "animatorStart invalidate()"); 577 | 578 | } 579 | }); 580 | animatorStart.start(); 581 | } 582 | } 583 | 584 | /** 585 | * 绘制文字 586 | * 587 | * @param pivotX 文字中心点x 588 | * @param pivotY 文字中心点y 589 | */ 590 | private void textCenter(String string, Paint paint, Canvas canvas, int pivotX, int pivotY) { 591 | //为了验证文字是否画在中心点,可以画两条基线判断一下 592 | // canvas.drawLine(pivotX - mWidth / 2, pivotY, pivotX + mWidth / 2, pivotY, paint); 593 | // canvas.drawLine(pivotX, pivotY - mHeight / 2, pivotX, pivotY + mHeight / 2, paint); 594 | Paint.FontMetrics fontMetrics = paint.getFontMetrics(); 595 | float total = -fontMetrics.ascent + fontMetrics.descent; 596 | float yAxis = total / 2 - fontMetrics.descent; 597 | 598 | canvas.drawText(string, pivotX, pivotY + yAxis, paint); 599 | } 600 | 601 | 602 | /** 603 | * 测绘measure 604 | * 605 | * @param specType 1为宽, 其他为高 606 | * @param contentSize 默认值 607 | */ 608 | private int measureSize(int specType, int contentSize, int measureSpec) { 609 | int result; 610 | //获取测量的模式和Size 611 | int specMode = MeasureSpec.getMode(measureSpec); 612 | int specSize = MeasureSpec.getSize(measureSpec); 613 | 614 | if (specMode == MeasureSpec.EXACTLY) { 615 | result = Math.max(contentSize, specSize); 616 | } else { 617 | result = contentSize; 618 | 619 | if (specType == 1) { 620 | // 根据传人方式计算宽 621 | result += (getPaddingLeft() + getPaddingRight()); 622 | } else { 623 | // 根据传人方式计算高 624 | result += (getPaddingTop() + getPaddingBottom()); 625 | } 626 | } 627 | 628 | return result; 629 | } 630 | 631 | /** 632 | * 设置起始角度 633 | * 634 | * @param mStartAngle 起始角度 635 | */ 636 | public void setStartAngle(float mStartAngle) { 637 | while (mStartAngle < 0) { 638 | mStartAngle = mStartAngle + 360; 639 | } 640 | while (mStartAngle > 360) { 641 | mStartAngle = mStartAngle - 360; 642 | } 643 | this.startAngle = mStartAngle; 644 | } 645 | 646 | /** 647 | * 设置数据 648 | * 649 | * @param mPieData 数据 650 | */ 651 | public void setPieData(ArrayList mPieData) { 652 | this.mPieData = mPieData; 653 | } 654 | 655 | /** 656 | * 设置间距 657 | */ 658 | public void setPieSpacing(float spaing) { 659 | this.PIE_SPACING = spaing; 660 | } 661 | 662 | /** 663 | * 整个view的展示角度 664 | */ 665 | public void setPieShowAngle(int angle) { 666 | this.PIE_VIEW_ANGLE = angle; 667 | } 668 | 669 | /** 670 | * 写在边缘标签文字的size 671 | */ 672 | public void setLabelTextSize(int labelTextSize) { 673 | this.labelTextSize = labelTextSize; 674 | mLabelPaint.setTextSize(labelTextSize); 675 | } 676 | 677 | /** 678 | * 触摸后弹出模块的文字size 679 | */ 680 | public void setTouchCenterTextSize(int touchCenterTextSize) { 681 | this.touchCenterTextSize = touchCenterTextSize; 682 | mPaint.setTextSize(touchCenterTextSize); 683 | } 684 | 685 | /** 686 | * 标签文字颜色 687 | */ 688 | public void setLabelTextColor(int labelTextColor) { 689 | this.labelTextColor = labelTextColor; 690 | mLabelPaint.setColor(labelTextColor); 691 | } 692 | 693 | /** 694 | * 触摸后弹出模块的文字颜色 695 | */ 696 | public void setTouchTextColor(int touchTextColor) { 697 | this.touchTextColor = touchTextColor; 698 | mPaint.setColor(touchTextColor); 699 | } 700 | 701 | /** 702 | * 白金模块颜色 703 | */ 704 | public void setGoldColor(int goldColor) { 705 | this.goldColor = goldColor; 706 | } 707 | 708 | /** 709 | * 触摸后弹出模块的阴影颜色 710 | */ 711 | public void setShadowColor(int shadowColor) { 712 | this.shadowColor = shadowColor; 713 | } 714 | 715 | /** 716 | * 中间bmp比例 717 | */ 718 | public void setBmpScale(float bmpScale) { 719 | this.bmpScale = bmpScale; 720 | } 721 | 722 | /** 723 | * 最里面透明层比例 724 | */ 725 | public void setRatioInsideRect(double insideRadiusScale) { 726 | this.insideRadiusScale = insideRadiusScale; 727 | } 728 | 729 | /** 730 | * 白金层比例 731 | */ 732 | public void setRatioGoldRect(double goldRatio) { 733 | this.goldRadioRectF = goldRatio; 734 | } 735 | 736 | /** 737 | * 绘制bitmap层比例 738 | */ 739 | public void setRatioBmpRect(double widthBmpScaleRadius) { 740 | this.widthBmpRadioRectF = widthBmpScaleRadius; 741 | } 742 | 743 | /** 744 | * 绘制标签层比例 745 | */ 746 | public void setRatioLabelRect(double labelRadiusScale) { 747 | this.labelRadioRectF = labelRadiusScale; 748 | } 749 | 750 | /** 751 | * 绘制触摸层(弹出层)比例 752 | */ 753 | public void setTouchRatioRect(double touchScaleRadius) { 754 | this.touchRatioRectFF = touchScaleRadius; 755 | } 756 | 757 | /** 758 | * 中间的bitmap 759 | */ 760 | public void setBmpCenter(Bitmap bmpCenter) { 761 | this.bmpCenter = bmpCenter; 762 | } 763 | 764 | /** 765 | * 动画持续时间 766 | */ 767 | public void setAnimatorDuration(long animatorDuration) { 768 | this.animatorDuration = animatorDuration; 769 | } 770 | } 771 | -------------------------------------------------------------------------------- /RoundMenuView/app/src/main/java/com/example/roundmenuview/PieChart/PieData.java: -------------------------------------------------------------------------------- 1 | package com.example.roundmenuview.PieChart; 2 | 3 | 4 | public class PieData { 5 | private String name; 6 | //缩写 7 | private String name_label; 8 | //设置权重,默认为1 9 | private float weight = 1; 10 | //标签层颜色 11 | private int labelColor = 0; 12 | private float angle = 0; 13 | //内切bitmap的最大宽/高 14 | private double max_drawable_size; 15 | 16 | public String getName_label() { 17 | return name_label; 18 | } 19 | 20 | public void setName_label(String name_label) { 21 | this.name_label = name_label; 22 | } 23 | 24 | public double getMax_drawable_size() { 25 | return max_drawable_size; 26 | } 27 | 28 | public void setMax_drawable_size(double max_drawable_size) { 29 | this.max_drawable_size = max_drawable_size; 30 | } 31 | 32 | private int drawableId; 33 | 34 | public int getDrawableId() { 35 | return drawableId; 36 | } 37 | 38 | public void setDrawableId(int drawableId) { 39 | this.drawableId = drawableId; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | 50 | public float getWeight() { 51 | return weight; 52 | } 53 | 54 | public void setWeight(float weight) { 55 | this.weight = weight; 56 | } 57 | 58 | 59 | public int getLabelColor() { 60 | return labelColor; 61 | } 62 | 63 | public void setLabelColor(int labelColor) { 64 | this.labelColor = labelColor; 65 | } 66 | 67 | public float getAngle() { 68 | return angle; 69 | } 70 | 71 | public void setAngle(float angle) { 72 | this.angle = angle; 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /RoundMenuView/app/src/main/java/com/example/roundmenuview/PieChart/utils/PieUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.roundmenuview.PieChart.utils; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Matrix; 6 | 7 | /** 8 | * Created by QDX on 2017/3/2. 9 | */ 10 | 11 | public class PieUtils { 12 | 13 | /*** 14 | * 图片的缩放方法 15 | * 16 | * @param bgimage :源图片资源 17 | * @param newWidth :缩放后宽度 18 | * @param newHeight :缩放后高度 19 | */ 20 | public static Bitmap zoomImage(Bitmap bgimage, double newWidth, double newHeight) { 21 | // 获取这个图片的宽和高 22 | float width = bgimage.getWidth(); 23 | float height = bgimage.getHeight(); 24 | // 创建操作图片用的matrix对象 25 | Matrix matrix = new Matrix(); 26 | // 计算宽高缩放率 27 | float scaleWidth = ((float) newWidth) / width; 28 | float scaleHeight = ((float) newHeight) / height; 29 | // 缩放图片动作 30 | matrix.postScale(scaleWidth, scaleHeight); 31 | 32 | return Bitmap.createBitmap(bgimage, 0, 0, (int) width, (int) height, matrix, true); 33 | } 34 | 35 | /** 36 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素) 37 | */ 38 | public static int dp2px(Context context, float dpValue) { 39 | final float scale = context.getResources().getDisplayMetrics().density; 40 | return (int) (dpValue * scale + 0.5f); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /RoundMenuView/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 |