最终效果展示
代码实现
package com.example.formdemo.view;
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.view.View;
import androidx.annotation.Nullable;
import java.util.List;
public class FormView extends View {
public FormView(Context context) {
this(context,null);
}
public FormView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public FormView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private float widthSize;
private float heightSize;
private Paint linePaint;
private Paint linePaint2;
private Paint itemsTextPaint;
private Paint dataTextPaint;
private List<String> transverseItems;//transverse 横轴项
private String transverseName;
private String transverseUnit;
private List<String> longitudinalItems;//longitudinal 纵轴项
private String longitudinalName;
private String longitudinalUnit;
private List<List<Integer>> dataList;//数据
private int ALERT_VALUE =100;//警报值超过这个值的数据显示红色
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//减-12 是为了避开View边缘绘制
widthSize=MeasureSpec.getSize(widthMeasureSpec)-12;
heightSize=MeasureSpec.getSize(heightMeasureSpec)-12;
linePaint=new Paint();
linePaint.setColor(Color.BLACK);
linePaint.setStrokeWidth(2);
linePaint2=new Paint();
linePaint2.setColor(Color.BLACK);
linePaint2.setStyle(Paint.Style.STROKE);//设置样式为不填充
linePaint2.setStrokeWidth(2);
linePaint2.setAntiAlias(true);//抗锯齿功能
itemsTextPaint =new Paint();
itemsTextPaint.setColor(Color.BLACK);
itemsTextPaint.setStrokeWidth(2);
itemsTextPaint.setTextSize(34);
itemsTextPaint.setTextAlign(Paint.Align.CENTER);//设置文字居中显示
dataTextPaint =new Paint();
dataTextPaint.setColor(Color.RED);
dataTextPaint.setStrokeWidth(2);
dataTextPaint.setTextSize(30);
dataTextPaint.setTextAlign(Paint.Align.CENTER);//设置文字居中显示
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (transverseItems==null || transverseItems.size()==0)return;
if (longitudinalItems==null || longitudinalItems.size()==0)return;
//绘制竖格线
float transverseStep=widthSize/(transverseItems.size()+1);
for (int k=0;k<=transverseItems.size()+1;k++){
//加6 是为了避开View边缘绘制
float startX=k*transverseStep+6;
float startY=0+6;
float endX=k*transverseStep+6;
float endY=heightSize+6;
canvas.drawLine(startX,startY,endX,endY,linePaint);
}
//绘制横格线
float longitudinalStep=heightSize/(longitudinalItems.size()+1);
for (int k=0;k<=longitudinalItems.size()+1;k++){
//加6 是为了避开View边缘绘制
float startX=0+6;
float startY=k*longitudinalStep+6;
float endX=widthSize+6;
float endY=k*longitudinalStep+6;
canvas.drawLine(startX,startY,endX,endY,linePaint);
}
//绘制外框
canvas.drawLine(0+1,0+1,widthSize+11,0+1,linePaint);//上边缘
canvas.drawLine(widthSize+11,0+1,widthSize+11,heightSize+11,linePaint);//右边缘
canvas.drawLine(widthSize+11,heightSize+11,0+1,heightSize+11,linePaint);//下边缘
canvas.drawLine(0+1,heightSize+11,0+1,0+1,linePaint);//左边缘
//绘制第一个格子的斜切线(分两段绘制)
float X1=0*transverseStep+6;
float Y1=0+6;
float X2=transverseStep/2f;
float Y2=longitudinalStep/2f;
float X3=transverseStep+6;
float Y3=longitudinalStep+6;
//canvas.drawLine(X1,Y1,X3,Y3,linePaint2);//直线斜切
Path path1=new Path();
path1.moveTo(X1,Y1);//起点
path1.quadTo(X2,Y1-4 //控制点
,X2,Y2);//终点
canvas.drawPath(path1,linePaint2);
Path path2=new Path();
path2.moveTo(X2,Y2);//起点
path2.quadTo(X2,Y3+4 //控制点
,X3,Y3);//终点
canvas.drawPath(path2,linePaint2);
//绘制轴名称
canvas.drawText(transverseName,(X3+X2)/2f,(Y3+Y1)/2f+(itemsTextPaint.getTextSize()/5f), itemsTextPaint);
canvas.drawText(longitudinalName,(X2+X1)/2f,(Y3+Y1)/2f+(itemsTextPaint.getTextSize()/5f), itemsTextPaint);
//绘制横轴项
for (int k=0;k<transverseItems.size();k++){
float textX=(transverseStep+6)+(transverseStep/2f)+(k*transverseStep);
float textY=(longitudinalStep/2f)+6+(itemsTextPaint.getTextSize()/5f);
canvas.drawText(transverseItems.get(k),textX,textY, itemsTextPaint);
}
//绘制Y轴项
for (int k=0;k<longitudinalItems.size();k++){
float textX=(transverseStep/2f)+6;
float textY=(longitudinalStep+6)+(longitudinalStep/2f)+(k*longitudinalStep)+(itemsTextPaint.getTextSize()/5f);
canvas.drawText(longitudinalItems.get(k),textX,textY, itemsTextPaint);
}
//数据校验,异常判断
if (dataList.size() != transverseItems.size())return;//要求:横项数=数据长度 (数据与功能项不能对应停止绘制)
for (int k=0;k<dataList.size();k++){
if (dataList.get(k).size()!=longitudinalItems.size())return;//要求:纵项数=子数据长度 (数据与功能项不能对应停止绘制)
}
//绘制数据
for (int k=0;k<dataList.size();k++){
for (int a=0;a<dataList.get(k).size();a++){
float textX=(transverseStep+6)+(transverseStep/2f)+(k*transverseStep);
float textY=(longitudinalStep+6)+(longitudinalStep/2f)+(a*longitudinalStep)+(dataTextPaint.getTextSize()/5f);
//null数据处理
if (dataList.get(k).get(a)==null){ dataList.get(k).set(a,0); }
if (dataList.get(k).get(a)>= ALERT_VALUE){
dataTextPaint.setColor(Color.RED);
}else {
dataTextPaint.setColor(Color.BLUE);
}
canvas.drawText(dataList.get(k).get(a)+"",textX,textY, dataTextPaint);
}
}
}
/**
* 设置横轴数据
* @param items 轴项信息
* @param name 轴名称
* @param unit 单位
*/
public void setTransverse(List<String> items,String name,String unit){
transverseItems=items;
transverseName=name;
transverseUnit=unit;
postInvalidate();
}
/**
* 设置纵轴数据
* @param items 轴项信息
* @param name 轴名称
* @param unit 单位
*/
public void setLongitudinal(List<String> items,String name,String unit){
longitudinalItems=items;
longitudinalName=name;
longitudinalUnit=unit;
postInvalidate();
}
/**
* 设置数据
* @param dataLists
*/
public void setData(List<List<Integer>> dataLists){
this.dataList=dataLists;
postInvalidate();
}
}
使用方法
- 在xml中引用
<com.example.formdemo.view.FormView
android:id="@+id/form_view"
android:layout_margin="20px"
android:layout_width="match_parent"
android:layout_height="600px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
- 导入数据
private void initFormView() {
List<String> trans=new ArrayList<>();
trans.add("已售");
trans.add("库存");
trans.add("破损");
List<String> longit=new ArrayList<>();
longit.add("XS");
longit.add("S");
longit.add("M");
longit.add("L");
longit.add("XL");
longit.add("XXL");
//已售数据
List<Integer> dataList1=new ArrayList<>();
dataList1.add(null);
dataList1.add(67);
dataList1.add(54);
dataList1.add(130);
dataList1.add(45);
dataList1.add(23);
//库存数据
List<Integer> dataList2=new ArrayList<>();
dataList2.add(34);
dataList2.add(121);
dataList2.add(67);
dataList2.add(78);
dataList2.add(34);
dataList2.add(43);
//破损数据
List<Integer> dataList3=new ArrayList<>();
dataList3.add(8);
dataList3.add(4);
dataList3.add(0);
dataList3.add(1);
dataList3.add(6);
dataList3.add(4);
List<List<Integer>> dataList=new ArrayList<>();
dataList.add(dataList1);
dataList.add(dataList2);
dataList.add(dataList3);
FormView formView=(FormView)findViewById(R.id.form_view);
formView.setTransverse(trans,"服装","件");
formView.setLongitudinal(longit,"尺码","");
formView.setData(dataList);
//========模拟变化的数据,实现动效===========
for (int k=0;k<dataList.size();k++){
for (int a=0;a<dataList.get(k).size();a++){
int finalA = a;
int finalK = k;
//异常处理
if (dataList.get(k).get(a)==null){
dataList.get(k).set(a,0);
}
new AbAHeartbeat().start(5, dataList.get(k).get(a), 255, new ActionCallback() {
@Override
public void toDo(Object o) {
dataList.get(finalK).set(finalA, (Integer) o);
formView.setData(dataList);
}
});
}
}
}
动态效果辅助类
package com.example.formdemo.util;
import android.os.Looper;
import android.os.Message;
/**
* 心跳计时器
*/
public class AbAHeartbeat {
//事件标签
public final int ACTION_TAG =0xFF;
//要回调的接口
private ActionCallback thisActionDo;
//Handler事件监听器
private android.os.Handler mHandler = new android.os.Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case ACTION_TAG:
thisActionDo.toDo(msg.obj);
if ((int)msg.obj==maxValue && forTag==true){
doHUi();
}
break;
}
}
};
int dataValue;
int maxValue;
int countDownTime;
public void start(int countDownTime,int dataValue,int maxValue,ActionCallback actionDo){
this.dataValue=dataValue;
this.maxValue=maxValue;
this.countDownTime=countDownTime;
thisActionDo=actionDo;
for (int a=0;a<maxValue;a++){
Message msg = mHandler.obtainMessage();
msg.what = ACTION_TAG;
msg.obj=a+1;
mHandler.sendMessageDelayed(msg,countDownTime*a);
}
};
private boolean forTag=true;
private void doHUi(){
forTag=false;
for (int k=0;k<=maxValue-dataValue;k++){
Message msg2 = mHandler.obtainMessage();
msg2.what = ACTION_TAG;
msg2.obj=maxValue-k;
mHandler.sendMessageDelayed(msg2,countDownTime*k);
}
}
}
package com.example.formdemo.util;
public interface ActionCallback {
void toDo(Object o);
}