圆形动态分配图
2016-02-17 08:49
495 查看
昨天项目中需要让我来做个动态分布图
效果如图:
在网上也查了很多,但发现大多数都是使用的第三方工具包。虽然都不错,但个人还是想通过自定义来实现一个,如有不足多多指教
先自定义了个继承SurfaceView的View
还有一个就是具体的model
在initEvent中去调用
具体的Demo版本下载链接http://download.csdn.net/detail/u012892687/9433811
效果如图:
在网上也查了很多,但发现大多数都是使用的第三方工具包。虽然都不错,但个人还是想通过自定义来实现一个,如有不足多多指教
先自定义了个继承SurfaceView的View
package com.haolixin.view; import java.util.Timer; import java.util.TimerTask; import com.haolixin.activity.R; import com.haolixin.models.Trading; import com.haolixin.models.TradingList; import com.haolixin.utils.DensityUtil; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; import android.view.View; /** * 多个扇形百分比View * @author linking */ public class DataCircleView extends SurfaceView implements Callback { private Canvas mCanvas; private Paint mPaint; private SurfaceHolder holder; private Timer drawTimer, moveTimer;// 绘制圆的线程和转动圆的线程 private DrawTimerTask drawTimerTask; private MoveTimerTask moveTimerTask; private float radius;// 圆的的半径 private float x0, y0;// 圆心的坐标 private TradingList list;// 数据百分比的数组 private int height, width; private String total = ""; private boolean canClick = false; private int totalAngle = 0; private int position;// 标记点击的是哪个position public DataCircleView(Context context, AttributeSet attrs) { super(context, attrs); // 使surfaceview设置的背景有效 setLayerType(View.LAYER_TYPE_SOFTWARE, null); setZOrderOnTop(true); getHolder().setFormat(PixelFormat.TRANSPARENT); getHolder().addCallback(this); } public void init() { this.holder = getHolder(); drawTimer = new Timer(); mPaint = new Paint(); mPaint.setColor(Color.BLUE); mPaint.setAntiAlias(true); // 绘制圆环 drawTimerTask = new DrawTimerTask(); drawTimer.schedule(drawTimerTask, 150, 1); } class DrawTimerTask extends TimerTask { public void run() { if (list == null) { drawTimer.cancel(); return; } if (totalAngle < 360) { // 开启一个线程,每一毫秒执行一次绘制,慢慢增加ANGLE // 循环绘制,每次绘制的圆环角度不一样,慢慢增加至360,模拟缩放效果 drawRectF(totalAngle, list.getStart()); totalAngle += 12; } else { // 最终绘制一个360度的圆 drawRectF(360, list.getStart()); cancelTimer(); } } }; private float start; class MoveTimerTask extends TimerTask { public void run() { float moveAngle = list.getStartAngle(); if (Math.abs(start - moveAngle) > 180) start -= 360; try { // 转动 if (start < moveAngle) { drawRectF(360, start); start += 8; } // 转动 else if (start > moveAngle) { drawRectF(360, start); start -= 8; } if (Math.abs(start - moveAngle) <= 10) { drawRectF(360, moveAngle); post(new Runnable() { public void run() { if (onCircleItemClickListener != null) onCircleItemClickListener.OnItemSelected(position); } }); cancelTimer(); // 当转完之后可以点击 canClick = true; } } catch (Exception e) { e.printStackTrace(); cancelTimer(); } } } /* * /* (non-Javadoc) * * @see android.view.View#onTouchEvent(android.view.MotionEvent) */ public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN && canClick) { float x = event.getX() - x0; float y = y0 - event.getY(); position = list.getWhichClicked(x, y, radius); if (position >= 0) { list.sort(position); start = list.getStart(); cancelTimer(); moveTimer = new Timer(); moveTimer.schedule(new MoveTimerTask(), 200, 1); canClick = false; } } return true; } /** * 根据百分比绘制一个扇形 * * @param percent * 每个扇形快占的百分比 * @param angle * 绘制占的角度,360表示一个整圆环 * @param startAngle * 开始绘制的角度 */ private float sss; private void drawRectF(float angle, float startAngle) { mCanvas = holder.lockCanvas(); try { height = mCanvas.getHeight(); width = mCanvas.getWidth(); RectF mRectF = new RectF(width / 2 - radius, 0, width / 2 + radius, height); radius = height / 2; x0 = width / 2; y0 = height / 2; sss = startAngle; for (int i = 0; i < list.size(); i++) { Trading data = list.get(i); mPaint.setColor(getResources().getColor(data.getColor())); mCanvas.drawArc(mRectF, sss, data.sweepAngle * angle / 360, true, mPaint); //画上图标暂时不需要 // float middleAngle = sss + data.sweepAngle * angle / 360 / 2; // middleAngle = middleAngle > 180 ? 360 - middleAngle : -middleAngle; // drawImage(BitmapFactory.decodeResource(getResources(), data.getLittleImageId()), middleAngle); sss += data.sweepAngle * angle / 360f; } // 绘制中间的白圈 mPaint.setColor(Color.WHITE); mCanvas.drawCircle(x0, y0, radius * 3 / 5, mPaint); // 绘制文字 mPaint.setColor(0xff999999); mPaint.setTextSize(DensityUtil.sp2px(getContext(), 16)); mPaint.setTextAlign(Align.CENTER); mCanvas.drawText("交易总量", x0, y0 - DensityUtil.dp2px(getContext(), 22), mPaint); mPaint.setColor(0xff333333); mPaint.setTextSize(DensityUtil.sp2px(getContext(), 20)); mCanvas.drawText(total, x0, y0 + DensityUtil.dp2px(getContext(), 22), mPaint); holder.unlockCanvasAndPost(mCanvas); } catch (Exception e) { } } public void drawImage(Bitmap bitmap, float middleAngle) { Rect dst = new Rect();// 屏幕 >>目标矩形 int width = 30;// 绘制图片的高度 int height = 35;// 宽度 int left = (int) (x0 + radius * 4 / 5 * Math.cos(middleAngle / 180f * Math.PI)) - width / 2; int top = (int) (y0 - radius * 4 / 5 * Math.sin(middleAngle / 180f * Math.PI)) - height / 2; dst.left = left; dst.top = top; dst.right = left + width; dst.bottom = top + height; // 画出指定的位图,位图将自动--》缩放/自动转换,以填补目标矩形 // 这个方法的意思就像 将一个位图按照需求重画一遍 mCanvas.drawBitmap(bitmap, null, dst, null); dst = null; } public void setData(String total, TradingList list, boolean canClick, OnCircleItemClickListener l) { cancelTimer(); this.canClick = canClick; this.total = total; this.list = list; this.onCircleItemClickListener = l; init(); } public void surfaceCreated(SurfaceHolder holder) { } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } public void surfaceDestroyed(SurfaceHolder holder) { cancelTimer(); } public void cancelTimer() { if (drawTimer != null) drawTimer.cancel(); if (moveTimer != null) moveTimer.cancel(); if (drawTimerTask != null) drawTimerTask.cancel(); if (moveTimerTask != null) moveTimerTask.cancel(); } // 选中哪个圆环时的监听 private OnCircleItemClickListener onCircleItemClickListener; public interface OnCircleItemClickListener { public void OnItemSelected(int position); } }里面需要两个bean,一个是用来记录一共有多少数据的List数组
package com.haolixin.models; import java.util.ArrayList; /** * @author linking * */ public class TradingList { private ArrayList<Trading> list; private int number = 0; private float start;// 扇形开始的角度 public TradingList() { list = new ArrayList<Trading>(); } public void add(Trading data) { if (data.percent <= 0) return; data.id = number; // 计算初始角度 if (number == 0) { data.startAngle = (25 - data.percent / 2f) / 100f * 360; start = data.startAngle;// 最开始的角度就是第一个的角度 } else { data.startAngle = list.get(number - 1).sweepAngle + list.get(number - 1).startAngle; } data.sweepAngle = data.percent / 100f * 360; number++; list.add(data); } /** * 重新对数据进行排序 * * @param poistion * 将这个position的对象排到第一个 */ public void sort(int poistion) { // 每次转动钱先保存点击的这个扇形开始的角度 start = list.get(poistion).startAngle; for (int i = 0; i < list.size(); i++) { Trading data = list.get(i); data.id = (data.id + (number - poistion)) % number; } ArrayList<Trading> l = new ArrayList<Trading>(); for (int i = 0; i < list.size(); i++) { for (int j = 0; j < number; j++) { Trading data = list.get(j); if (data.id == i) { l.add(data); break; } } } list = l; for (int i = 0; i < number; i++) { Trading data = list.get(i); if (i == 0) { data.startAngle = (25 - data.percent / 2f) / 100f * 360; } else { data.startAngle = list.get(i - 1).sweepAngle + list.get(i - 1).startAngle; } data.sweepAngle = data.percent / 100f * 360; } } /** * 根据点击的坐标获取点击是哪个区域 * * @param x * @param y */ public int getWhichClicked(float x, float y, float radius) { float angle = 0; // 判断是否在弧圈内 if ((x * x + y * y) > (radius / 2) * (radius / 2) && (x * x + y * y) < radius * radius) { if (y > 0) { angle = (int) (-Math.acos(x / Math.sqrt(x * x + y * y)) / Math.PI * 180); } else // 获取到点击点相对于x正轴的角度 angle = (int) (Math.acos(x / Math.sqrt(x * x + y * y)) / Math.PI * 180); // 转换成相对于开始绘制角度的角度 angle = (angle + 360) % 360; // 根据总角度确认点击是哪个区域 for (int i = 0; i < list.size(); i++) { Trading data = list.get(i); float a1 = data.startAngle; float a2 = a1 + data.sweepAngle; if (angle >= a1 && angle <= a2) { return i; } else if ((angle + 360) >= a1 && (angle + 360) <= a2) { return i; } } } return -1; } /** * 获取第一个开始的角度 * * @return */ public float getStartAngle() { return list.get(0).startAngle; } public Trading get(int position) { if (list.size() > 0) return list.get(position); else { return null; } } public int size() { return list.size(); } public float getStart() { return start; } }
还有一个就是具体的model
package com.haolixin.models; import com.haolixin.activity.R; public class Trading { public String total; public float percent; public String source; public int sourceId; public int id;// 表示顺序 public float startAngle;// 开始的角度 public float sweepAngle;// 旋转过的角度 public Trading() { } public Trading(String total_, float percent_, String source_, int sourceId_) { this.total = total_; this.percent = percent_; this.source = source_; this.sourceId = sourceId_; } /** * 获取对应类型显示的文字 * * @return String */ public String getTitle() { return source + " " + (int) percent + "%"; } public String getNumber() { return total + "笔"; } /** * 获取对应的类别的画笔颜色 * * @return 颜色整形 0xff000000 */ public int getColor() { switch(sourceId){ case 0: return R.color.lightgreen; case 1: return R.color.sky_blue; case 2: return R.color.silver; case 3: return R.color.lightyellow; case 4: return R.color.bisque; case 5: return R.color.peachpuff; case 6: return R.color.tomato; default: return R.color.white; } } }
在initEvent中去调用
package com.haolixin.activity; import com.haolixin.models.Trading; import com.haolixin.models.TradingList; import com.haolixin.view.DataCircleView; import com.haolixin.view.TitleBar; import com.haolixin.view.TitleBar.topOnClickListener; import android.app.Activity; import android.os.Bundle; import android.view.View; /** * 资产配置 * @author linking * */ public class AssetAllocationActivity extends Activity implements topOnClickListener{ private DataCircleView mDataCircleView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_asset_allocation); initTitle(); initUI(); initEvent(); } private void initTitle(){ // 初始化标题栏 TitleBar mTitleBar = (TitleBar) findViewById(R.id.title); mTitleBar.setTitleText("资产配置"); mTitleBar.setLeftVisibility(true); mTitleBar.setTopBarListener(this); } @Override public void leftClick(View view) { finish(); } @Override public void rightClick(View view) { } private void initUI(){ mDataCircleView = (DataCircleView)findViewById(R.id.circle_view); } private void initEvent(){ TradingList list = new TradingList(); list.add(new Trading("", 30, "测试1", 0)); list.add(new Trading("", 50, "测试2", 1)); list.add(new Trading("",20,"测试3",2)); mDataCircleView.setData("1.000.28", list, false, null); } protected void onResume() { super.onResume(); if (mDataCircleView != null) mDataCircleView.init(); } }
具体的Demo版本下载链接http://download.csdn.net/detail/u012892687/9433811
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories