最终效果
可滚动条形统计图
代码实现
package com.example.scrollbarchat.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import java.util.List;
public class ScrollBarChart extends View {
public ScrollBarChart(Context context) {
this(context,null);
}
public ScrollBarChart(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ScrollBarChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
Paint paint;
Paint linePaint;
Paint textPaint;
float widthSize;
float heightSize;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
widthSize=MeasureSpec.getSize(widthMeasureSpec);
heightSize=MeasureSpec.getSize(heightMeasureSpec);
int[] mColors = {Color.parseColor("#FF9800"),Color.parseColor("#FFCE85"),Color.parseColor("#FFEBCF")};
LinearGradient linearGradient=new LinearGradient(50, 0, 50, heightSize, mColors, null, Shader.TileMode.MIRROR);
paint=new Paint();
paint.setShader(linearGradient);
linePaint=new Paint();
linePaint.setStrokeWidth(0);
linePaint.setColor(Color.parseColor("#CACACA"));
textPaint=new Paint();
textPaint.setStrokeWidth(2);
textPaint.setColor(Color.parseColor("#FF9800"));
textPaint.setTextSize(30);
textPaint.setTextAlign(Paint.Align.CENTER);
}
boolean initTag=true;
float moveValue=0;//移动值
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制横格条
linePaint.setStrokeWidth(0);
float squareStep=(heightSize-60-60)/39;
for (int k=0;k<40;k++){
if (k==0){
canvas.drawLine(0,1+60,widthSize,1+60,linePaint);
}else {
canvas.drawLine(0,squareStep*k+60,widthSize,squareStep*k+60,linePaint);
}
}
//绘制纵格条
for (int k=0;k<(widthSize/squareStep);k++){
if (k==0){
canvas.drawLine(1,0+60,1,heightSize-60,linePaint);
}else {
canvas.drawLine(k*squareStep,0+60,k*squareStep,heightSize-60,linePaint);
}
}
//约束绘制条件================================================================================
if (dataList==null || dataList.size()==0)return;//约束:非空
if (sectionNameList ==null || sectionNameList.size()==0)return;//约束:非空
if (dataList.size()!= sectionNameList.size())return;//约束:数据项和名称项长度相等
for (int k=0;k<dataList.size()-1;k++){
if (dataList.get(k).size()!=dataList.get(k+1).size())return;//约束:所有子列表长度必须相等
}
//开始绘制数据================================================================================
float maxData=0;//计算出列表最大值
float minData=1000000000;//计算出列表最小值
for (int a=0;a<dataList.size();a++){
for (int k=0;k<dataList.get(a).size();k++){
if (maxData<dataList.get(a).get(k)){
maxData=dataList.get(a).get(k);
}
if (minData>dataList.get(a).get(k)){
minData=dataList.get(a).get(k);
}
}
}
float dataStep=(heightSize-60-60)/maxData;//数据步进
//倒过来绘制,从末尾开始绘制
int dataSize=dataList.size();
int childDataSize=dataList.get(dataSize-1).size();
float barWidth=widthSize/(childDataSize+childDataSize+1);// 条形宽度=布局宽度/{【条形个数+(条形个数+1)】}
float modeRightX=0;
float modeLeftX=0;
for (int a=dataSize;a>0;a--){
List<Integer> childList=dataList.get(a-1);
for (int k=0;k<childList.size();k++){
float left=(widthSize-((-a+dataSize)*widthSize))-((2*k+2)*barWidth)+moveValue;
float top=(heightSize-60)-(childList.get(k))*dataStep;
float right=(widthSize-((-a+dataSize)*widthSize))-((2*k+1)*barWidth)+moveValue;
float bottom=heightSize-60;
//绘制方形条
canvas.drawRect(left, top, right, bottom, paint);
//绘制数据值
textPaint.setColor(Color.parseColor("#FF9800"));
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(30);
if (childList.get(k)==maxData){
textPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(childList.get(k)+"(峰值)",right+dataStep,top+30,textPaint);
}else if (childList.get(k)==minData){
canvas.drawText(childList.get(k)+"(低谷)",(left+right)/2f,top-30,textPaint);
}else {
canvas.drawText(String.valueOf(childList.get(k)),(left+right)/2f,top-30,textPaint);
}
//绘制刻度
linePaint.setStrokeWidth(4);
linePaint.setColor(Color.GRAY);
canvas.drawLine(right,heightSize-60,right,heightSize-60-10,linePaint);
canvas.drawLine(left,heightSize-60,left,heightSize-60-10,linePaint);
//绘制刻度文字
textPaint.setColor(Color.GRAY);
canvas.drawText(scaleList.get(k)+ scaleUnit,(left+right)/2f,heightSize-30,textPaint);
//存储当前模块左右两边的X坐标值
if (k==0)modeRightX=right;
if (k==childList.size()-1)modeLeftX=left;
}
//绘制X轴
linePaint.setStrokeWidth(4);
linePaint.setColor(Color.GRAY);
canvas.drawLine(0,heightSize-60,widthSize,heightSize-60,linePaint);
//绘制区域名称
linePaint.setStrokeWidth(2);
linePaint.setColor(Color.parseColor("#CACACA"));
canvas.drawLine(modeLeftX,30,modeLeftX,60,linePaint);
canvas.drawLine(modeLeftX,30,modeLeftX+(widthSize/2f-200),30,linePaint);
canvas.drawLine(modeRightX,30,modeRightX,60,linePaint);
canvas.drawLine(modeRightX,30,modeRightX-(widthSize/2f-200),30,linePaint);
textPaint.setTextSize(40);
textPaint.setColor(Color.GRAY);
canvas.drawText(sectionNameList.get(a-1),(modeRightX+modeLeftX)/2f,30,textPaint);
}
}
float downX;//按下的X坐标
float oldMoveValue;//之前的移动值
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction()==MotionEvent.ACTION_DOWN){
downX=event.getX();
oldMoveValue=moveValue;
return true;
}
if (event.getAction()==MotionEvent.ACTION_MOVE){
moveValue=(event.getX()-downX)+oldMoveValue;//叠加偏移量
postInvalidate();
return true;
}
if (event.getAction()==MotionEvent.ACTION_UP){
return true;
}
return super.onTouchEvent(event);
}
//设置数据
private List<List<Integer>> dataList;
private String dataUnit ="";
public void setData(List<List<Integer>> dataList,String unit){
this.dataList=dataList;
dataUnit=unit;
postInvalidate();
}
//设置区间名称
private List<String> sectionNameList;
private String sectionUnit ="";
public void setSectionName(List<String> sectionNameList){
this.sectionNameList =sectionNameList;
postInvalidate();
}
private List<Integer> scaleList;
private String scaleUnit ="";
public void setScaleName(List<Integer> scaleList,String unit){
this.scaleList=scaleList;
scaleUnit =unit;
postInvalidate();
}
}
使用方法
- 带xml中引用
<com.example.scrollbarchat.view.ScrollBarChart
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="600px"/>
- 导入数据
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initChart();
Button reset=(Button) findViewById(R.id.reset);
reset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doReset();
}
});
}
private List<Integer> firstYear;
private List<Integer> secondYear;
private List<Integer> thirdYear;
private ScrollBarChart chart;
private void initChart() {
List<Integer> monthList=new ArrayList<>();
monthList.add(12);//12月
monthList.add(11);//11月
monthList.add(10);//10月
monthList.add(9);//9月
monthList.add(8);//8月
monthList.add(7);//7月
monthList.add(6);//6月
monthList.add(5);//5月
monthList.add(4);//4月
monthList.add(3);//3月
monthList.add(2);//2月
monthList.add(1);//1月
firstYear=new ArrayList<>();
firstYear.add(5);//12月
firstYear.add(8);//11月
firstYear.add(34);//10月
firstYear.add(56);//9月
firstYear.add(90);//8月
firstYear.add(145);//7月
firstYear.add(78);//6月
firstYear.add(45);//5月
firstYear.add(37);//4月
firstYear.add(87);//3月
firstYear.add(66);//2月
firstYear.add(108);//1月
secondYear=new ArrayList<>();
secondYear.add(77);//12月
secondYear.add(87);//11月
secondYear.add(90);//10月
secondYear.add(78);//9月
secondYear.add(46);//8月
secondYear.add(145);//7月
secondYear.add(321);//6月
secondYear.add(105);//5月
secondYear.add(98);//4月
secondYear.add(78);//3月
secondYear.add(53);//2月
secondYear.add(56);//1月
thirdYear=new ArrayList<>();
thirdYear.add(108);//12月
thirdYear.add(99);//11月
thirdYear.add(78);//10月
thirdYear.add(56);//9月
thirdYear.add(22);//8月
thirdYear.add(33);//7月
thirdYear.add(44);//6月
thirdYear.add(55);//5月
thirdYear.add(66);//4月
thirdYear.add(77);//3月
thirdYear.add(88);//2月
thirdYear.add(76);//1月
List<List<Integer>> list=new ArrayList<>();
list.add(firstYear);
list.add(secondYear);
list.add(thirdYear);
List<String> modeNameList=new ArrayList<>();
modeNameList.add("2020年");
modeNameList.add("2021年");
modeNameList.add("2022年");
chart=(ScrollBarChart) findViewById(R.id.chart);
chart.setData(list,"件");
chart.setSectionName(modeNameList);
chart.setScaleName(monthList,"月");
doReset();
}
private void doReset() {
//模拟数据实现动态效果
for (int a=0;a<firstYear.size();a++){
new BarHeartbeat().start(30, a, firstYear.get(a), new ActionCallback() {
@Override
public void toDo(Object o) {
List<Integer> list2= (List<Integer>) o;
firstYear.set(list2.get(0),list2.get(1));
List<List<Integer>> list=new ArrayList<>();
list.add(firstYear);
list.add(secondYear);
list.add(thirdYear);
chart.setData(list,"件");
}
});
}
for (int a=0;a<secondYear.size();a++){
new BarHeartbeat().start(30, a, secondYear.get(a), new ActionCallback() {
@Override
public void toDo(Object o) {
List<Integer> list2= (List<Integer>) o;
secondYear.set(list2.get(0),list2.get(1));
List<List<Integer>> list=new ArrayList<>();
list.add(firstYear);
list.add(secondYear);
list.add(thirdYear);
chart.setData(list,"件");
}
});
}
for (int a=0;a<thirdYear.size();a++){
new BarHeartbeat().start(30, a, thirdYear.get(a), new ActionCallback() {
@Override
public void toDo(Object o) {
List<Integer> list2= (List<Integer>) o;
thirdYear.set(list2.get(0),list2.get(1));
List<List<Integer>> list=new ArrayList<>();
list.add(firstYear);
list.add(secondYear);
list.add(thirdYear);
chart.setData(list,"件");
}
});
}
}
}
实现动态效果的辅助类和接口
package com.example.scrollbarchat.unit;
import android.os.Looper;
import android.os.Message;
import com.example.scrollbarchat.Interface.ActionCallback;
import java.util.ArrayList;
import java.util.List;
/**
* 心跳计时器
*/
public class BarHeartbeat {
//事件标签
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:
List<Integer> list= (List<Integer>) msg.obj;
thisActionDo.toDo(list);
break;
}
}
};
//启动
public void start(int countDownTime,int index,int maxNumber,ActionCallback actionDo){
thisActionDo=actionDo;
for (int a=0;a<=maxNumber;a++){
Message msg = mHandler.obtainMessage();
msg.what = ACTION_TAG;
List<Integer> list=new ArrayList<>();
list.add(index);
list.add(a);
msg.obj=list;
mHandler.sendMessageDelayed(msg,countDownTime*a);
}
};
}
package com.example.scrollbarchat.Interface;
public interface ActionCallback {
void toDo(Object o);
}
到此结束,复制使用即可,不需要关注细节实现。