版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qingfeng812/article/details/52211110
- 这个版本为固定设置漏斗数据;后面提供动态设置数据;
- 具有动画效果;
- 主要麻烦的地方是算坐标点;
效果截图:
![]()
代码 FunnelView:
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
/**
* 漏斗图 v1.0
*/
@SuppressLint("NewApi")
public class FunnelView extends View implements ValueAnimator.AnimatorUpdateListener {
public static final float ANGLE_SCALE = 3.0f;
private List<Integer> mMoneys = new ArrayList<>();
private int maxMoney;
private float phaseX = 1f;
private int textAlpha = 255;
private Paint mPaint1;
private Paint mPaint2;
private Paint mPaint3;
private Paint mPaint4;
private Paint mPaint5;
private Paint mPaint6;
private Paint mPaint7;
private Paint mPaint8;
private Paint mPaint9;
private Paint mPaint10;
private Paint mPaintLine;
private Paint mPaintText;
private Path mPath1;
private Path mPath2;
private Path mPath3;
private Path mPath4;
private Path mPath5;
private Path mPath6;
private Path mPath7;
private Path mPath8;
private Path mPath9;
private Path mPath10;
private float mTotalHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 250, getResources().getDisplayMetrics());
private float mPath1Height;
private float mPath2Height;
private float mPath3Height;
private float mPath4Height;
private float mPath5Height;
private float mPath6Height;
private float mPath7Height;
private float mPath8Height;
private float mPath9Height;
private float mPath10Height;
private float mPath1AngleWidth;
private float mPath2AngleWidth;
private float mPath3AngleWidth;
private float mPath4AngleWidth;
private float mPath5AngleWidth;
private float mPath6AngleWidth;
private float mPath7AngleWidth;
private float mPath8AngleWidth;
private float mPath9AngleWidth;
private float mPath10AngleWidth;
private float mPath3LineStartX;
private float mPath3LineStartY;
private float mPath4LineStartX;
private float mPath4LineStartY;
private float mPath5LineStartX;
private float mPath5LineStartY;
private float maxWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 330, getResources().getDisplayMetrics());
private float maxLineH = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, getResources().getDisplayMetrics());
private float minLineH = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());
private float startOffsetX = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());
private float startOffsetY = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());
private float lineStartOffsetX = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
private float textStartOffsetX = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7, getResources().getDisplayMetrics());
private float mLastX;
private float mLastY;
private float mLastWidth;
public FunnelView(Context context) {
this(context, null);
}
public FunnelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FunnelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* @desc:设置数据
* @author:Arison on 2016/8/15
*/
public void setData(List<Integer> moneys, int maxMoney) {
this.mMoneys = moneys;
this.maxMoney = maxMoney;
calculate();
//int averageHeight = (int) (mTotalHeight / 5);
invalidate();
}
private void calculate() {
for (int i = 0; i < mMoneys.size(); i++) {
int money = mMoneys.get(i);
float scale = (float) money / maxMoney;
switch (i) {
case 0:
mPath1Height = mTotalHeight * scale * phaseX;
if (mPath1Height < minLineH * phaseX) {
mPath1Height = minLineH * phaseX;
} else if (mPath1Height > maxLineH * phaseX) {
mPath1Height = maxLineH * phaseX;
}
mPath1AngleWidth = mPath1Height / ANGLE_SCALE;
//System.out.println("mPath1Height=" + mPath1Height + " ,phaseX=" + phaseX);
break;
case 1:
mPath2Height = mTotalHeight * scale * phaseX;
if (mPath2Height < minLineH * phaseX) {
mPath2Height = minLineH * phaseX;
} else if (mPath2Height > maxLineH * phaseX) {
mPath2Height = maxLineH * phaseX;
}
mPath2AngleWidth = mPath2Height / ANGLE_SCALE;
//System.out.println("mPath2Height=" + mPath2Height);
break;
case 2:
mPath3Height = mTotalHeight * scale * phaseX;
if (mPath3Height < minLineH * phaseX) {
mPath3Height = minLineH * phaseX;
} else if (mPath3Height > maxLineH * phaseX) {
mPath3Height = maxLineH * phaseX;
}
mPath3AngleWidth = mPath3Height / ANGLE_SCALE;
//System.out.println("mPath3Height=" + mPath3Height);
break;
case 3:
mPath4Height = mTotalHeight * scale * phaseX;
if (mPath4Height < minLineH * phaseX) {
mPath4Height = minLineH * phaseX;
} else if (mPath4Height > maxLineH * phaseX) {
mPath4Height = maxLineH * phaseX;
}
mPath4AngleWidth = mPath4Height / ANGLE_SCALE;
//System.out.println("mPath4Height=" + mPath4Height);
break;
case 4:
mPath5Height = mTotalHeight * scale * phaseX;
if (mPath5Height < minLineH * phaseX) {
mPath5Height = minLineH * phaseX;
} else if (mPath5Height > maxLineH * phaseX) {
mPath5Height = maxLineH * phaseX;
}
mPath5AngleWidth = mPath5Height / ANGLE_SCALE;
//System.out.println("mPath5Height=" + mPath5Height);
break;
case 5:
mPath6Height = mTotalHeight * scale * phaseX;
if (mPath6Height < minLineH * phaseX) {
mPath6Height = minLineH * phaseX;
} else if (mPath6Height > maxLineH * phaseX) {
mPath6Height = maxLineH * phaseX;
}
mPath6AngleWidth = mPath6Height / ANGLE_SCALE;
break;
case 6:
mPath7Height = mTotalHeight * scale * phaseX;
if (mPath7Height < minLineH * phaseX) {
mPath7Height = minLineH * phaseX;
} else if (mPath7Height > maxLineH * phaseX) {
mPath7Height = maxLineH * phaseX;
}
mPath7AngleWidth = mPath7Height / ANGLE_SCALE;
break;
case 7:
mPath8Height = mTotalHeight * scale * phaseX;
if (mPath8Height < minLineH * phaseX) {
mPath8Height = minLineH * phaseX;
} else if (mPath8Height > maxLineH * phaseX) {
mPath8Height = maxLineH * phaseX;
}
mPath8AngleWidth = mPath8Height / ANGLE_SCALE;
break;
case 8:
mPath9Height = mTotalHeight * scale * phaseX;
if (mPath9Height < minLineH * phaseX) {
mPath9Height = minLineH * phaseX;
} else if (mPath9Height > maxLineH * phaseX) {
mPath9Height = maxLineH * phaseX;
}
mPath9AngleWidth = mPath9Height / ANGLE_SCALE;
break;
case 9:
mPath10Height = mTotalHeight * scale * phaseX;
if (mPath10Height < minLineH * phaseX) {
mPath10Height = minLineH * phaseX;
} else if (mPath10Height > maxLineH * phaseX) {
mPath10Height = maxLineH * phaseX;
}
mPath10AngleWidth = mPath10Height / ANGLE_SCALE;
break;
}
}
}
private void init() {
mPaint1 = new Paint();
mPaint2 = new Paint();
mPaint3 = new Paint();
mPaint4 = new Paint();
mPaint5 = new Paint();
mPaint6 = new Paint();
mPaint7 = new Paint();
mPaint8 = new Paint();
mPaint9 = new Paint();
mPaint10 = new Paint();
//FF0000 00CCFF FFFF00 00FF00 FF00FF FF9900 993366 C0C0C0 FFCC99
mPaint1.setColor(Color.parseColor("#FF0000"));
mPaint1.setStyle(Paint.Style.FILL);
mPaint1.setDither(true);
mPaint1.setAntiAlias(true);
mPaint2.setColor(Color.parseColor("#00CCFF"));
mPaint2.setStyle(Paint.Style.FILL);
mPaint2.setDither(true);
mPaint2.setAntiAlias(true);
mPaint3.setColor(Color.parseColor("#FFFF00"));
mPaint3.setStyle(Paint.Style.FILL);
mPaint3.setDither(true);
mPaint3.setAntiAlias(true);
mPaint4.setColor(Color.parseColor("#00FF00"));
mPaint4.setStyle(Paint.Style.FILL);
mPaint4.setDither(true);
mPaint4.setAntiAlias(true);
mPaint5.setColor(Color.parseColor("#FF00FF"));
mPaint5.setStyle(Paint.Style.FILL);
mPaint5.setDither(true);
mPaint5.setAntiAlias(true);
mPaint6.setColor(Color.parseColor("#FF9900"));
mPaint6.setStyle(Paint.Style.FILL);
mPaint6.setDither(true);
mPaint6.setAntiAlias(true);
mPaint7.setColor(Color.parseColor("#993366"));
mPaint7.setStyle(Paint.Style.FILL);
mPaint7.setDither(true);
mPaint7.setAntiAlias(true);
mPaint8.setColor(Color.parseColor("#C0C0C0"));
mPaint8.setStyle(Paint.Style.FILL);
mPaint8.setDither(true);
mPaint8.setAntiAlias(true);
mPaint9.setColor(Color.parseColor("#FFCC99"));
mPaint9.setStyle(Paint.Style.FILL);
mPaint9.setDither(true);
mPaint9.setAntiAlias(true);
mPaint10.setColor(Color.parseColor("#FFCC99"));
mPaint10.setStyle(Paint.Style.FILL);
mPaint10.setDither(true);
mPaint10.setAntiAlias(true);
mPaintLine = new Paint();
mPaintText = new Paint();
mPaintLine.setColor(Color.parseColor("#A8ADB2"));
mPaintLine.setStyle(Paint.Style.FILL);
mPaintLine.setStrokeWidth(2);
mPaintText.setColor(Color.parseColor("#A8ADB2"));
mPaintText.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics()));
mPaintText.setAntiAlias(true);
mPaintText.setTextAlign(Paint.Align.LEFT);
}
private void draw1(Canvas canvas) {
mLastX = startOffsetX;
mLastY = startOffsetY;
mLastWidth = maxWidth - startOffsetX;
mPath1 = new Path();
mPath1.moveTo(mLastX, startOffsetY);
mPath1.lineTo(mLastX + mLastWidth, startOffsetY);
mPath1.lineTo(mLastX + mLastWidth - mPath1AngleWidth, mLastY + mPath1Height);
mPath1.lineTo(mLastX + mPath1AngleWidth, mLastY + mPath1Height);
mPath1.close();
canvas.drawPath(mPath1, mPaint1);
mLastWidth = mLastWidth - 2 * mPath1AngleWidth;
mLastX = mLastX + mPath1AngleWidth;
mLastY = mLastY + mPath1Height;
}
private void draw2(Canvas canvas) {
mPath2 = new Path();
mPath2.moveTo(mLastX, mLastY);
mPath2.lineTo(mLastX + mLastWidth, mLastY);
mPath2.lineTo(mLastX + mLastWidth - mPath2AngleWidth, mLastY + mPath2Height);
mPath2.lineTo(mLastX + mPath2AngleWidth, mLastY + mPath2Height);
mPath2.close();
canvas.drawPath(mPath2, mPaint2);
mLastWidth = mLastWidth - mPath2AngleWidth - mPath2AngleWidth;
mLastX = mLastX + mPath2AngleWidth;
mLastY = mLastY + mPath2Height;
}
private void draw3(Canvas canvas) {
mPath3 = new Path();
mPath3.moveTo(mLastX, mLastY);
mPath3.lineTo(mLastX + mLastWidth, mLastY);
mPath3.lineTo(mLastX + mLastWidth - mPath3AngleWidth, mLastY + mPath3Height);
mPath3.lineTo(mLastX + mPath3AngleWidth, mLastY + mPath3Height);
mPath3.close();
canvas.drawPath(mPath3, mPaint3);
mLastWidth = mLastWidth - mPath3AngleWidth - mPath3AngleWidth;
mLastX = mLastX + mPath3AngleWidth;
mLastY = mLastY + mPath3Height;
mPath3LineStartX = mLastX + mLastWidth + mPath3AngleWidth / 2;
mPath3LineStartY = mLastY - mPath3Height / 2;
}
private void draw4(Canvas canvas) {
mPath4 = new Path();
mPath4.moveTo(mLastX, mLastY);
mPath4.lineTo(mLastX + mLastWidth, mLastY);
mPath4.lineTo(mLastX + mLastWidth - mPath4AngleWidth, mLastY + mPath4Height);
mPath4.lineTo(mLastX + mPath4AngleWidth, mLastY + mPath4Height);
mPath4.close();
canvas.drawPath(mPath4, mPaint4);
mLastWidth = mLastWidth - 2 * mPath4AngleWidth;//最新长度
mLastX = mLastX + mPath4AngleWidth;//第四个点的x坐标
mLastY = mLastY + mPath4Height;//第四个点的y坐标
mPath4LineStartX = mLastX + mLastWidth + mPath4AngleWidth / 2;
mPath4LineStartY = mLastY - mPath4Height / 2;
}
private void draw5(Canvas canvas) {
mPath5 = new Path();
mPath5.moveTo(mLastX, mLastY);
mPath5.lineTo(mLastX + mLastWidth, mLastY);
mPath5.lineTo(mLastX + mLastWidth - mPath5AngleWidth, mLastY + mPath5Height);
mPath5.lineTo(mLastX + mPath5AngleWidth, mLastY + mPath5Height);
mPath5.close();
canvas.drawPath(mPath5, mPaint5);
//mLastWidth = maxWidth - startOffsetX;
//mLastX = mLastX;
// mLastY = mLastY + mPath5Height;
// mPath5LineStartX = mLastX + mLastWidth;
// mPath5LineStartY = mLastY - mPath5Height / 2;
mLastWidth = mLastWidth - 2 * mPath5AngleWidth;//最新长度
mLastX = mLastX + mPath5AngleWidth;//第四个点的x坐标
mLastY = mLastY + mPath5Height;//第四个点的y坐标
}
private void draw6(Canvas canvas) {
mPath6 = new Path();
mPath6.moveTo(mLastX, mLastY);
mPath6.lineTo(mLastX + mLastWidth, mLastY);
mPath6.lineTo(mLastX + mLastWidth - mPath6AngleWidth, mLastY + mPath6Height);
mPath6.lineTo(mLastX + mPath6AngleWidth, mLastY + mPath6Height);
mPath6.close();
canvas.drawPath(mPath6, mPaint6);
mLastWidth = mLastWidth - 2 * mPath6AngleWidth;//最新长度
mLastX = mLastX + mPath6AngleWidth;//第四个点的x坐标
mLastY = mLastY + mPath6Height;//第四个点的y坐标
}
private void draw7(Canvas canvas) {
mPath7 = new Path();
mPath7.moveTo(mLastX, mLastY);
mPath7.lineTo(mLastX + mLastWidth, mLastY);
mPath7.lineTo(mLastX + mLastWidth - mPath7AngleWidth, mLastY + mPath7Height);
mPath7.lineTo(mLastX + mPath7AngleWidth, mLastY + mPath7Height);
mPath7.close();
canvas.drawPath(mPath7, mPaint7);
mLastWidth = mLastWidth - 2 * mPath7AngleWidth;//最新长度
mLastX = mLastX + mPath7AngleWidth;//第四个点的x坐标
mLastY = mLastY + mPath7Height;//第四个点的y坐标
}
private void draw8(Canvas canvas) {
mPath8 = new Path();
mPath8.moveTo(mLastX, mLastY);
mPath8.lineTo(mLastX + mLastWidth, mLastY);
mPath8.lineTo(mLastX + mLastWidth - mPath8AngleWidth, mLastY + mPath8Height);
mPath8.lineTo(mLastX + mPath8AngleWidth, mLastY + mPath8Height);
mPath8.close();
canvas.drawPath(mPath8, mPaint8);
mLastWidth = mLastWidth - 2 * mPath8AngleWidth;//最新长度
mLastX = mLastX + mPath8AngleWidth;//第四个点的x坐标
mLastY = mLastY + mPath8Height;//第四个点的y坐标
}
private void draw9(Canvas canvas) {
mPath9 = new Path();
mPath9.moveTo(mLastX, mLastY);
mPath9.lineTo(mLastX + mLastWidth, mLastY);
mPath9.lineTo(mLastX + mLastWidth - mPath9AngleWidth, mLastY + mPath9Height);
mPath9.lineTo(mLastX + mPath9AngleWidth, mLastY + mPath9Height);
mPath9.close();
canvas.drawPath(mPath9, mPaint9);
// mLastWidth = mLastWidth - 2 * mPath9AngleWidth;//最新长度
// mLastX = mLastX + mPath9AngleWidth;//第四个点的x坐标
mLastY = mLastY + mPath9Height;//第四个点的y坐标
}
private void draw10(Canvas canvas) {
mPath10 = new Path();
mPath10.moveTo(mLastX, mLastY);
mPath10.lineTo(mLastX + mLastWidth, mLastY);
mPath10.lineTo(mLastX + mLastWidth - mPath10AngleWidth, mLastY + mPath10Height);
mPath10.lineTo(mLastX + mPath10AngleWidth, mLastY + mPath10Height);
mPath10.close();
canvas.drawPath(mPath10, mPaint10);
mLastWidth = mLastWidth - 2 * mPath10AngleWidth;//最新长度
mLastX = mLastX + mPath10AngleWidth;//第四个点的x坐标
mLastY = mLastY + mPath10Height;//第四个点的y坐标
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
draw1(canvas);
draw2(canvas);
draw3(canvas);
draw4(canvas);
draw5(canvas);
draw6(canvas);
draw7(canvas);
draw8(canvas);
draw9(canvas);
drawText9(canvas);
}
public void animateY() {
ObjectAnimator xAnimator = ObjectAnimator.ofFloat(this, "phaseX", 0, 1);
xAnimator.setDuration(2000);
xAnimator.addUpdateListener(this);
xAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
xAnimator.start();
ObjectAnimator alphaAnimator = ObjectAnimator.ofInt(this, "textAlpha", 0, 255);
alphaAnimator.setDuration(2000);
//alphaAnimator.addUpdateListener(this);
alphaAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
alphaAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
if (specMode == MeasureSpec.EXACTLY) {
height = specSize;
} else {
height = (int) mLastY;
}
System.out.println("onMeasure mLastY=" + mLastY);
setMeasuredDimension(MeasureSpec.getMode(widthMeasureSpec), height);*/
}
public float getPhaseX() {
return phaseX;
}
public void setPhaseX(float phaseX) {
this.phaseX = phaseX;
}
public int getTextAlpha() {
return textAlpha;
}
public void setTextAlpha(int textAlpha) {
this.textAlpha = textAlpha;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
calculate();
postInvalidate();
}
private void drawText1(Canvas canvas) {
Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offY = fontTotalHeight / 2 - fontMetrics.bottom;
//System.out.println("offY=" +offY);
//float newY = baseY + offY;
mPaintText.setAlpha(textAlpha);
canvas.drawText("初期沟通(10%)", maxWidth + textStartOffsetX, mLastY - mPath1Height / 2 + offY, mPaintText);
}
private void drawText2(Canvas canvas) {
Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offY = fontTotalHeight / 2 - fontMetrics.bottom;
//float newY = baseY + offY;
canvas.drawText("立项跟踪(10%)", maxWidth + textStartOffsetX, mLastY - mPath2Height / 2 + offY, mPaintText);
}
private void drawLine3(Canvas canvas) {
canvas.drawLine(mPath3LineStartX + lineStartOffsetX, mPath3LineStartY, maxWidth, mPath3LineStartY, mPaintLine);
}
private void drawText3(Canvas canvas) {
Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offY = fontTotalHeight / 2 - fontMetrics.bottom;
//float newY = baseY + offY;
canvas.drawText("呈报方案(10%)", maxWidth + textStartOffsetX, mLastY - mPath3Height / 2 + offY, mPaintText);
}
private void drawLine4(Canvas canvas) {
canvas.drawLine(mPath4LineStartX + lineStartOffsetX, mPath4LineStartY, maxWidth, mPath4LineStartY, mPaintLine);
}
private void drawText4(Canvas canvas) {
Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offY = fontTotalHeight / 2 - fontMetrics.bottom;
//float newY = baseY + offY;
canvas.drawText("商务谈判(10%)", maxWidth + textStartOffsetX, mLastY - mPath4Height / 2 + offY, mPaintText);
}
private void drawText9(Canvas canvas) {
Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offY = fontTotalHeight / 2 - fontMetrics.bottom;
//float newY = baseY + offY;
canvas.drawText("销售漏斗", mLastX + mLastWidth - mPath9AngleWidth + 3 * textStartOffsetX, mLastY - mPath9Height / 2 + offY, mPaintText);
}
private void drawLine5(Canvas canvas) {
canvas.drawLine(mPath5LineStartX + lineStartOffsetX, mPath5LineStartY, maxWidth, mPath5LineStartY, mPaintLine);
}
private void drawText5(Canvas canvas) {
Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offY = fontTotalHeight / 2 - fontMetrics.bottom;
//float newY = baseY + offY;
canvas.drawText("赢单(10%)", maxWidth + textStartOffsetX, mLastY - mPath5Height / 2 + offY, mPaintText);
}
}