效果图:
-
显示数据
-
隐藏数据
具体实现
- **创建视图View:LineChart **
public class LineChart extends View {
private Paint XPaint; // X轴画笔
private Paint YPaint; // Y轴画笔
private Paint pointPaint; // 点画笔
private Paint circlePaint; // 圆画笔
private Paint shapePaint; // 形状画笔
private Paint effectPaint, effectPaint1; // 效果画笔
private float yandianx, yuandiany, height, wigth; // 原点坐标、高度、宽度
private Context context; // 上下文
private String ysplit[]; // Y轴刻度分割值
private String unitY; // 单位
private String unitX; // 单位
private float max; // 最大值
private int textSize = 7; // 文字大小
private int margin = 20; // 边距
private float gao; // 高度比例
private boolean start = false; // 是否开始绘制
private List<Map<String, Float>> mapList; // 多组数据
private List<Integer> colorList; // 每条折线所对应的颜色
public boolean showPointText = true;
public LineChart(Context context) {
super(context);
this.context = context;
}
public LineChart(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public LineChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LineChart(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.context = context;
}
// 初始化视图
private void initView() {
XPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // X轴画笔
XPaint.setAntiAlias(true);
XPaint.setStrokeWidth(dp2px(1));
YPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // Y轴画笔
YPaint.setAntiAlias(true);
YPaint.setStrokeWidth(dp2px(2));
/*
* ANTI_ALIAS_FLAG:表示开启抗锯齿功能,让画出来的点更加平滑。
setAntiAlias(true):再次开启抗锯齿功能,确保画出来的点更加平滑。
setStyle(Paint.Style.STROKE):设置画笔的样式为描边,即只画出图形的轮廓线。
setStrokeWidth(dp2px(1)):设置画笔的宽度为1dp,使用dp2px()方法将1dp转换为对应的像素值,确保在不同分辨率下画笔的宽度保持一致。*/
pointPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 点画笔
pointPaint.setAntiAlias(true);
pointPaint.setStyle(Paint.Style.STROKE);//Paint.Style.FILL:填充整个图形内部。Paint.Style.FILL_AND_STROKE
pointPaint.setStrokeWidth(dp2px(1));
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建一个圆画笔,设置抗锯齿标志
circlePaint.setAntiAlias(true); // 开启画笔的抗锯齿功能
circlePaint.setStyle(Paint.Style.FILL); // 设置画笔样式为填充
circlePaint.setStrokeWidth(1); // 设置画笔宽度为1
shapePaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建一个形状画笔,设置抗锯齿标志
shapePaint.setAntiAlias(true); // 开启画笔的抗锯齿功能
shapePaint.setStyle(Paint.Style.FILL); // 设置画笔样式为填充
shapePaint.setStrokeWidth(1); // 设置画笔宽度为1
effectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建一个效果画笔1,设置抗锯齿标志
effectPaint.setAntiAlias(true); // 开启画笔的抗锯齿功能
effectPaint.setStyle(Paint.Style.FILL); // 设置画笔样式为填充
effectPaint.setStrokeWidth(1); // 设置画笔宽度为1
effectPaint.setTextSize(sp2px(textSize)); // 设置画笔的文字大小,可以调整为其他值
effectPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建一个效果画笔2,设置抗锯齿标志
effectPaint1.setAntiAlias(true); // 开启画笔的抗锯齿功能
effectPaint1.setStyle(Paint.Style.FILL); // 设置画笔样式为填充
effectPaint1.setStrokeWidth(1); // 设置画笔宽度为1
effectPaint1.setTextSize(sp2px(9)); // 设置画笔的文字大小,
}
@Override
protected void onDraw(Canvas canvas) {
if (start) {
yandianx = dp2px(margin); // 原点x坐标
yuandiany = getMeasuredHeight() - dp2px(margin); // 原点y坐标
wigth = getMeasuredWidth() - dp2px(margin * 2); // 宽度
height = getMeasuredHeight() - dp2px(margin * 2); // 高度
// 绘制Y轴的线条和箭头
float point[] = new float[]{yandianx, yuandiany, yandianx, yandianx, yandianx - dp2px(3), yandianx + dp2px(textSize / 2)};
canvas.drawLines(point, 0, 4, YPaint);
canvas.drawLines(point, 2, 4, YPaint);
canvas.drawLine(yandianx, yandianx, yandianx + dp2px(3), yandianx + dp2px(textSize / 2), YPaint);
// 绘制X轴的线条和箭头
canvas.drawLine(yandianx, yuandiany, yandianx + wigth, yuandiany, YPaint);
canvas.drawLine(yandianx + wigth, yuandiany, yandianx + wigth - dp2px(textSize / 2), yuandiany - dp2px(3), YPaint);
// canvas.drawLine(yandianx + wigth, yuandiany, yandianx + wigth - dp2px(textSize / 2), yuandiany + dp2px(3), YPaint);
// 绘制Y轴0刻度值
canvas.drawText("0", yandianx - sp2px(textSize) - dp2px(2), yuandiany + sp2px(textSize) + dp2px(2), effectPaint);
if (null != unitY) {
// 绘制Y轴单位
canvas.drawText(unitY, yandianx - sp2px(textSize) * unitY.length() / 2, yuandiany - height - dp2px(3), effectPaint);
}
if (null != unitX) {
float xUnitTextWidth = sp2px(textSize) * unitX.length();
// 绘制X轴单位
canvas.drawText(unitX, yandianx + wigth + dp2px(5) - xUnitTextWidth / 2, yuandiany + sp2px(textSize) + dp2px(2), effectPaint);
}
if (null != ysplit && ysplit.length > 0) {
gao = height / (ysplit.length + 1);
// 绘制Y轴刻度线和刻度值
for (int i = 0; i < ysplit.length; i++) {
float a = Float.parseFloat(ysplit[i]);
if (max < a) {
max = a;
}
// canvas.drawLine(yandianx, yuandiany - (i + 1) * gao, yandianx + dp2px(3), yuandiany - (i + 1) * gao, YPaint);
canvas.drawText(ysplit[i], yandianx - (sp2px(textSize) * (ysplit[i].length())), yuandiany - (i + 1) * gao + sp2px(textSize / 2), effectPaint);
}
}
if (mapList.size() > 0) {
float kuan = wigth / (mapList.get(0).size() + 1);
for (int j = 0; j < mapList.size(); j++) {
// 设置点的文字
Map<String, Float> mapx = mapList.get(j);
Object o[] = mapx.keySet().toArray();//获取map里所有的key
XPaint.setColor(colorList.get(j)); // 设置折线的颜色
pointPaint.setColor(colorList.get(j));//设置 点颜色 为 线颜色
// 绘制X轴刻度线、刻度值和曲线
for (int i = 0; i < o.length; i++) {
String text = mapx.get(o[i]).toString();
if (text.equals("-1.0")) {
break;
}
String s = o[i].toString();//转换为字符串(日期)
float x = yandianx + (i + 1) * kuan;
float y = yuandiany - (height - gao) / max * mapx.get(o[i]);
//画X轴
canvas.drawLine(x, yuandiany, x, yuandiany - dp2px(3), YPaint);
//写X轴下的文字(修改距离中心的长度:x越大,离得越远)
canvas.drawText(s, x - (sp2px(textSize) * (s.length() / 4)), yuandiany + sp2px(textSize) + dp2px(3), effectPaint);
if (i > 0) {
canvas.drawLine(yandianx + i * kuan, yuandiany - (height - gao) / max * mapx.get(o[i - 1]), x, y, XPaint);
}
int size = dp2px(3);
// 绘制曲线上的形状(左与下 虚线)
// for (int i1 = 0; i1 < (x - yandianx) / size; i1++) {
// if (i1 % 2 == 0)
// canvas.drawLine(i1 * size + yandianx, y, (i1 + 1) * size + yandianx, y, shapePaint);
// }
//
// for (int i1 = 0; i1 < (yuandiany - y) / size; i1++) {
// if (i1 % 2 == 0)
// canvas.drawLine(x, yuandiany - i1 * size, x, yuandiany - (i1 + 1) * size, shapePaint);
// }
if (showPointText) {
// Y减值点的字体上移,X减左移
canvas.drawText(text, x - text.length() * sp2px(textSize / 4) + 10, y - dp2px(3) - 10, effectPaint1);
}
}
// 绘制曲线上的点
for (int i = 0; i < o.length; i++) {
String text = mapx.get(o[i]).toString();
if (text.equals("-1.0")) {
break;
}
float x = yandianx + (i + 1) * kuan;
float y = yuandiany - (height - gao) / max * mapx.get(o[i]);
canvas.drawCircle(x, y, dp2px((float) 1), circlePaint);
canvas.drawCircle(x, y, dp2px(1), pointPaint);
}
}
}
}
}
public void startDraw(List<Map<String, Float>> mapList, String[] ysplit, String unitY,
String unitX, int margin, int textSize, List<Integer> colorList) {
start = true;
this.mapList = mapList;
this.ysplit = ysplit;
this.unitY = unitY;
this.unitX = unitX;
this.textSize = textSize;
this.margin = margin;
this.colorList = colorList;
initView();
// 修改点的颜色为红色
circlePaint.setColor(Color.WHITE);
invalidate();
}
/**
* sp转换成px
*/
private int sp2px(float spValue) {
float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
/**
* dp转换成px
*/
private int dp2px(float dpValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
- 创建lineChart的XML布局放进layout目录里,名称fragment_zt_fragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 注意,这里要是自己的包名-->
<com.example.test.LineChart
android:id="@+id/lineChart"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- 封装进入Fragment,方便使用(创建Zt_fragment java类继承Fragment)
public class Zt_fragment extends Fragment {
public List<Integer> colorList; // 每条折线所对应的颜色
public List<Map<String, Float>> mapList;
private boolean notXY;// LineChart对象用于显示线形图
// private LineChartNotXY mLineChartNotXY;// LineChart对象用于显示线形图
private LineChart mLineChart;
private String unitY;
private String unitX;
private boolean showPointText = true;//默认不显示点文字
public Zt_fragment(List<Map<String, Float>> mapList, boolean notXY, String unitY, String unitX) {
this.mapList = mapList;
this.notXY = notXY;
this.unitY = unitY;
this.unitX = unitX;
colorList = fillColorList();
}
public Zt_fragment(List<Map<String, Float>> mapList, boolean notXY, String unitY, String unitX, boolean showPointText) {
this.mapList = mapList;
this.notXY = notXY;
this.unitY = unitY;
this.unitX = unitX;
this.showPointText = showPointText;
colorList = fillColorList();
}
private List<Integer> fillColorList() {
List<Integer> colorList = new ArrayList<>();
for (int i = 0; i < mapList.size(); i++) {
//这里,如果是从自定义的文件color.xml里拿数据会失效,可能是局域限制的问题
if (i == 0) {
colorList.add(Color.rgb(64, 224, 208));
} else if (i == 1) {
colorList.add(Color.rgb(253, 83, 23));
} else {//2个之后,颜色随机
Random random = new Random();
colorList.add(Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
}
}
return colorList;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragmen
// if (notXY)
// return inflater.inflate(R.layout.fragment_zt_fragment_notxy, container, false); // 加载布局文件
// else
return inflater.inflate(R.layout.fragment_zt_fragment, container, false); // 加载布局文件
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String[] y = new String[]{"20", "40", "60", "80"};
if (notXY) {
// mLineChartNotXY = getActivity().findViewById(R.id.lineChartNotXY); // 初始化LineChartNotXY对象
// mLineChartNotXY.startDraw(mapList, y, 0, 0, colorList);
// if (!showPointText) {// 不显示点文字
// mLineChartNotXY.showPointText = false;
// }
} else {
mLineChart = getActivity().findViewById(R.id.lineChart); // 初始化LineChart对象
if (!showPointText) {// 不显示点文字
mLineChart.showPointText = false;
}
mLineChart.startDraw(mapList, y, unitY, unitX, 30, 10, colorList);
}
}
}
- 具体使用
public class MainActivity extends AppCompatActivity{
private final int LINE_NUM = 4;//自定义线条数量
private Zt_fragment zt_fragment;
private boolean hidePointTextFlag = true;
private TextView hidePointText;
List<Map<String, Float>> data = new ArrayList<>(); // 创建多组数据的List对象
private String unitX="时间";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注:data变量的数据早自己填充,其中List的size表示有多少条线,
// 里面的map的key为x轴数值,value为y轴的数值,一个键值对对应一个点,一个完整的map对应一条线
Random random = new Random();
for (int i = 0; i < 3; i++) {
Map<String, Float> map = new HashMap<>();
for (int j = 0; j < 9; j++) {
// 生成0-100之间的随机浮点数
float randomFloat = random.nextFloat() * 100;
// 创建键,这里假设键是"Key"加上当前的索引
String key = "Key" + j;
map.put(key, randomFloat);
}
data.add(map);
}
zt_fragment = showLine(data);
hidePointText = (TextView) findViewById(R.id.hidePointText);
findViewById(R.id.hidePointText).setOnClickListener(v -> {
if (hidePointTextFlag) {
hidePointTextExace(true);
hidePointText.setText("显示\n数值");
hidePointTextFlag = false;
} else {
hidePointTextExace(false);
hidePointText.setText("隐藏\n数值");
hidePointTextFlag = true;
}
});
}
/**
* 折线图
*
* @param pointData 折线图的数据点
* @return 折线图对象
*/
private Zt_fragment showLine(List<Map<String, Float>> pointData) {
// 创建折线图对象
Zt_fragment mZt_fragment = new Zt_fragment(pointData, false, "患病率/%", unitX);
// 将折线图对象添加到布局中
FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
fT.replace(R.id.frameLayout, mZt_fragment).commit();
// 返回折线图对象
return mZt_fragment;
}
/**
* 取消显示线
*
* @param zt_fragment 线的Fragment对象
*/
private void cancelLine(Zt_fragment zt_fragment) {
// 获取FragmentManager对象
FragmentManager fragmentManager = getSupportFragmentManager();
// 创建FragmentTransaction对象
FragmentTransaction fT = fragmentManager.beginTransaction();
// 从FragmentTransaction对象中移除线的Fragment对象
fT.remove(zt_fragment);
// 提交FragmentTransaction对象
fT.commit();
}
private void hidePointTextExace(boolean b) {
// 取消上一页的线
cancelLine(zt_fragment);
Zt_fragment mZt_fragmentTemp;
if (b)
mZt_fragmentTemp = new Zt_fragment(data, false, "患病率/%", unitX, false);
else
mZt_fragmentTemp = new Zt_fragment(data, false, "患病率/%", unitX);
// 将折线图对象添加到布局中
FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
fT.replace(R.id.frameLayout, mZt_fragmentTemp).commit();
// 显示当前页的线
zt_fragment = mZt_fragmentTemp;
}
}
- ** activity对应的布局**
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="2dp"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<TextView
android:layout_marginRight="30sp"
android:layout_marginTop="20sp"
android:layout_gravity="top|right"
android:padding="5dp"
android:id="@+id/hidePointText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/radius_background04"
android:text="隐藏\n数值"
android:textColor="#E91E63"
android:textSize="10sp" />
</FrameLayout>
</LinearLayout>
- ** drawable资源,名称:radius_background04.xml**
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="5dp"/>
<solid android:color="#1FBB86FC"/>
</shape>
结束留言
这个东西的二次开发性蛮高的,可以根据自己的项目要求进行更改,另外通过取消线条然后再添加线条(数据跟随改变)还可以实现动态效果。
它也可以取消XY轴的显示,仅仅显示线条。由于这个使用的不多,我也仅用过一次,但有需要的可以自己研究,上面关于我写的部分不显示XY轴的代码还在,只是注释掉了。
最后,看到这条博客的友人,祝君所愿皆如愿,所求皆成,康乐无疆。