Android 圆盘旋转/飞转菜单(高度定制化)


看了建行的圆盘菜单,效果还不错,于是也动手试试做一个,目标——高度定制化,数量、样式及动画。

为什么要用适配器做成定制化?你知道的,UI那边总是动不动就改的,加点什么啊,删点什么啊,而且有多态机器需要适配,底色不一样就算了,数量和Item也不一样,怎么搞啊?我们总不能每一次都改一大串吧,改一大串和重复类似工作对我们来说简直就是折磨,所以,需要定制化。当然,如果第二次就直接淘汰圆盘了,那另当别论,

工程代码:https://github.com/aknew123/CircleMenu  点击打开链接

效果如下:

                             


程序架构的UML图,如下:



一、圆盘菜单自定义控件的使用

网上查看了一下,看一下他们的实现方式千篇一律,功能都写在一个文件里,阅读难度稍大,于是采用适配器模式做一个,把view和实现逻辑分离,就是简单的MVC,代码结构如下图:


当然,这只是一个Library,把DefaultMenuAdapter删了,重新编译就可以直接用jar包了,测试模块写在另一个工程,使用示例如下:


package com.example.circlemenutest;

import java.util.ArrayList;
import java.util.List;

import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.Toast;

import com.pan.tanglang.circlemenu.model.CircleMenuStatus;
import com.pan.tanglang.circlemenu.view.CircleMenu;
import com.pan.tanglang.circlemenu.view.CircleMenu.OnMenuItemClickListener;
import com.pan.tanglang.circlemenu.view.CircleMenu.OnMenuStatusChangedListener;

public class MainActivity extends Activity {

	public static final String TAG = "MainActivity";

	private String[] mItemTexts = new String[] { "安全中心", "特殊服务", "投资理财", "转账汇款", "我的账户", "信用卡", "腾讯", "阿里", "百度" };
	private int[] mItemImgs = new int[] { R.drawable.foreign01, R.drawable.foreign02, R.drawable.foreign03,
			R.drawable.foreign04, R.drawable.foreign05, R.drawable.foreign06, R.drawable.foreign07,
			R.drawable.foreign08, R.drawable.foreign09 };

	private CircleMenu mCircleMenu;
	private ImageView ivCenter;

	private float startRotate;
	private float startFling;

	ObjectAnimator animRotate = null;
	ObjectAnimator animFling = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mCircleMenu = (CircleMenu) findViewById(R.id.cm_main);
		ivCenter = (ImageView) findViewById(R.id.iv_center_main);
		ivCenter.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				Toast.makeText(MainActivity.this, "圆盘中心", Toast.LENGTH_SHORT).show();

			}
		});
		mCircleMenu.setOnItemClickListener(new OnMenuItemClickListener() {

			@Override
			public void onClick(View view, int position) {
				Toast.makeText(MainActivity.this, mItemTexts[position], Toast.LENGTH_SHORT).show();
			}
		});
		mCircleMenu.setOnStatusChangedListener(new OnMenuStatusChangedListener() {

			@Override
			public void onStatusChanged(CircleMenuStatus status, double rotateAngle) {
				// TODO 可在此处定制各种动画
				odAnimation(status, (float)rotateAngle);
			}

		});
		List<ItemInfo> data = new ArrayList<>();
		ItemInfo item = null;
		for (int i = 0; i < mItemTexts.length; i++) {
			item = new ItemInfo(mItemImgs[i], mItemTexts[i]);
			data.add(item);
		}

		mCircleMenu.setAdapter(new CircleMenuAdapter(data));

	}

	private void odAnimation(CircleMenuStatus status, float rotateAngle) {

		switch (status) {
		case IDLE:
			Log.i(TAG, "--- -IDLE-----");
			animRotate.cancel();
			animRotate.cancel();
			break;
		case START_ROTATING:
			Log.i(TAG, "--- -START_ROTATING-----");
			break;
		case ROTATING:
			animRotate = ObjectAnimator.ofFloat(ivCenter, "rotation", startRotate, startRotate + rotateAngle);
			animRotate.setDuration(200).start();
			startRotate += rotateAngle;
			// Log.i(TAG, "--- -ROTATING-----");
			break;
		case STOP_ROTATING:
			Log.i(TAG, "--- -STOP_ROTATING-----");
			break;
		case START_FLING:
			Log.i(TAG, "--- -START_FLING-----");
			break;

		case FLING:
			// Log.i(TAG, "--- -FLING-----");
			animFling = ObjectAnimator.ofFloat(ivCenter, "rotation", startFling, startFling + rotateAngle);
			animFling.setDuration(200).start();
			startFling += rotateAngle;
			break;
		case STOP_FLING:
			Log.i(TAG, "--- -STOP_FLING-----");

			break;

		default:
			break;
		}

	}
}

后面的是用户定制化动画实现(提供诸多状态,爱怎么折腾怎么折腾)。好了,我们来看看实现原理。

二、实现原理

