├── CircleMenu ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── ywl5320 │ │ │ └── circlemenu │ │ │ └── ApplicationTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── ywl5320 │ │ │ │ └── circlemenu │ │ │ │ ├── CircleMenuLayout.java │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ └── item_circle_menu.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── icon_alipay.png │ │ │ ├── icon_bankpay.png │ │ │ ├── icon_circle_menu.png │ │ │ ├── icon_code.png │ │ │ ├── icon_qrcode.png │ │ │ ├── icon_spcode.png │ │ │ └── icon_wexin.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── icon_circle_menu.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── ywl5320 │ │ └── circlemenu │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── README.md └── imgs └── circlemenu.gif /CircleMenu/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.ywl5320.circlemenu" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 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 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.4.0' 26 | } 27 | -------------------------------------------------------------------------------- /CircleMenu/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:\Android\android-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 | -------------------------------------------------------------------------------- /CircleMenu/app/src/androidTest/java/com/ywl5320/circlemenu/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.ywl5320.circlemenu; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /CircleMenu/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CircleMenu/app/src/main/java/com/ywl5320/circlemenu/CircleMenuLayout.java: -------------------------------------------------------------------------------- 1 | package com.ywl5320.circlemenu; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.animation.Animation; 10 | import android.view.animation.LinearInterpolator; 11 | import android.view.animation.RotateAnimation; 12 | import android.widget.ImageView; 13 | import android.widget.ProgressBar; 14 | import android.widget.RelativeLayout; 15 | import android.widget.TextView; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * Created by ywl on 2016/8/7. 22 | */ 23 | public class CircleMenuLayout extends RelativeLayout { 24 | 25 | private static final int ID_CENTER_VIEW = 0x001;//中心点id 26 | private Context context; 27 | private View centerview; 28 | private ProgressBar progressBar; 29 | private RotateAnimation mRotateUpAnim; 30 | private LinearInterpolator lir; 31 | private LayoutInflater layoutInflater; 32 | private List menuitems; 33 | private float radus = 0; //菜单之间的相隔度数 34 | private float offsetradus = 0; //旋转角度的和 35 | private float oldradus = 0; //根据菜单点击的项来设置起始度数 36 | private float sub = 0; //累计旋转角度 用于判断是否到零界点 37 | private boolean isrun = false; //是否处于旋转中 38 | private float finishdus = 0; //点击菜单旋转结束度数 39 | private OnMenuItemSelectedListener onMenuItemSelectedListener; 40 | private int tag; 41 | private boolean isright = false; 42 | private int currentPosition = 0; 43 | private float bgdus = 0; //背景旋转的角度 44 | 45 | 46 | public CircleMenuLayout(Context context) { 47 | this(context, null); 48 | } 49 | 50 | public CircleMenuLayout(Context context, AttributeSet attrs) { 51 | this(context, attrs, 0); 52 | } 53 | 54 | /** 55 | * 初始化布局 把旋转背景和中心点添加进去 56 | * @param context 57 | * @param attrs 58 | * @param defStyleAttr 59 | */ 60 | public CircleMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) { 61 | super(context, attrs, defStyleAttr); 62 | this.context = context; 63 | layoutInflater = LayoutInflater.from(context); 64 | menuitems = new ArrayList(); 65 | centerview = new View(context);//中心点 66 | centerview.setId(ID_CENTER_VIEW); 67 | LayoutParams lp = new LayoutParams(0, 0); 68 | lp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 69 | addView(centerview, lp); //添加中心的 用于旋转定位 70 | 71 | progressBar = new ProgressBar(context);//旋转的背景 72 | LayoutParams lp2 = new LayoutParams(dip2px(context, 90), dip2px(context, 90)); 73 | lp2.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 74 | addView(progressBar, lp2); 75 | progressBar.setIndeterminateDrawable(context.getResources().getDrawable(R.mipmap.icon_circle_menu)); 76 | } 77 | 78 | /** 79 | * 点击回调接口 80 | * @param onMenuItemSelectedListener 81 | */ 82 | public void setOnMenuItemSelectedListener(OnMenuItemSelectedListener onMenuItemSelectedListener) { 83 | this.onMenuItemSelectedListener = onMenuItemSelectedListener; 84 | } 85 | 86 | /** 87 | * 初始化数据 88 | * @param titles 89 | * @param imgs 90 | */ 91 | public void initDatas(String[] titles, int[] imgs) 92 | { 93 | initMenuItem(3, dip2px(context, 45), titles, imgs); 94 | } 95 | 96 | 97 | /** 98 | * 菜单的数量 和 半径 名字 和图片 这里只为3个菜单做了适配 99 | * @param size 100 | * @param center_distance 101 | */ 102 | public void initMenuItem(int size, int center_distance, String[] titles, int[] imgs) 103 | { 104 | radus = 360f / size; 105 | int width = dip2px(context, 50); 106 | int height = dip2px(context, 50); 107 | for(int i = 0; i < size; i++) 108 | { 109 | int top = 0; 110 | int left = 0; 111 | 112 | top = -(int)(Math.sin(radus * i * 3.1415f / 180) * center_distance); //r * cos(ao * 3.14 /180 ) 113 | left = -(int)(Math.cos(radus * i * 3.1415f / 180) * center_distance); 114 | LayoutParams lp = new LayoutParams(dip2px(context, 50), dip2px(context, 50)); 115 | View view = layoutInflater.inflate(R.layout.item_circle_menu, this, false); 116 | view.setTag(i); 117 | TextView tvname = (TextView) view.findViewById(R.id.tv_name); 118 | ImageView ivimg = (ImageView) view.findViewById(R.id.img); 119 | tvname.setText(titles[i]); 120 | ivimg.setImageResource(imgs[i]); 121 | view.setOnClickListener(new OnClickListener() { 122 | @Override 123 | public void onClick(View v) {//根据点击的区域 旋转菜单 124 | if(!isrun) { 125 | tag = (int) v.getTag(); 126 | currentPosition = tag; 127 | if(tag == 0) 128 | { 129 | finishdus = -360; 130 | } 131 | else if(tag == 1) 132 | { 133 | finishdus = -120; 134 | } 135 | else if(tag == 2) 136 | { 137 | finishdus = -240; 138 | } 139 | LayoutParams lp = (LayoutParams) v.getLayoutParams(); 140 | int l = lp.leftMargin; 141 | int t = lp.topMargin; 142 | 143 | if (t > -dip2px(context, 5) && l > -dip2px(context, 5)) { 144 | oldradus = 120f; 145 | isright = false; 146 | } else if (t > -dip2px(context, 5) && l < -dip2px(context, 5)) { 147 | oldradus = 120f; 148 | isright = true; 149 | } else if (t < -dip2px(context, 5)) { 150 | oldradus = 0f; 151 | } 152 | sub = 0; 153 | circleMenu(8, dip2px(context, 45), oldradus, isright); 154 | 155 | } 156 | } 157 | }); 158 | lp.addRule(RelativeLayout.BELOW, centerview.getId()); 159 | lp.addRule(RelativeLayout.RIGHT_OF, centerview.getId()); 160 | lp.setMargins(-width / 2 + top, -height / 2 + left, 0, 0); 161 | addView(view, lp); 162 | menuitems.add(view); 163 | } 164 | 165 | handler.postDelayed(runnable, 0); 166 | } 167 | 168 | /** 169 | * 根据度数来旋转菜单 菜单中心都在一个圆上面 采用圆周运动来旋转 170 | * @param offserradius 171 | * @param center_distance 172 | * @param d 173 | * @param right 174 | */ 175 | public void circleMenu(float offserradius, int center_distance, float d, boolean right) 176 | { 177 | if(oldradus != 0) 178 | { 179 | progressBar.clearAnimation(); 180 | if(isright) 181 | { 182 | mRotateUpAnim = new RotateAnimation(bgdus, bgdus + 120, 183 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 184 | 0.5f); 185 | bgdus += 120; 186 | } 187 | else 188 | { 189 | mRotateUpAnim = new RotateAnimation(bgdus, bgdus - 120, 190 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 191 | 0.5f); 192 | bgdus -= 120; 193 | } 194 | 195 | lir = new LinearInterpolator(); 196 | mRotateUpAnim.setDuration(350); 197 | mRotateUpAnim.setFillAfter(true); 198 | mRotateUpAnim.setInterpolator(lir); 199 | // mRotateUpAnim.setRepeatCount(Animation.INFINITE); 200 | progressBar.startAnimation(mRotateUpAnim); 201 | } 202 | circleMenuItem(offserradius, center_distance, d, right); 203 | } 204 | 205 | /** 206 | * 菜单旋转 207 | * @param offserradius 208 | * @param center_distance 209 | * @param d 210 | * @param right 211 | */ 212 | public void circleMenuItem(float offserradius, int center_distance, float d, boolean right) 213 | { 214 | sub += offserradius; 215 | if(sub > d) 216 | { 217 | if(onMenuItemSelectedListener != null) 218 | { 219 | onMenuItemSelectedListener.onMenuItemOnclick(tag); 220 | } 221 | isrun = false; 222 | return; 223 | } 224 | if(right) { 225 | offsetradus -= offserradius; 226 | } 227 | else 228 | { 229 | offsetradus += offserradius; 230 | } 231 | int size = menuitems.size(); 232 | int width = dip2px(context, 50); 233 | int height = dip2px(context, 50); 234 | for(int i = 0; i < size; i++) 235 | { 236 | if(Math.abs(sub - d) <= 8) 237 | { 238 | offsetradus = finishdus; 239 | } 240 | LayoutParams lp = (LayoutParams) menuitems.get(i).getLayoutParams(); 241 | float ds = radus * i + offsetradus; 242 | int top = -(int)(Math.sin(ds * 3.1415f / 180) * center_distance); //r * cos(ao * 3.14 /180 ) 243 | int left = -(int)(Math.cos(ds * 3.1415f / 180) * center_distance); 244 | lp.setMargins(-width / 2 + top, -height / 2 + left, 0, 0); 245 | menuitems.get(i).requestLayout(); 246 | } 247 | 248 | if(sub <= d) { 249 | isrun = true; 250 | offsetradus = offsetradus % 360; 251 | handler.postDelayed(runnable, 5); 252 | } 253 | else 254 | { 255 | if(onMenuItemSelectedListener != null) 256 | { 257 | onMenuItemSelectedListener.onMenuItemOnclick(tag); 258 | } 259 | isrun = false; 260 | } 261 | } 262 | 263 | 264 | 265 | public int dip2px(Context context, float dipValue) { 266 | final float scale = context.getResources().getDisplayMetrics().density; 267 | return (int) (dipValue * scale + 0.5f); 268 | } 269 | 270 | Handler handler = new Handler(); 271 | Runnable runnable = new Runnable() { 272 | @Override 273 | public void run() { 274 | circleMenuItem(8, dip2px(context, 45), oldradus, isright); 275 | } 276 | }; 277 | 278 | public interface OnMenuItemSelectedListener 279 | { 280 | void onMenuItemOnclick(int code); 281 | } 282 | 283 | /** 284 | * 设置旋转到哪个菜单项 285 | * @param tag 286 | */ 287 | public void setCurrentTag(int tag) 288 | { 289 | if(currentPosition == tag) 290 | { 291 | return; 292 | } 293 | if(tag == 0) 294 | { 295 | finishdus = -360; 296 | } 297 | else if(tag == 1) 298 | { 299 | finishdus = -120; 300 | } 301 | else if(tag == 2) 302 | { 303 | finishdus = -240; 304 | } 305 | 306 | if(currentPosition == 0) //当前是0 307 | { 308 | if(tag == 1) 309 | { 310 | oldradus = 120f; 311 | isright = true; 312 | } 313 | else if(tag == 2) 314 | { 315 | oldradus = 120f; 316 | isright = false; 317 | } 318 | } 319 | else if(currentPosition == 1) 320 | { 321 | if(tag == 2) 322 | { 323 | oldradus = 120f; 324 | isright = true; 325 | } 326 | else if(tag == 0) 327 | { 328 | oldradus = 120f; 329 | isright = false; 330 | } 331 | } 332 | else if(currentPosition == 2) 333 | { 334 | if(tag == 0) 335 | { 336 | oldradus = 120f; 337 | isright = true; 338 | } 339 | else if(tag == 1) 340 | { 341 | oldradus = 120f; 342 | isright = false; 343 | } 344 | } 345 | 346 | currentPosition = tag; 347 | this.tag = tag; 348 | sub = 0; 349 | circleMenu(8, dip2px(context, 45), oldradus, isright); 350 | 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /CircleMenu/app/src/main/java/com/ywl5320/circlemenu/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ywl5320.circlemenu; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.Toast; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | private String[] titles = {"支付宝", "银联", "微信"}; 12 | private int[] imgs = {R.mipmap.icon_alipay, R.mipmap.icon_bankpay, R.mipmap.icon_wexin}; 13 | private CircleMenuLayout cmlmenu; 14 | private Button btn; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | cmlmenu = (CircleMenuLayout) findViewById(R.id.cml_menu); 21 | btn = (Button) findViewById(R.id.btn); 22 | cmlmenu.initDatas(titles, imgs); 23 | cmlmenu.setOnMenuItemSelectedListener(new CircleMenuLayout.OnMenuItemSelectedListener() { 24 | @Override 25 | public void onMenuItemOnclick(int code) { 26 | if(code == 0)// 27 | { 28 | Toast.makeText(MainActivity.this, "支付宝", Toast.LENGTH_SHORT).show(); 29 | } 30 | else if(code == 1) 31 | { 32 | Toast.makeText(MainActivity.this, "银联", Toast.LENGTH_SHORT).show(); 33 | } 34 | else if(code == 2) 35 | { 36 | Toast.makeText(MainActivity.this, "微信", Toast.LENGTH_SHORT).show(); 37 | } 38 | } 39 | }); 40 | 41 | btn.setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | cmlmenu.setCurrentTag(2); 45 | } 46 | }); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CircleMenu/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 |