您的位置:首页 > 移动开发 > Android开发

圆形动态分配图

2016-02-17 08:49 495 查看
昨天项目中需要让我来做个动态分布图

效果如图:


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

先自定义了个继承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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android