前言
最近写了2个自定义图形,随机矩形和漂浮的圆环,代码参考https://github.com/isqing/BubbleChart
随机矩形
随机矩形最关键的是算随机的坐标,我计算的矩形左上角的x,y坐标,根据x,y坐标和矩形的宽高度绘制一个一个的小矩形
其实我计算的时候是有规律可循的,是一行一行的计算的,根据屏幕的宽度计算最大列数,根据个数计算最大行数,每行最少随机生成一个点的坐标,如果到最大行数还没到总个数,再次循环随机点,下面是计算的方法
public staticList getPoints(intw,inth,intmRectX,intmRectY,intmSpaceH,intamount){
List pointList=newArrayList<>();
intmaxRows =(int)Math.ceil(h/(mRectY+mSpaceH));//最大行数
intmaxCol =Math.min(5,w/mRectX);//最大列数
intmSum=0;//累计每次生成的个数
Map numberMap=newHashMap<>();
while(mSum
for(inti =0; i < maxRows; i++) {
intmPreRandom=0;//上次循环生成的值
if(numberMap.containsKey(i)){
mPreRandom=numberMap.get(i);
}
intmRandom =0;//随机生成一个的个数
if(maxCol>mPreRandom){
mRandom =newRandom().nextInt(maxCol-mPreRandom +1-1)+1;//生成1到maxCol,随机生成的每行的个数
}else{
mRandom=0;//当前行的个数超过最大个是
}
//当生成的随机数和之前的和大于图形总个数时
if(mSum + mRandom >= amount) {
mRandom = amount - mSum;
mSum = amount;
numberMap.put(i,mRandom+mPreRandom);
break;
}else{
mSum += mRandom;
numberMap.put(i,mRandom+mPreRandom);
}
}
}
for(inti=0;i
intcol=numberMap.get(i);//第i行的个数
intoverW=w;//计算剩余的宽度
floataverageW = w / col;//第一次取平均值
floatstartX=0;
floatendX=0;
floatmRandomX=0;
if(j==0){
startX=0;
endX=averageW- mRectX;
}else{
averageW=overW/(col-j);//计算剩余的平均宽度
startX = mRandomX+mRectX;
endX = mRandomX+mRectX + averageW - mRectX-10;//10为间隔
// Log.i("startX",startX+"==="+endX);
}
//计算y的范围
floatstartY=i*(mRectY+mSpaceH);
floatendY=(i+1)*(mRectY+mSpaceH)-mRectY;
mRandomX=startX + ((endX - startX) *newRandom().nextFloat());//x坐标
floatmRandomY =startY + ((endY - startY) *newRandom().nextFloat());//Y坐标
// Log.i("mRandomX",mRandomX+"==="+mRectX+"==="+overW);
overW=(int)(w-(mRandomX+mRectX));
// Log.i("overW",""+overW);
Point point=newPoint(mRandomX,mRandomY);
pointList.add(point);
}
}
returnpointList;
}
绘制随机图形
在onMeasure里面
intwidthP = DensityUtil.getScreenWidth(getContext());
intheightP = DensityUtil.getScreenHeight(getContext());
intheightZ1=((int)Math.ceil(hashOvalList.size()/4))*(mRectY+mSpaceH);
intheightZ2= (int)Math.ceil(hashOvalList.size()/(widthP/mRectX))*(mRectY+mSpaceH);
heightZ= Math.max(heightZ1,heightZ2)+mSpaceH;
if(heightZ<=0||heightZ
heightZ=heightP;
// isHalfScreen=true;
}
setMeasuredDimension(widthP,heightZ);
chartPait=newPaint();
textPaint=newPaint();
//透明度动画
initAnimatorRed();
initAnimatorGreen();
drawRect主要是根据生成的坐标绘制矩形
for(inti =0; i
HashOval hashOval=hashOvalList.get(i);
Point point =pointList.get(i);
//画圆角矩形
floatleft = point.getX();
floatrigth = point.getX() +mRectX;
floattop = point.getY();
floatbottom = point.getY() +mRectY;
if(isHalfScreen) {
top = point.getY()+marginTop;
bottom = point.getY() +mRectY+marginTop;
}
mRect=newRectF(left+pading, top+pading, rigth-pading, bottom-pading);
chartPait.setStyle(Paint.Style.FILL);//充满
chartPait.setColor(hashOval.getChartColor());
chartPait.setAntiAlias(true);// 设置画笔的锯齿效果
intred = (hashOval.getChartColor() &0xff0000) >>16;
intgreen = (hashOval.getChartColor() &0x00ff00) >>8;
intblue = (hashOval.getChartColor() &0x0000ff);
if(hashOval.getIsAlpha()==0) {
chartPait.setARGB(animatedAplaGreen, red, green, blue);
}else{
chartPait.setARGB(animatedAplaRed, red, green, blue);
}
canvas.drawRoundRect(mRect,20,20,chartPait);//第二个参数是x半径,第三个参数是y半径
//写字
Rect bounds =newRect();
textPaint.setStyle(Paint.Style.FILL);//充满
textPaint.setColor(hashOval.getTextColor());
textPaint.setTextSize(DensityUtil.dip2px(getContext(),hashOval.getTextSize()));
textPaint.setAntiAlias(true);// 设置画笔的锯齿效果
textPaint.getTextBounds(hashOval.getText(),0, hashOval.getText().length(), bounds);
inttextW = bounds.width();
inttextH = bounds.height();
// Log.i("textw",textW+","+mRectX);
// Log.i("textH",textH+","+mRectY);
floattLeft=left+(mRectX-textW)/2;
floattTop=top+(mRectY-textH);
String text=hashOval.getText();
if(text.length()>4){
text=text.substring(0,4);
textPaint.getTextBounds(text,0, text.length(), bounds);
textW = bounds.width();
textH = bounds.height();
tLeft=left+(mRectX-textW)/2;
tTop=top+(mRectY-textH);
}
inttextRed = (Color.WHITE&0xff0000) >>16;
inttextGreen = (Color.WHITE&0x00ff00) >>8;
inttextBlue = (Color.WHITE&0x0000ff);
if(hashOval.getIsAlpha()==0) {
textPaint.setARGB(animatedAplaGreen, textRed, textGreen, textBlue);
}else{
textPaint.setARGB(animatedAplaRed, textRed, textGreen, textBlue);
}
canvas.drawText(text,tLeft,tTop,textPaint);
}
点击事件
重写onTouchEvent事件中的MotionEvent.ACTION_DOWN调用setClick(event.getX(),event.getY());根据点击的坐标,判断点击的在哪个小矩形里
private voidsetClick(floatx,floaty){
for(inti =0; i
HashOval hashOval =hashOvalList.get(i);
Point point =pointList.get(i);
//矩形
floatleft = point.getX();
floatrigth = point.getX() +mRectX;
floattop = point.getY();
floatbottom = point.getY() +mRectY;
if(isHalfScreen){
top = point.getY()+marginTop;
bottom = point.getY() +mRectY+marginTop;
}
if(x>left&xtop&y
// Toast.makeText(getContext(),hashOval.getText(),Toast.LENGTH_SHORT).show();
onClickChartItem.setOnClickChartItem(hashOval);
}
}
}
漂浮的圆环
这里有参考其他人的方法,在屏幕上随机生成圆环,然后向角度的方向移动,偏移圆心,根据圆心画圆环,计算圆环是否碰到边界,可配置圆环的分配,加了一些绘制动画
定义一个类Ball
classBall {
intradius;// 半径
floatcx;// 圆心
floatcy;// 圆心
floatvx;// X轴速度
floatvy;// Y轴速度
Paintpaint;
// 移动
voidmove() {
//向角度的方向移动,偏移圆心
cx+=vx;
cy+=vy;
}
intleft() {
return(int) (cx-radius);
}
intright() {
return(int) (cx+radius);
}
intbottom() {
return(int) (cy+radius);
}
inttop() {
return(int) (cy-radius);
}
onMeasure初始化一些数据
mWidth=resolveSize(mWidth, widthMeasureSpec);
mHeight=resolveSize(mHeight, heightMeasureSpec);
setMeasuredDimension(mWidth,mHeight);
maxRadius=mWidth/12;
minRadius=maxRadius/2;
// 初始化圆的半径和圆心
for(inti=0; i
mBalls[i].radius=mRandom.nextInt(maxRadius+1-minRadius) +minRadius;
// mBalls[i].mass = (int) (Math.PI * mBalls[i].radius * mBalls[i].radius);
// 初始化圆心的位置, x最小为 radius, 最大为mwidth- radius
mBalls[i].cx=mRandom.nextInt(mWidth-mBalls[i].radius) +mBalls[i].radius;
mBalls[i].cy=mRandom.nextInt(mHeight-mBalls[i].radius) +mBalls[i].radius;
}
cyclePaint=newPaint();
center= getWidth() /2;
mRadius= (int) (center-mStrokeWidth/2);
oval=newRectF(center-mRadius,center-mRadius,center+mRadius,center+mRadius);
centerTextPaint=newPaint();
centerTextRect=newRect();
centerTextPaint.getTextBounds(centerText,0,centerText.length(),centerTextRect);
dataPaint=newPaint();
initRingAnimator();
initCenterAnimator();
initDataAnimator();
drawRing画环,根据数据所占比例计算绘制的角度
floatstartPercent =0;
floatsweepPercent =0;
for(inti =0; i
floatbfb =dataList.get(i).getRingData() / sumData(dataList);
// Log.i(TAG, dataList.get(i) + ", " + sumData(dataList) + "," + dataList.get(i).getRingData() / sumData(dataList));
sweepPercent = bfb *360;
// Log.i(TAG, "sweepPercent: " + sweepPercent);
//第一段
cyclePaint.setAntiAlias(true);
cyclePaint.setStyle(Paint.Style.STROKE);
cyclePaint.setStrokeWidth(mStrokeWidth);
cyclePaint.setColor(dataList.get(i).getRingColor());
if(Math.min(sweepPercent -1,animatedRingValue- startPercent) >=0) {
canvas.drawArc(oval, startPercent, Math.min(sweepPercent -1,animatedRingValue- startPercent),false,cyclePaint);
// drawText(canvas,sweepPercent/2+startPercent,dataList.get(i)+"",sweepPercent);
}
startPercent += sweepPercent;
}
drawDataText绘制环上的字,根据不同象限计算
dataPaint.setAntiAlias(true);
dataPaint.setStyle(Paint.Style.FILL);
dataPaint.setTextSize(dataTextSize);
dataPaint.setStrokeWidth(1);
// dataPaint.setAlpha(animatedDataValue);
// dataPaint.setColor(dataTextColor);
intred = (getDataTextColor() &0xff0000) >>16;
intgreen = (getDataTextColor() &0x00ff00) >>8;
intblue = (getDataTextColor() &0x0000ff);
dataPaint.setARGB(animatedDataValue, red, green, blue);
floatdataPre =0;
floatdataCurr =0;
for(inti =0; i
if(i ==0) {
dataPre =0;
}else{
dataPre += (dataList.get(i -1).getRingData() / sumData(dataList)) *360;
}
// Log.i(TAG, "dataPre: " + dataPre);
dataCurr = (dataList.get(i).getRingData() / sumData(dataList)) *360+ dataPre;
// Log.i(TAG, "dataCurr: " + dataCurr);
//一四象限
if(((dataPre + (dataCurr - dataPre) /2)>=0&&(dataPre + (dataCurr - dataPre) /2)<90)||((dataPre + (dataCurr - dataPre) /2)>=270&&(dataPre + (dataCurr - dataPre) /2)<360)){
PointF point2 = MathHelper.getInstance().calcArcEndPointXY(
ball.cx, ball.cy, ball.radius, dataPre + (dataCurr - dataPre) /2);
//标识2
DrawHelper.getInstance().drawRotateText(String.valueOf(dataList.get(i).getRingText()), point2.x, point2.y,0,
canvas,dataPaint);
}else{
//二三象限
PointF point2 = MathHelper.getInstance().calcArcEndPointXY(
ball.cx, ball.cy, ball.radius, dataPre + (dataCurr - dataPre) /2);
//标识2
DrawHelper.getInstance().drawRotateText(String.valueOf(dataList.get(i).getRingText()), point2.x, point2.y,0,
canvas,dataPaint);
}
}
在onDraw里面利用for循环绘制,判断环边界
// 先画出所有圆
for(inti =0; i
Ball ball =mBalls[i];
// canvas.drawCircle(ball.cx, ball.cy, ball.radius, ball.paint);
center=(int) ball.cx;
oval=newRectF(ball.cx- ball.radius,ball.cy- ball.radius, ball.cx+ ball.radius, ball.cy+ ball.radius);
drawRing(canvas);
drawCenterText(canvas,ball);
drawDataText(canvas,ball);
}
// 球碰撞边界
for(inti =0; i
Ball ball =mBalls[i];
collisionDetectingAndChangeSpeed(ball);// 碰撞边界的计算
ball.move();// 移动
}
longstopTime = System.currentTimeMillis();
longrunTime = stopTime - startTime;
// 16毫秒执行一次
postInvalidateDelayed(Math.abs(runTime -16));
判断小球边界
public voidcollisionDetectingAndChangeSpeed(Ball ball) {
intleft = getLeft();
inttop = getTop();
intright = getRight();
intbottom = getBottom();
floatspeedX = ball.vx;
floatspeedY = ball.vy;
// 碰撞左右,X的速度取反。 speed的判断是防止重复检测碰撞,然后黏在墙上了=。=
if(ball.left() <= left && speedX <0) {
ball.vx= -ball.vx;
}else if(ball.top() <= top && speedY <0) {
ball.vy= -ball.vy;
}else if(ball.right() >= right && speedX >0) {
ball.vx= -ball.vx;
}else if(ball.bottom() >= bottom && speedY >0) {
ball.vy= -ball.vy;
}
}