2019-03-05 自定义View绘制线性表
自定义View绘图,学的就是用的,怎么方便怎么来,就像随手涂鸦一样,以后都不会再
拾起,所以没怎么封装,也不标准。
第一次写个稍微完整点的例子,写的不好还请多多指教..
1、先来效果图:
图1 图2 素材1 素材2 素材32、图1的源码:
package com.lipy.linechart;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.Switch;
import java.util.ArrayList;
import java.util.List;
public class TrendLineChartView extends View {
private List<Integer> trendData; //走势数据
private Paint redPaint,blackPaint,greenPaint; //圆点画笔
private Paint realLinePaint,dashLinePaint; //实线和灰线画笔
private int defaultPadding = this.dp2px(getContext(),5); //默认内边距
private int dotRadius = this.dp2px(getContext(),4); //圆点的半径
/**
* 设置走势数据
* 列表首项是最远天数,画在图表的最左边
* 数据值对应 1:红点,0:绿点,-1:黑点
*/
public void setTrendData(List<Integer> trendData){
this.trendData = trendData;
invalidate();
}
public TrendLineChartView(Context context) {
this(context,null);
}
public TrendLineChartView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
dashLinePaint = new Paint();
dashLinePaint.setColor(Color.GRAY);
dashLinePaint.setAlpha(100);
dashLinePaint.setStrokeWidth(1.5f);
realLinePaint = new Paint(dashLinePaint);
realLinePaint.setColor(Color.parseColor("#555555"));
realLinePaint.setAntiAlias(true);
realLinePaint.setStrokeWidth(1.8f);
redPaint = new Paint(realLinePaint);
redPaint.setColor(Color.RED);
redPaint.setStyle(Paint.Style.FILL);
greenPaint = new Paint(redPaint);
greenPaint.setColor(Color.GREEN);
blackPaint = new Paint(greenPaint);
blackPaint.setColor(Color.BLACK);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
drawData(canvas);
}
//绘制数据
private void drawData(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
if (trendData == null){
return;
}
if (trendData.size() <= 1){
//数据只有一条时,默认画在中间
canvas.drawCircle(width/2,getDrawHeight(height,trendData.get(0)),dotRadius,getPaint(trendData.get(0)));
}
else{
float space = (width-defaultPadding*2)/(trendData.size()-1);
//画折线
for (int i = 0; i < trendData.size()-1; i++) {
canvas.drawLine(defaultPadding+i*space,getDrawHeight(height,trendData.get(i)),defaultPadding+(i+1)*space,getDrawHeight(height,trendData.get(i+1)),realLinePaint);
}
//画圆点
for (int i = 0; i < trendData.size(); i++) {
canvas.drawCircle(defaultPadding+i*space,getDrawHeight(height,trendData.get(i)),dotRadius,getPaint(trendData.get(i)));
}
}
}
//画三条虚线组成的背景
private void drawBackground(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
canvas.drawLine(defaultPadding,defaultPadding,width-defaultPadding,defaultPadding,dashLinePaint);
canvas.drawLine(defaultPadding,height/2,width-defaultPadding,height/2,dashLinePaint);
canvas.drawLine(defaultPadding,height-defaultPadding,width-defaultPadding,height-defaultPadding,dashLinePaint);
}
/**
* 更具trendData数据获得对应颜色的画笔
*/
private Paint getPaint(int result){
if (result == -1){
return blackPaint;
}
else if (result == 0){
return greenPaint;
}
else if (result == 1){
return redPaint;
}
return greenPaint;
}
/**
* @param height 控件的总高度
* @param result 数据的走势
* @return 计算出该圆点的Y坐标
*/
private int getDrawHeight(int height,int result){
if (result == -1){
return height - defaultPadding;
}
else if (result == 0){
return height/2;
}
else if (result == 1){
return defaultPadding;
}
return height/2;
}
private int dp2px(Context context, int dip) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dip * scale + 0.5f);
}
private int sp2px(Context context, float spValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, context.getResources().getDisplayMetrics());
}
}
3、图2的源码:
package com.lipy.linechart;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.PathShape;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import java.util.Arrays;
import java.util.List;
public class HitRateLineGridView extends View {
private List<Integer> hitRateData; //命中率数据, 固定的4条数据。
private String[] xLabels; //x轴的标签
private int maxIndex = 0; //命中率最高的一项的索引,设置数据时就会计算
private Bitmap bmLeftTip, bmCenterTip,bmRightTip; //位于三个不同方位的提示图片
private Paint hollowCirclePaint,solidCirclePaint; //空心元和实心圆画笔
private Paint labelFontPaint,tipFontPaint; //标签文字画笔和提示文字画笔
private Paint realLinePaint,dashLinePaint; //折线画笔和背景灰线画笔
private Paint shaderBgPaint; //阴影背景画笔
private int dataColor = Color.parseColor("#d92e48"); //数据颜色,红色
private int defaultPadding = this.dp2px(getContext(),20); //默认边距
private int fontSize = this.sp2px(getContext(),8); //默认字体颜色
private Path shaderPath; //渐变阴影的路径,在绘制折线的时候记录路径
/**
* 设置命中率数据,取最近的7天
* 不需要这个方法,已标记为过时
*/
@Deprecated
public void setHitRateData(List<Integer> hitRateData){
this.hitRateData = hitRateData;
if (hitRateData.size()<=7){
//this.hitRateData = hitRateData;
//++计算统计后再赋值给 hitRateData变量
}
else{
//this.hitRateData = hitRateData.subList(hitRateData.size() - 7,hitRateData.size());
//++计算统计后再赋值给 hitRateData变量
}
invalidate();
}
/**
* 设置命中率数据,已经统计过的数据,注意!只接收固定的4条数据
*分别是近7天的命中率,近5天的命中率,近3天的命中率,和近2天的命中率
*/
public void setHitRateDataCounted(List<Integer> hitRateData){
this.hitRateData = hitRateData;
for (int i = 0; i < hitRateData.size(); i++) {
if (hitRateData.get(maxIndex) < hitRateData.get(i)){
maxIndex = i;
}
}
invalidate();
}
/**
* @param xLabels x轴底部的标签,最多取前4个
*/
private void setXLabels(String[] xLabels){
if (xLabels.length<=4){
this.xLabels = xLabels;
}
else{
this.xLabels = Arrays.copyOfRange(xLabels,0,4);
}
invalidate();
}
public HitRateLineGridView(Context context) {
this(context,null);
}
public HitRateLineGridView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
dashLinePaint = new Paint();
dashLinePaint.setColor(Color.GRAY);
dashLinePaint.setAlpha(100);
dashLinePaint.setStrokeWidth(1.5f);
labelFontPaint = new Paint();
labelFontPaint.setColor(Color.GRAY);
labelFontPaint.setTextSize(fontSize);
setXLabels(new String[]{"近7","近5","近3","近2"} );
realLinePaint = new Paint();
realLinePaint.setColor(dataColor);
realLinePaint.setStrokeWidth(4f);
realLinePaint.setAntiAlias(true);
solidCirclePaint = new Paint(realLinePaint);
solidCirclePaint.setStrokeWidth(1.5f);
//solidCirclePaint.setStyle(Paint.Style.FILL); //设置实心
hollowCirclePaint = new Paint(realLinePaint);
hollowCirclePaint.setColor(Color.WHITE); //采取在一个大实心圆里画一个小白圆来实现空心圆效果的策略
hollowCirclePaint.setStrokeWidth(1.8f);
//hollowCirclePaint.setStyle(Paint.Style.STROKE); //设置空心
tipFontPaint = new Paint();
tipFontPaint.setTextSize(sp2px(getContext(),10));
tipFontPaint.setColor(Color.WHITE);
bmLeftTip = getTipBitmap(R.drawable.bubble_red_left,defaultPadding * 3,defaultPadding);
bmCenterTip = getTipBitmap(R.drawable.bubble_red_mid,defaultPadding * 3,defaultPadding);
bmRightTip = getTipBitmap(R.drawable.bubble_red_right,defaultPadding * 3,defaultPadding);
shaderPath = new Path();
shaderBgPaint = new Paint();
shaderBgPaint.setAntiAlias(true);
shaderBgPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
drawXLabels(canvas);
drawData(canvas);
}
//绘制渐变阴影
private void drawShader(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
LinearGradient lg = new LinearGradient(width/2,height,width/2,0,
Color.parseColor("#00d92e48"),
Color.parseColor("#99d92e48"),
Shader.TileMode.CLAMP);// CLAMP重复最后一个颜色至最后
shaderBgPaint.setShader(lg);
canvas.drawPath(shaderPath,shaderBgPaint);
}
//绘制数据
private void drawData(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
int gridHeight = height - defaultPadding*2;
if (hitRateData == null){
return;
}
float w_space = (width-defaultPadding*2)/3;
//画折线
for (int i = 0; i < 3; i++) {
float startX = defaultPadding+i*w_space;
float startY = (100-hitRateData.get(i)) *gridHeight/100 + defaultPadding;
float stopX = defaultPadding+(i+1)*w_space;
float stopY = (100-hitRateData.get(i+1))*gridHeight/100 + defaultPadding;
canvas.drawLine(startX,startY,stopX,stopY,realLinePaint);
//阴影
if (i == 0){
shaderPath.moveTo(startX,startY);
}
else if(i == 1){
shaderPath.lineTo(startX,startY);
}
else if (i == 2){
shaderPath.lineTo(startX,startY);
shaderPath.lineTo(stopX,stopY);
shaderPath.lineTo(stopX,height-defaultPadding);
shaderPath.lineTo(defaultPadding,height-defaultPadding);
shaderPath.close();
}
}
//画阴影
drawShader(canvas);
//画圆点
for (int i = 0; i < 4; i++) {
if (i == maxIndex){ //最大项单独处理
//画空心圆
canvas.drawCircle(defaultPadding+i*w_space,(100-hitRateData.get(i))*gridHeight/100 + defaultPadding,dp2px(getContext(),5),solidCirclePaint);
canvas.drawCircle(defaultPadding+i*w_space,(100-hitRateData.get(i))*gridHeight/100 + defaultPadding,dp2px(getContext(),3),hollowCirclePaint);
//画提示图
int tipTopValue = (100-hitRateData.get(i))*gridHeight/100 - dp2px(getContext(),6); //提示图y轴方向的位置
if (maxIndex == 0){
float tipLeftValue = i*w_space + dp2px(getContext(),1);
canvas.drawBitmap(bmLeftTip,tipLeftValue , tipTopValue, tipFontPaint);
canvas.drawText("命中率"+hitRateData.get(i)+"%", tipLeftValue + dp2px(getContext(),3), tipTopValue + dp2px(getContext(),12), tipFontPaint);
}
else if (maxIndex == 1){
float tipLeftValue = i*w_space - defaultPadding/2;
canvas.drawBitmap(bmCenterTip, tipLeftValue, tipTopValue, tipFontPaint);
canvas.drawText("命中率"+hitRateData.get(i)+"%", tipLeftValue + dp2px(getContext(),3), tipTopValue + dp2px(getContext(),12), tipFontPaint);
}
else if(maxIndex == 2){
float tipLeftValue = i*w_space - defaultPadding/2;
canvas.drawBitmap(bmCenterTip, tipLeftValue, tipTopValue, tipFontPaint);
canvas.drawText("命中率"+hitRateData.get(i)+"%", tipLeftValue + dp2px(getContext(),3), tipTopValue + dp2px(getContext(),12), tipFontPaint);
}
else if (maxIndex == 3){
float tipLeftValue = i*w_space - defaultPadding - dp2px(getContext(),1);
canvas.drawBitmap(bmRightTip, tipLeftValue, tipTopValue, tipFontPaint);
canvas.drawText("命中率"+hitRateData.get(i)+"%", tipLeftValue + dp2px(getContext(),3), tipTopValue + dp2px(getContext(),12), tipFontPaint);
}
} else{
canvas.drawCircle(defaultPadding+i*w_space,(100-hitRateData.get(i))*gridHeight/100 + defaultPadding,dp2px(getContext(),3),solidCirclePaint);
}
}
}
//绘制x轴下方的标签
private void drawXLabels(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
if (xLabels == null){
return;
}
int space = (width-defaultPadding - defaultPadding) / 3;
Path path = new Path();
for (int i = 0; i < xLabels.length; i++) {
// path.reset(); //用路径也没法实现垂直绘制文字,得拆分了画
// path.moveTo(i*space + defaultPadding,height - defaultPadding + 3);
//path.lineTo(i*space + defaultPadding,height - 3);
// canvas.drawTextOnPath(xLabels[i],path,0f,0f,labelFontPaint);
canvas.drawText(xLabels[i],i*space + defaultPadding/2 + 3,height- defaultPadding/2 + 3,labelFontPaint);
}
}
//绘制背景4*3表格,
private void drawBackground(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
//画5横
canvas.drawLine(defaultPadding,defaultPadding,width-defaultPadding,defaultPadding,dashLinePaint);
canvas.drawLine(defaultPadding,(height - defaultPadding - defaultPadding)/4 + defaultPadding ,width-defaultPadding,(height - defaultPadding - defaultPadding)/4 + defaultPadding,dashLinePaint);
canvas.drawLine(defaultPadding,height/2 ,width-defaultPadding, height/2,dashLinePaint);
canvas.drawLine(defaultPadding,(height - defaultPadding - defaultPadding)*3/4 + defaultPadding ,width-defaultPadding,(height - defaultPadding - defaultPadding)*3/4 + defaultPadding,dashLinePaint);
canvas.drawLine(defaultPadding,height-defaultPadding,width-defaultPadding,height-defaultPadding,dashLinePaint);
//画4竖
canvas.drawLine(defaultPadding,defaultPadding,defaultPadding,height - defaultPadding,dashLinePaint);
canvas.drawLine((width - defaultPadding - defaultPadding) / 3 + defaultPadding,defaultPadding,(width - defaultPadding - defaultPadding) / 3 + defaultPadding,height - defaultPadding,dashLinePaint);
canvas.drawLine((width - defaultPadding - defaultPadding) *2/ 3 + defaultPadding,defaultPadding,(width - defaultPadding - defaultPadding)*2 / 3 + defaultPadding,height - defaultPadding,dashLinePaint);
canvas.drawLine(width-defaultPadding,defaultPadding,width-defaultPadding,height - defaultPadding,dashLinePaint);
}
/**
* @param sourceId 图片的资源id
* @param width 指定宽度
* @param height 指定高度
* @return 适应控件大小的图片
*/
private Bitmap getTipBitmap(int sourceId,int width,int height){
Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(),sourceId);
if (bitmap.getWidth() == width && bitmap.getHeight() == height){ //写这个判断,避createScaledBitmap()的坑
return bitmap;
}
return Bitmap.createScaledBitmap(bitmap,width,height,true);
}
private int dp2px(Context context, int dip) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dip * scale + 0.5f);
}
private int sp2px(Context context, float spValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, context.getResources().getDisplayMetrics());
}
}
4、后话:
这也没啥技术含量,就是官方的api用用,基本的计算公式摆摆,也不多废话了。
反正用代码画图挺好玩的,文章里可能会有些你一时没想到的小技巧吧。