1.啥都别说,先上效果图。
2.这效果是平时真实项目开发可能用到的。我也是看到Android交流群里有人要这种效果,于是开始动手写一写。
3.分析:这个自定义View效果 的实现大概分为几步
3.1.分析里面第个部分的内容:大概有:中间的文本内容、所占比例、颜色等,这就需要创建JavaBean来封装这些内容
3.2.有了数据,那个就开始分析绘制的过程
3.2.1.根据比例,先绘制扇形
3.2.2.绘制中间的一个白色圆
3.2.3.绘制中间上面的文本
3.2.4.绘制中间下面的文本内容
4.下面是代码,先创建一个JavaBean,PieData。
public class PieData { public String name;// 名称 public String color;// 颜色 public float ratio;// 比例要通过计算 public float value;// 值 }
5.创建自定义View,PieView
package cn.carhouse.viewdemo.pie; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.view.animation.AccelerateInterpolator; import java.util.Arrays; import java.util.List; /** * 画饼状图 */ public class PieView extends View { // 上面的文字大小和颜色 public static final int TOP_TEXT_SIZE = 16; public static final int TOP_TEXT_COLOR = Color.BLUE; public static final int BOTTOM_TEXT_COLOR = Color.BLACK; public static final int BOTTOM_TEXT_SIZE = 20; // 测试用的,实际开发改成自己的 颜色表 (注意: 此处定义颜色使用的是ARGB,带Alpha通道的) private int[] mColors = {0xFF889933, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080, 0xFFE6B800, 0xFF7CFC00}; private Paint mPaint, mTopTextPaint, mBottomTextPaint; private List<PieData> mPieDataList; private int mWidth, mHeight; private float mRadius, mInnerRadius; private RectF mOval; private float totalRadio;// 总比例 private float mStartAngle = 0; private PieData mPieData; private float animValue; //动画时间 private static final int ANIMATION_DURATION = 500; public PieView(Context context) { this(context, null); } public PieView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public PieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setDither(true); mTopTextPaint = new Paint(); mTopTextPaint.setAntiAlias(true); mTopTextPaint.setDither(true); mTopTextPaint.setColor(Color.BLUE); mTopTextPaint.setTextAlign(Paint.Align.CENTER); mTopTextPaint.setTextSize(dip2px(TOP_TEXT_SIZE)); mTopTextPaint.setColor(TOP_TEXT_COLOR); mBottomTextPaint = new Paint(); mBottomTextPaint.setAntiAlias(true); mBottomTextPaint.setDither(true); mBottomTextPaint.setColor(Color.BLUE); mBottomTextPaint.setTextAlign(Paint.Align.CENTER); mBottomTextPaint.setTextSize(dip2px(BOTTOM_TEXT_SIZE)); mBottomTextPaint.setColor(BOTTOM_TEXT_COLOR); } private float dip2px(int dip) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics()); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); this.mWidth = w; this.mHeight = h; // 半径 mRadius = Math.min(mWidth, mHeight) * 0.70f / 2; mInnerRadius = mRadius * 0.50f; // 扇形绘制的矩形区域 mOval = new RectF(-mRadius, -mRadius, mRadius, mRadius); } @Override protected void onDraw(Canvas canvas) { if (mPieDataList == null || mPieDataList.size() <= 0) { return; } canvas.save(); // 将坐标点移动到View的中心 canvas.translate(mWidth / 2, mHeight / 2); // 1. 画扇形 float startAngle = 0; for (int i = 0; i < mPieDataList.size(); i++) { startAngles[i] = startAngle; PieData pieData = mPieDataList.get(i); float sweepAngle = (pieData.ratio * animValue) * 360 - 0.5f; mPaint.setColor(mColors[i % mColors.length]); canvas.drawArc(mOval, startAngle, sweepAngle, true, mPaint); startAngle += sweepAngle + 0.5f; } // 2.画内圆 mPaint.setColor(Color.WHITE); canvas.drawCircle(0, 0, mInnerRadius, mPaint); // 3.画上面文本 String text = mPieData.name; Rect rect = new Rect(); mTopTextPaint.getTextBounds(text, 0, text.length(), rect); canvas.drawText(text, 0, -rect.height() / 2, mTopTextPaint); // 4.画下面的面文本 text = String.format("%.2f", mPieData.ratio * 100) + "%"; rect = new Rect(); mBottomTextPaint.getTextBounds(text, 0, text.length(), rect); canvas.drawText(text, 0, rect.height() + rect.height() / 2, mBottomTextPaint); canvas.restore(); } //当用户与手机屏幕进行交互的时候(触摸) //触摸事件处理 //1,按下去 //2,移动 //3,抬起 //参数:触摸事件,这个事件是由用户与屏幕交互产生的,这个事件包含上述三种情况 @Override public boolean onTouchEvent(MotionEvent event) { //获取用户对屏幕的行为 int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: //做点击范围的认定 //获取用户点击的位置距当前视图的左边缘的距离 float x = event.getX(); float y = event.getY(); //将点击的x和y坐标转换为以饼状图为圆心的坐标 x = x - mWidth / 2; y = y - mHeight / 2; float touchAngle = getTouchAngle(x, y); float touchRadius = (float) Math.sqrt(x * x + y * y); //判断触摸的点距离饼状图圆心的距离<饼状图对应圆的圆心 if (touchRadius > mInnerRadius && touchRadius < mRadius) { //说明是一个有效点击区域 //查找触摸的角度是否位于起始角度集合中 //binarySearch:参数2在参数1对应的集合中的索引 //未找到,则返回 -(和搜索的值附近的大于搜索值的正确值对应的索引值+1) //{1,2,3} //搜索1:返回值1在集合中对应的索引0 //1.2:返回值为 -(1+1) -2 //1.8:返回值 -(1+1) -2 int searchResult = Arrays.binarySearch(startAngles, touchAngle); if (searchResult < 0) { position = -searchResult - 1 - 1; } else { position = searchResult; } if (mListener != null) { mListener.onSpecialTypeClick(mPieDataList.get(position)); } mPieData = mPieDataList.get(position); invalidate(); } break; } return super.onTouchEvent(event); } //被点击的扇形的位置 private int position; private float[] startAngles; public float getTouchAngle(float x, float y) { float touchAngle = 0; if (x < 0 && y < 0) { //2 象限 touchAngle += 180; } else if (y < 0 && x > 0) { //1象限 touchAngle += 360; } else if (y > 0 && x < 0) { //3象限 touchAngle += 180; } //Math.atan(y/x) 返回正数值表示相对于 x 轴的逆时针转角,返回负数值则表示顺时针转角。 //返回值乘以 180/π,将弧度转换为角度。 touchAngle += Math.toDegrees(Math.atan(y / x)); if (touchAngle < 0) { touchAngle = touchAngle + 360; } return touchAngle; } private OnPieClickListener mListener; public void setOnPieListener(OnPieClickListener mListener) { this.mListener = mListener; } public interface OnPieClickListener { void onSpecialTypeClick(PieData data); } /** * 设置饼状图的数据 */ public void setData(List<PieData> pieList) { this.mPieDataList = pieList; if (mPieDataList != null && mPieDataList.size() > 0) { totalRadio = 0; for (PieData pieData : mPieDataList) { totalRadio += pieData.value; } for (PieData pieData : mPieDataList) { pieData.ratio = pieData.value / totalRadio; } startAngles = new float[mPieDataList.size()]; mPieData = mPieDataList.get(0); // 0-1 ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 1); valueAnimator.setDuration(ANIMATION_DURATION); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { animValue = (float) animation.getAnimatedValue(); invalidate(); } }); valueAnimator.setInterpolator(new AccelerateInterpolator()); valueAnimator.start(); } } }6.处理点击事件:就是重写OnTouchEvent,然后根据点击的x y坐标计算出用户点击的位置是在哪块扇形上。
点击的有效范围: 内圆的半径< (x*x+y*y)开根号<扇形的半径。
7.加上属性动画,让绘制动起来。