1.先看需要做什么

(1).圆盘菜单CircleMenu是一个转盘,装有各种Item,是一个容器,那理所当然是继承ViewGroup,为了方便实现飞转,所以监听用户手势OnGestureListener;

(2).菜单项CircleItemView,虽是一个item,但为了可定制化,当然也是一个容器,也是为了方便实现飞转,重写onFling方法,所以这里继承LinearLayout,纯属为了方便布局,若有特殊需求,可在布局的时候,在外层添加一个FrameLayout容器,爱怎么搞怎么搞;

(3).Adapter和Model,既然有Item那肯定也有Adapter和数据model,Adapter继承BaseAdapter即可,就跟ListView一样。

好了,就这3个玩意儿,另外几个都是从上面这两个抽出来的。

2.旋转原理

来看一下转动分析图,


图中圆心的坐标应为( mRadius, mRadius),则圆盘半径为mRadius,按照大多数人的习惯右手向下滑动,圆盘也就跟着顺时针转动(或者说滚动),当然不是圆盘在转,是菜单项在滚动,滚动了弧度为a,那途中Item的x、y坐标应为

x = tmp *cos a;

y = tmp*sin a;

这是相对于圆心的坐标,再加上圆盘的半径,就是圆盘中的坐标了,子View只管在父容器中的坐标,父容器布局时会加上自身的left和top,这样逐层往上推就是在屏幕中的坐标了)。

我们将Item强制为正方形,itemWidth为Item的宽度,当 tmp  = mRaiuds - itemWidth / Math.sqrt(2) 时,那Item的外直角就走在圆盘圆周上,再大则要出边界了,所以,Item的中心点位于mRaiuds/2和mRaiuds - itemWidth / Math.sqrt(2)之间最为合理,那么,item的x、y为

		final int childCount = getChildCount();
		int left, top, halfDiagonal;
		// 限制Item的宽高
		int itemWidth = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION);
		float angleDelay = 360 / childCount;
		for (int i = 0; i < childCount; i++) {
			final View child = getChildAt(i);
			if (child.getVisibility() == View.GONE) {
				continue;
			}
			mStartAngle %= 360;
			// 取Item对角线的一半为Item中心到圆盘圆周的距离
			halfDiagonal = (int) (itemWidth / Math.sqrt(2));
			float distanceFromCenter = mRadius - halfDiagonal - mPadding;
			left = mRadius + (int) Math.round(distanceFromCenter * Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f * itemWidth);
			top = mRadius + (int) Math.round(distanceFromCenter * Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f * itemWidth);
			// 重新Layout
			child.layout(left, top, left +
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以使用以下步骤来生成一个可视数据圆盘: 1. 安装依赖:使用npm或yarn安装依赖。需要安装d3和vue-d3。 2. 创建组件:创建一个Vue组件。在组件中引入d3和vue-d3。 3. 绘制圆盘:使用d3来绘制圆盘。定义数据格式和颜色,然后使用d3的pie()函数将数据换为适合绘制的格式。使用d3的arc()函数来创建圆弧。然后使用vue-d3的v-d3来绑定数据和圆弧。 4. 添加标签:使用d3的text()函数来添加标签。使用vue-d3的v-d3来绑定标签和数据。 5. 样式设置:使用CSS来设置圆盘和标签的样式。 下面是一个简单的Vue组件示例,用于生成一个可视数据圆盘: ``` <template> <div class="chart"> <svg :width="width" :height="height"> <g :transform="'translate(' + radius + ',' + radius + ')'"> <g v-for="(arc, index) in arcs" :key="index"> <path :d="arc.path" :fill="arc.color" /> <text :transform="arc.labelTransform">{{ arc.label }}</text> </g> </g> </svg> </div> </template> <script> import * as d3 from 'd3'; import VueD3 from 'vue-d3'; export default { components: { VueD3, }, props: { data: { type: Array, default: [], }, width: { type: Number, default: 400, }, height: { type: Number, default: 400, }, radius: { type: Number, default: 200, }, }, computed: { arcs() { const pie = d3 .pie() .value((d) => d.value) .sort(null); const arcs = pie(this.data); const arc = d3 .arc() .innerRadius(0) .outerRadius(this.radius); arcs.forEach((d) => { d.path = arc(d); d.label = d.data.label; d.labelTransform = `translate(${arc.centroid(d)})`; }); return arcs; }, }, }; </script> <style scoped> .chart { display: flex; justify-content: center; align-items: center; } .chart svg { overflow: visible; } .chart text { font-size: 14px; text-anchor: middle; } </style> ``` 在父组件中,你可以传递数据并使用这个组件来生成一个可视数据圆盘: ``` <template> <div> <data-chart :data="data" /> </div> </template> <script> import DataChart from './DataChart.vue'; export default { components: { DataChart, }, data() { return { data: [ { label: 'A', value: 50 }, { label: 'B', value: 30 }, { label: 'C', value: 20 }, ], }; }, }; </script> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值