圆形动态分配图

昨天项目中需要让我来做个动态分布图

效果如图:

在网上也查了很多,但发现大多数都是使用的第三方工具包。虽然都不错,但个人还是想通过自定义来实现一个,如有不足多多指教

先自定义了个继承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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值