项目中最近用到各种图表,本来打算用第三方的,例如mpandroid,这是一个十分强大的图表库,应用起来十分方便,但是最终发现和设计不太一样,没办法,只能自己写了。今天将写好的柱状图的demo贴在这,该柱状图可根据数据的功能有一下几点:
1. 根据数据的多少,动态的绘制柱状图柱子的条数;
2. 柱状图每条柱子的绘制都有动态的动画效果;
3. 每条柱子有点击事件,点击时弹出提示框,显示相关信息,规定时间后,弹窗自动消失。
好了,先上演示图:
下边贴出相关代码:
自定义柱状图类:
package com.example.histogram; import android.content.context; import android.graphics.canvas; import android.graphics.color; import android.graphics.paint; import android.graphics.rectf; import android.os.handler; import android.text.textpaint; import android.util.attributeset; import android.util.log; import android.view.motionevent; import android.view.view; import com.example.histogram.ui.ui; import java.text.numberformat; /** * created by zhangzdon 2016/6/16 0016. * 柱状图 */ public class histogram extends view implements runnable { private handler handler = new handler(); // 用于延时更新,实现动画 private float animheight; // 进度条动画高度 private paint axislinepaint; // 坐标轴画笔 private paint hlinepaint; // 内部水平虚线画笔 private paint textpaint; // 绘制文本的画笔 private paint recpaint; // 绘制柱状图阴影背景的画笔 private paint datapaint; // 绘制柱状图的画笔 private paint textpaint2; // 绘制白色文本的画笔 private paint textpaint3; // 绘制坐标的画笔 private paint textpaint4; // 绘制x轴上的白色竖线的画笔 private string[] xtitlestring; // x轴刻度 private string[] ytitlestring; // y轴刻度 private string[] data; // 接口返回的indicatordata,用于计算柱子高度 numberformat numberformat; //用于格式化数字 private float currentheight; // 当前柱状图应有的高度,应由计算得来 private int num = -1; // 画多少条柱子,因为存在刚开机数据不足24条的情况 private float mrelativepxinheight; private float mrelativepxinwidth; private onchartclicklistener listener; private int mdist; public void setnum(int num) { this.num = num; invalidate(); } public void setdata(string[] data) { this.data = data; invalidate(); } public void setxtitlestring(string[] title) { this.xtitlestring = title; invalidate(); } public histogram(context context) { this(context, null); } public histogram(context context, attributeset attrs) { this(context, attrs, 0); } public void settitle(string[] title) { this.xtitlestring = title; } public histogram(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); init(context, attrs); } /** * 进行相关初始化操作 * @param context * @param attrs */ private void init(context context, attributeset attrs) { axislinepaint = new paint(); hlinepaint = new paint(); textpaint = new paint(); recpaint = new paint(); datapaint = new paint(); textpaint2 = new paint(); textpaint3 = new paint(); textpaint4 = new paint(); numberformat = numberformat.getnumberinstance(); numberformat.setminimumfractiondigits(3); //设置打印时保留三位小数 axislinepaint.setcolor(color.parsecolor("#dbdde4")); //设置坐标轴的颜色为白色 hlinepaint.setargb(51, 255, 255, 255); textpaint.setcolor(color.parsecolor("#8593a1")); // textpaint.settextsize(29); textpaint.settextsize(ui.dip2px(getcontext(), 12)); recpaint.setcolor(color.parsecolor("#f2f5fc")); datapaint.setcolor(color.cyan); textpaint2.setcolor(color.white); textpaint2.settextsize(ui.dip2px(getcontext(), 12)); textpaint3.setcolor(color.parsecolor("#000000")); textpaint3.settextsize(ui.dip2px(getcontext(), 9)); textpaint4.setcolor(color.parsecolor("#8593a1")); textpaint4.settextsize(ui.dip2px(getcontext(), 6)); axislinepaint.setantialias(true); hlinepaint.setantialias(true); textpaint.setantialias(true); recpaint.setantialias(true); datapaint.setantialias(true); textpaint2.setantialias(true); textpaint3.setantialias(true); textpaint4.setantialias(true); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); if(data == null || xtitlestring == null || num < 0 ) { return; } //绘制y轴刻度 paint.fontmetrics metrics = textpaint3.getfontmetrics(); int decent = (int) metrics.descent; float width = getwidth(); float height = getheight(); //根据原型图得出,图中每px高度在实际中的相对尺寸 mrelativepxinheight = height / 470; //根据原型图得出,图中每px宽度在实际中的相对尺寸 mrelativepxinwidth = width / 690; textpaint3.settextalign(paint.align.right); //绘制纵坐标 ytitlestring = new string[6]; ytitlestring[5] = "0"; ytitlestring[4] = "20"; ytitlestring[3] = "40"; ytitlestring[2] = "60"; ytitlestring[1] = "80"; ytitlestring[0] = "100"; for (int i = 0; i < ytitlestring.length; i++) { canvas.drawtext(ytitlestring[i], 88 * mrelativepxinwidth, (72 + i * 56) * mrelativepxinheight + decent, textpaint3); } //绘制x轴刻度 textpaint3.settextalign(paint.align.center); textpaint4.settextalign(paint.align.center); textpaint textpaint = new textpaint(); textpaint.setcolor(color.parsecolor("#000000")); textpaint.settextsize(ui.dip2px(getcontext(), 9)); //计算柱子之间的间隔 //最左侧位置100 * mrelativepxinwidth,最右侧位置630 epxinwidth, float totalwidth = 630 - 100; // 柱子与之子之间的间隔 mdist = (int) (totalwidth / (xtitlestring.length + 1)); for (int i = 0; i < xtitlestring.length; i++) { //绘制白色竖线 canvas.drawline((100 + (i+1) * mdist) * mrelativepxinwidth, 348 * mrelativepxinheight, (100 + (i+1) * mdist) * mrelativepxinwidth, 352 * mrelativepxinheight, axislinepaint); //绘制x轴文字 canvas.drawtext(xtitlestring[i], (100 + (i+1) * mdist) * mrelativepxinwidth, 370 * mrelativepxinheight, textpaint3); } // 绘制矩形阴影 for (int i = 0; i < num; i++) { rectf rectf = new rectf(); // rectf.left = 111 * relativepxinwidth + i * 22 * relativepxinwidth; // rectf.right = 121 * relativepxinwidth + i * 22 * relativepxinwidth; rectf.left = 95 * mrelativepxinwidth + (i+1) * mdist * mrelativepxinwidth; rectf.right = 105 * mrelativepxinwidth +(i+1) * mdist * mrelativepxinwidth; rectf.top = 70 * mrelativepxinheight; rectf.bottom = 338 * mrelativepxinheight; canvas.drawroundrect(rectf, 10, 10, recpaint); } // 绘制x轴坐标线 for (int i = 0; i < 6; i++) { canvas.drawline(100 * mrelativepxinwidth, (66 + i * 56) * mrelativepxinheight + decent, 630 * mrelativepxinwidth, (66 + i * 56) * mrelativepxinheight + decent, axislinepaint); } // 延时绘制,实现动画效果。数字越大,延时越久,动画效果就会越慢 handler.postdelayed(this, 1); for (int i = 0; i < num; i++) { rectf datarectf = new rectf(); datarectf.left = 95 * mrelativepxinwidth + (i + 1) * mdist * mrelativepxinwidth; datarectf.right = 105 * mrelativepxinwidth + (i + 1) * mdist * mrelativepxinwidth; datapaint.setcolor(color.parsecolor("#3ac2d9")); //获取柱子高度 currentheight = float.parsefloat(data[num - 1 - i]); if (currentheight == 0) { datarectf.top = 346 * mrelativepxinheight; } else if (currentheight == 100) { datarectf.top = 70 * mrelativepxinheight; } else { if (animheight >= currentheight) { datarectf.top = 346 * mrelativepxinheight - currentheight / 100 * 276 * mrelativepxinheight; } else { datarectf.top = 346 * mrelativepxinheight - 276 * mrelativepxinheight * (animheight / 100); } } datarectf.bottom = 346 * mrelativepxinheight; // 限制最高高度 if (datarectf.top < 70 * mrelativepxinheight) { datarectf.top = 70 * mrelativepxinheight; } canvas.drawroundrect(datarectf, 10, 10, datapaint); } } //实现柱子增长的动画效果 @override public void run() { animheight += 1; if (animheight >= 276 * mrelativepxinheight) { return; } else { invalidate(); } } @override public boolean ontouchevent(motionevent event) { switch (event.getaction()) { case motionevent.action_down: { //获取点击坐标 float x = event.getx(); float y = event.gety(); //判断点击点的位置 float leftx = 0; float rightx = 0; for (int i = 0; i < num; i++) { leftx = 95 * mrelativepxinwidth + (i+ 1) * mdist * mrelativepxinwidth - mdist/2 * mrelativepxinwidth; rightx = 105 * mrelativepxinwidth + (i+ 1) * mdist * mrelativepxinwidth + mdist/2 * mrelativepxinwidth; if (x < leftx) { continue; } if (leftx <= x && x <= rightx) { //获取点击的柱子区域的y值 float top = 346 * mrelativepxinheight - float.parsefloat(data[num - 1 - i])/ 100 * 276 * mrelativepxinheight; float bottom = 346 * mrelativepxinheight; if (y >= top && y <= bottom) { //判断是否设置监听 //将点击的第几条柱子,点击柱子顶部的坐值,用于弹出dialog提示数据,还要返回百分比currentheidht = float.parsefloat(data[num - 1 - i]) if(listener != null) { log.e("ss","x" + x +";y:" + y); listener.onclick(i + 1, leftx + mdist/2,top,float.parsefloat(data[num - 1 - i])); } break; } } } break; } case motionevent.action_move: log.e("touch", "action_move"); break; case motionevent.action_up: log.e("touch", "action_up"); break; } return true; } /** * 柱子点击时的监听接口 */ public interface onchartclicklistener { void onclick(int num, float x, float y, float value); } /** * 设置柱子点击监听的方法 * @param listener */ public void setonchartclicklistener(onchartclicklistener listener) { this.listener = listener; } }
在xml文件中的应用:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.histogram.mainactivity"> <textview android:layout_width="match_parent" android:layout_height="40dp" android:gravity="center" android:text="繁忙度指示图(%)" android:textsize="15sp" android:textcolor="#000000" /> <com.example.histogram.histogram android:id="@+id/staticview" android:layout_width="400dp" android:layout_height="500dp" android:layout_gravity="center_horizontal" android:layout_marginbottom="14dp" android:layout_margintop="5dp"/> </linearlayout>
在activity中的实现:
package com.example.histogram; import android.os.bundle; import android.os.handler; import android.support.v7.app.appcompatactivity; import android.util.log; import android.view.view; import android.widget.popupwindow; import android.widget.textview; public class mainactivity extends appcompatactivity { private popupwindow mpopupwindow; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); final histogram histogram = (histogram) findviewbyid(r.id.staticview); string[] data ={"100","20","40","20","80","20","60","30","5","20","60","30","5","5","20","60","30","5"}; final string[] title = {"1","2","3","4","5","6","7","8","9","6","7","8","9","9","6","7","8","9"}; histogram.setnum(title.length); histogram.setdata(data); histogram.setxtitlestring(title); histogram.setonchartclicklistener(new histogram.onchartclicklistener() { @override public void onclick(int num, float x, float y, float value) { //显示提示窗 view inflate = view.inflate(mainactivity.this, r.layout.popupwindow, null); textview textview = (textview) inflate.findviewbyid(r.id.main_tv); textview.settext(value + "%\n" + title[num - 1]); if(mpopupwindow != null) { mpopupwindow.dismiss(); } mpopupwindow = new popupwindow(inflate,140, 60, true); mpopupwindow.settouchable(true); log.e("ss","num" + num +";x" + x+";y"+ y + ";value" + value +";(int)((- histogram.getheight()) + y - 65)" +(int)((- histogram.getheight()) + y - 65) + "histogram.getheight()" + histogram.getheight()); // 设置好参数之后再show // toast.maketext(mainactivity.this, "num" + num +";x" + x+";y"+ y + ";value" + value // +";popupwindow.getwidth()"+ mpopupwindow.getwidth()+";"+ mpopupwindow.getheight(), toast.length_short).show(); mpopupwindow.showasdropdown(histogram,(int)(x - 65),(int)((- histogram.getheight()) + y - 65) ); mpopupwindow.setbackgrounddrawable(getresources().getdrawable(r.mipmap.databg_busyness)); new handler().postdelayed(new runnable(){ public void run() { mpopupwindow.dismiss(); } }, 1000); } }); } }
以上所述是小编给大家介绍的android自定义带增长动画和点击弹窗提示效果的柱状图,实现一个模拟后台数据登入的效果,希望对大家有所帮助
如对本文有疑问, 点击进行留言回复!!
android:screenOrientation=“portrait“ 一直警告报红
Codeforces C. A Cookie for You (模拟 / 分类 / 贪心) (Round #654 Div.2)
DialogFragment弹窗(带黑色透明背景和不带黑色透明背景)
网友评论