
1.雷达图
思路:定义半径来确定各个点的坐标,先绘制虚线背景再绘制两个覆盖区域。通过改变半径值刷新canvas形成动画,也可以加入其它插值。
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import java.util.List;
/**
* @description: 自定义雷达图
* @author: fanrunqi
* @date: 2018/6/8 09:06
*/
public class RadarView extends View{
private int count = 4;
private float radius;
private float radiusStorage;
private int centerX;
private int centerY;
private Paint mainPaint;
private Paint valuePaint;
private Paint valuePaint2;
private List<Double> data;
private List<Double> data1;
private float maxValue = 100;
private float angle;
public RadarView(Context context) {
this(context, null);
}
public RadarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//设置虚线的间隔和点的长度
PathEffect effects = new DashPathEffect(new float[]{20f,10f}, 0);
//背景画笔
mainPaint = new Paint();
mainPaint.setPathEffect(effects);
mainPaint.setColor(Color.GRAY);
mainPaint.setAntiAlias(true);
mainPaint.setStrokeWidth(1);
mainPaint.setStyle(Paint.Style.STROKE);
//第一个图层画笔
valuePaint=new Paint();
valuePaint.setColor(Color.RED);
valuePaint.setAntiAlias(true);
valuePaint.setStyle(Paint.Style.FILL);
//第二个图层画笔
valuePaint2=new Paint();
valuePaint2.setColor(Color.BLUE);
valuePaint2.setAntiAlias(true);
valuePaint2.setStyle(Paint.Style.FILL);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
radiusStorage = radius = Math.min(w, h) / 2 * 1f;
centerX = w / 2;
centerY = h / 2;
super.onSizeChanged(w, h, oldw, oldh);
startAnim();
}
@Override
protected void onDraw(Canvas canvas) {
drawPolygon(canvas);
drawRegion(valuePaint,data,canvas);
drawRegion(valuePaint2,data1,canvas);
}
/**
* 绘制背景多边形
*/
private void drawPolygon(Canvas canvas) {
Path path = new Path();
angle = (float) (2 * Math.PI / count);
path.reset();
for (int j = 0; j < count; j++) {
if (j == 0) {
path.moveTo(centerX + radiusStorage, centerY);
} else {
/**
* 直角三角形:
* sin(x)是对边比斜边
* cos(x)是底边比斜边
* tan(x)是对边比底边
* 推导出:底边(x坐标)=斜边(半径)*cos(夹角角度),对边(y坐标)=斜边(半径)*sin(夹角角度)
*/
float x = (float) (centerX + radiusStorage * Math.cos(angle * j));
float y = (float) (centerY + radiusStorage * Math.sin(angle * j));
path.lineTo(x, y);
}
}
path.close();
canvas.drawPath(path, mainPaint);
//绘制虚线
for (int i = 0; i < count*2; i++) {
path.reset();
path.moveTo(centerX, centerY);
float x = (float) (centerX + (radiusStorage+18) * Math.cos(angle/2 * i));
float y = (float) (centerY + (radiusStorage+18) * Math.sin(angle/2 * i));
path.lineTo(x, y);
canvas.drawPath(path, mainPaint);
}
}
/**
* 绘制覆盖区域
*/
int Alpha=128;
private void drawRegion(Paint paint,List<Double> data,Canvas canvas){
Path path=new Path();
for (int i = 0; i < count; i++) {
Double perCenter = data.get(i)/maxValue;
double perRadius=perCenter*radius;
float x = (float) (centerX + perRadius * Math.cos(angle * i));
float y = (float) (centerY + perRadius * Math.sin(angle * i));
if(i==0){
path.moveTo(x,y);
}else {
path.lineTo(x,y);
}
}
//闭合覆盖区域
path.close();
//填充覆盖区域
paint.setAlpha(Alpha);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path,paint);
}
public void startAnim(){
ValueAnimator animator = ValueAnimator.ofFloat(0f,radiusStorage);
animator.setDuration(400);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radius = (Float) animation.getAnimatedValue();
Alpha = (int)(radius/radiusStorage*128);
invalidate();
}
});
animator.start();
}
//设置数值
public void setData(List<Double> data,List<Double> data1) {
this.data = data;
this.data1=data1;
}
}
2. 柱状图
思路:先绘制文字和间隔线,再绘制柱状图,通过柱状图最大高度变化形成加载动画,根据点击位置再画一层点击后放大效果。
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Typeface;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.example.duia.zqdemo.R;
import com.example.duia.zqdemo.bean.LeanRecordBean;
import com.example.duia.zqdemo.utils.DensityUtil;
import java.util.ArrayList;
import java.util.List;
/**
* @description: 自定义柱状图
* @author: fanrunqi@qq.com
* @date: 2018/6/9 11:46
*/
public class LearnRecordView extends View {
//柱状图之间的间隔距离
private float interval=2;
//间距的颜色
private String intervalColor ="#f0f0f2";
//view 的宽度
private float viewWidth;
//view 的高度
private float viewHeight;
//数字距离顶部的距离
private float numFromTop;
//文字距离顶部的距离
private float textFromTop;
//柱状图的最大高度;
private float HistogramMaxHeight;
//每个柱状图的宽度
private float Histogramwidth;
//柱状图的背景色
private String HistogramColor ="#f0f0f2";
//数字颜色
private String numColor="#222222";
//文字颜色
private String textColor="#a9a9a9";
//间隔线画笔
private Paint intervalPaint;
//柱状图画笔
private Paint HistogramPaint;
//数字画笔
private Paint numPaint;
//文字画笔
private Paint textPaint;
List<LeanRecordBean> datas;
Context ctx;
public LearnRecordView(Context context) {
this(context, null);
}
public LearnRecordView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LearnRecordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ctx =context;
init();
}
//构造函数初始化
private void init() {
//数字画笔
numPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
numPaint.setTypeface(Typeface.DEFAULT_BOLD);
numPaint.setColor(Color.parseColor(numColor)); // 设置paint颜色
numPaint.setAntiAlias(true); // 设置抗锯齿
numPaint.setTextAlign(Paint.Align.CENTER); // 设置字体居中
//文字画笔
textPaint = new TextPaint();
textPaint.setColor(Color.parseColor(textColor));
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
//间隔线画笔,粗线条
intervalPaint=new Paint();
intervalPaint.setColor(Color.parseColor(intervalColor));
intervalPaint.setAntiAlias(true);
intervalPaint.setStrokeCap( Paint.Cap.SQUARE);
intervalPaint.setStrokeWidth(interval);
//柱状图画笔,画路径连线
HistogramPaint=new Paint();
HistogramPaint.setColor(Color.parseColor(HistogramColor));
HistogramPaint.setAntiAlias(true);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
setAxis();
startAnim();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBg(canvas);
drawHistogram(canvas);
if(curItemClick!=-1){
//画点击效果
drawClickedItem(canvas);
}
}
int curItemClick = -1;
@Override
public boolean onTouchEvent(MotionEvent event) {
/**
* 判断点击位置
*/
if(event.getAction() == MotionEvent.ACTION_DOWN){
float curX = (int)event.getX();
int distance = (int)(interval+Histogramwidth);
float i = curX/distance;
float j = curX%distance;
if (j>interval){
if(curItemClick == (int)i){
curItemClick = -1;
}else {
curItemClick =(int)i;
if(curItemClick>4){
curItemClick=4;
}
}
}
startAnim2();
}
return super.onTouchEvent(event);
}
//阴影的宽度
private float shadowHeight = 30;
//点击放大的距离
private float overflowLength=6;
//间距
private float space = shadowHeight+overflowLength;
//红色画布padding距离
private float padding=20;
/**
* 画点击后的视图
* @param canvas
*/
String [] itemColors={"#fdd124","#17cff9","#ff3f7c"};
String [] itemShadow={"#feefe1","#e1f2fe","#fee1ea"};
private void drawClickedItem(Canvas canvas){
//需要绘制图标的个数
List<Bitmap> list = new ArrayList<>();
if(datas.get(curItemClick).getQuestionFlag()){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.question_white);
list.add(setImgSize(bitmap,imgSize,imgSize));
}
if(datas.get(curItemClick).getVideoFlag()){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.video_white);
list.add(setImgSize(bitmap,imgSize,imgSize));
}
if(datas.get(curItemClick).getLiveFlag()){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.live_white);
list.add(setImgSize(bitmap,imgSize,imgSize));
}
float redHeight = (HistogramMaxHeight/3)*list.size();
Path path = new Path();
path.moveTo(x1,y1);
path.lineTo(x2,y1);
path.lineTo(x2,y2);
path.lineTo(x1,y2);
path.close();
Paint mPaint =new Paint();
mPaint.setShadowLayer(shadowHeight,0,3,Color.parseColor(itemShadow[list.size()-1]));
this.setLayerType(View.LAYER_TYPE_SOFTWARE,mPaint);//设置为SOFTWARE才会实现阴影
mPaint.setColor(Color.WHITE);
mPaint.setAntiAlias(true);
canvas.drawPath(path,mPaint);
//文字和数字
numPaint.setTextSize(DensityUtil.sp2px(ctx,18)); // 设置字体大小DP
textPaint.setTextSize(DensityUtil.sp2px(ctx,12));
drawTextAndNum(curItemClick,canvas);
//绘制布
Path redPath = new Path();
float z1 = x1+padding;
float z2 = x2-padding;
float w2 = y2-padding;
float w1 = w2-redHeight;
redPath.moveTo(z1,w1);
redPath.lineTo(z2,w1);
redPath.lineTo(z2,w2);
redPath.lineTo(z1,w2);
redPath.close();
Paint redPaint =new Paint();
redPaint.setColor(Color.parseColor(itemColors[list.size()-1]));
redPaint.setAntiAlias(true);
canvas.drawPath(redPath,redPaint);
Paint mBitPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBitPaint.setFilterBitmap(true);
mBitPaint.setDither(true);
float distance = (redHeight-imgSize*(list.size()) )/(list.size()+1);
for(int i=0;i<list.size();i++){
int left = (int)( (z2-z1-imgSize)/2+z1 );
int top = (int) ((w2-distance-imgSize)-i*(distance+imgSize) );
canvas.drawBitmap(list.get(i), left, top, mBitPaint);
}
}
private int imgSize=46;
/**
* 重设图片大小
* @param bm
* @param newWidth
* @param newHeight
* @return
*/
public Bitmap setImgSize(Bitmap bm, int newWidth ,int newHeight){
// 获得图片的宽高.
int width = bm.getWidth();
int height = bm.getHeight();
// 计算缩放比例.
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 取得想要缩放的matrix参数.
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// 得到新的图片.
Bitmap newBm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
return newBm;
}
/**
* 画柱状图
* @param canvas
*/
private void drawHistogram(Canvas canvas){
float HMH = 0;
Path path = new Path();
for(int i=0;i<5;i++){
if(0==i){
HMH = HistogramMaxHeight0;
} else if(1==i){
HMH = HistogramMaxHeight1;
}else if(2==i){
HMH = HistogramMaxHeight2;
}else if(3==i ){
HMH = HistogramMaxHeight3;
}else if(4==i){
HMH = HistogramMaxHeight4;
}
float per = datas.get(i).getValue()/3f;
path.moveTo(space+interval+i*(interval+Histogramwidth),space+viewHeight-(per*HMH));
path.lineTo(space+interval+i*(interval+Histogramwidth)+Histogramwidth,space+viewHeight-(per*HMH));
path.lineTo(space+interval+i*(interval+Histogramwidth)+Histogramwidth,space+viewHeight);
path.lineTo(space+interval+i*(interval+Histogramwidth),space+viewHeight);
path.close();
canvas.drawPath(path,HistogramPaint);
}
}
/**
* 画背景
* @param canvas
*/
private void drawBg(Canvas canvas){
for(int i=0;i<5;i++){
numPaint.setTextSize(DensityUtil.sp2px(ctx,15)); // 设置字体大小DP
textPaint.setTextSize(DensityUtil.sp2px(ctx,9));
drawTextAndNum(i,canvas);
//--------------------------画间隔线---------------------
canvas.drawLine( interval/2+i*(Histogramwidth+interval)+space,space,
i*(Histogramwidth+interval)+space, space+viewHeight, intervalPaint );
if(i==4){
i+=1;
canvas.drawLine( space+i*(Histogramwidth+interval),space,
i*(Histogramwidth+interval)+space, space+viewHeight, intervalPaint );
}
}
}
private void drawTextAndNum(int i , Canvas canvas){
//--------------------------画数字---------------------
// float stringWidth = numPaint.measureText(datas.get(i).getDay());
Paint.FontMetrics fontMetrics = numPaint.getFontMetrics();
float stringHeight = (Math.abs(fontMetrics.ascent) - fontMetrics.descent);
float x = interval + Histogramwidth/2+ i*(Histogramwidth+interval)+space;
float y = numFromTop +stringHeight;
canvas.drawText(datas.get(i).getDay(), x, y, numPaint);
//--------------------------画文字---------------------
Paint.FontMetrics fontMetrics1 = textPaint.getFontMetrics();
float stringHeight1 = (Math.abs(fontMetrics.ascent) - fontMetrics.descent);
float x1 = interval + Histogramwidth/2+ i*(Histogramwidth+interval)+space;
float y1 = textFromTop +stringHeight1;
canvas.drawText(datas.get(i).getXinqi(), x1, y1, textPaint);
}
/**
* 初始获取画布坐标
*/
private void setAxis(){
viewWidth = getWidth()-2*space;
viewHeight = getHeight()-2*space;
numFromTop = (float) (viewHeight*0.043478)+space;
textFromTop = (float)(viewHeight*0.162857)+space;
HistogramMaxHeight = (float)(viewHeight*0.707764);
Histogramwidth = (float)((viewWidth-6*interval)/5.0);
}
/**
* 点击放大动画
*/
float x1,x2,y1,y2;
float x11,x22;
public void startAnim2(){
x11=x1 =space+ curItemClick*(interval+Histogramwidth)-overflowLength;
x22=x2 = space+(1+curItemClick)*(interval+Histogramwidth)+overflowLength;
y1 = shadowHeight;
y2 = getHeight()-shadowHeight;
ValueAnimator animator = ValueAnimator.ofFloat(0,100);
animator.setDuration(300);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = (float)animation.getAnimatedValue();
x1 = x11+overflowLength-overflowLength*(v/100);
x2 = x22-overflowLength+overflowLength*(v/100);
y1 = 30+overflowLength - overflowLength*(v/100);
y2 = getHeight()-30-overflowLength+ overflowLength*(v/100);
shadowHeight = 30*(v/100);
invalidate();
}
});
animator.start();
}
/**
* 柱状图加载动画
*/
float HistogramMaxHeight0,HistogramMaxHeight1,HistogramMaxHeight2,HistogramMaxHeight3,HistogramMaxHeight4 =0;
public void startAnim(){
final Handler handler=new Handler();
Runnable runnable=new Runnable() {
@Override
public void run() {
synAnim(count);
if (count < 5) {
handler.postDelayed(this, 100);
count++;
}
}
};
handler.postDelayed(runnable, 100);
}
int count=0;
public void synAnim(final int i){
ValueAnimator animator = ValueAnimator.ofFloat(0,HistogramMaxHeight);
animator.setDuration(700-i*100);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setHistogramMaxHeight((float)animation.getAnimatedValue(),i);
invalidate();
}
});
animator.start();
}
private void setHistogramMaxHeight(float H,int i){
switch (i){
case 0:
HistogramMaxHeight0 = H;
break;
case 1:
HistogramMaxHeight1 = H;
break;
case 2:
HistogramMaxHeight2 = H;
break;
case 3:
HistogramMaxHeight3 = H;
break;
case 4:
HistogramMaxHeight4 = H;
break;
}
}
public void setData(List<LeanRecordBean> datas){
this.datas=datas;
}
}
- 布局和activity设置
布局:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:clipChildren="false"
android:clipToPadding="false"
android:background="#fff"
>
<com.example.duia.zqdemo.view.RadarView
android:layout_margin="20dp"
android:id="@+id/radarview"
android:layout_width="157dp"
android:layout_height="157dp" />
<com.example.duia.zqdemo.view.LearnRecordView
android:layout_marginTop="20dp"
android:id="@+id/learnRecordView"
android:layout_width="300dp"
android:layout_height="188dp" />
</LinearLayout>
Activity:
public class MainActivity extends AppCompatActivity {
RadarView radarView;
LearnRecordView lrview;
List<LeanRecordBean> datas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//--------------------------------雷达图-----------------------------------------------------------
radarView = findViewById(R.id.radarview);
List<Double> data=new ArrayList<>();
data.add(40.0);
data.add(80.0);
data.add(80.0);
data.add(80.0);
List<Double> data1=new ArrayList<>();
data1.add(80.0);
data1.add(70.0);
data1.add(30.0);
data1.add(80.0);
radarView.setData(data,data1);
//----------------------------------柱状图---------------------------------------------------------
lrview = findViewById(R.id.learnRecordView);
int[] values = {1,2,3,1,2};
String [] days ={"Mon","Tue","Wed","Thu","Fri"};
String [] xins ={"21","22","23","24","25"};
datas = new ArrayList<>();
for (int i=0;i<5;i++){
LeanRecordBean bean = new LeanRecordBean();
bean.setDay(xins[i]);
bean.setXinqi(days[i]);
bean.setValue(values[i]);
if(values[i]==1){
bean.setLiveFlag(true);
}else if(values[i]==2){
bean.setLiveFlag(true);
bean.setQuestionFlag(true);
}else if(values[i]==3){
bean.setLiveFlag(true);
bean.setQuestionFlag(true);
bean.setVideoFlag(true);
}
datas.add(bean);
}
lrview.setData(datas);
}
}
工具类:
import android.content.Context;
public class DensityUtil {
/**
* 根据手机的分辨率从 dip 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp==dip
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* 将px值转换为sp值,保证文字大小不变
*
* @param pxValue
* (DisplayMetrics类中属性scaledDensity)
* @return
*/
public static int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
/**
* 将sp值转换为px值,保证文字大小不变
*
* @param spValue
* (DisplayMetrics类中属性scaledDensity)
* @return
*/
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
LeanRecordBean
public class LeanRecordBean {
String day;
String xinqi;
int value;
Boolean videoFlag =false;
Boolean questionFlag =false;
Boolean liveFlag=false;
public String getDay() {
return day;
}
public void setDay(String day) {
this.day = day;
}
public String getXinqi() {
return xinqi;
}
public void setXinqi(String xinqi) {
this.xinqi = xinqi;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Boolean getVideoFlag() {
return videoFlag;
}
public void setVideoFlag(Boolean videoFlag) {
this.videoFlag = videoFlag;
}
public Boolean getQuestionFlag() {
return questionFlag;
}
public void setQuestionFlag(Boolean questionFlag) {
this.questionFlag = questionFlag;
}
public Boolean getLiveFlag() {
return liveFlag;
}
public void setLiveFlag(Boolean liveFlag) {
this.liveFlag = liveFlag;
}
}
网友评论