Android自定义带增长动画和点击弹窗提示效果的柱状图DEMO
作者:zzd_92 发布时间:2022-10-16 17:10:25
标签:android,柱状图
项目中最近用到各种图表,本来打算用第三方的,例如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自定义带增长动画和点击弹窗提示效果的柱状图,实现一个模拟后台数据登入的效果网站的支持!
来源:http://blog.csdn.net/u011450295/article/details/53095613


猜你喜欢
- 1.使用usb口输入的扫描枪,这里实现使用了winform首先创建一个CS文件using System;using System.Colle
- 引言思考:HashTable是线程安全的,为什么不推荐使用?HashTable是一个线程安全的类,它使用synchronized来锁住整张H
- 本文实例讲述了java获取中文拼音首字母工具类定义与用法。分享给大家供大家参考,具体如下:package com.sw.documentar
- 现在Android智能手机的像素都会提供照相的功能,大部分的手机的摄像头的像素都在1000万以上的像素,有的甚至会更高。它们大多都会支持光学
- 本文实例讲述了C#序列化与反序列化的方法。分享给大家供大家参考。具体分析如下:把“对象”转换为“字节序列”的过程称为对象的序列化。 
- 通过spring注解开发,测试单例和多例区别1.注解和配置两种用法形式配置版:注解版:2.在spring框架中,scope作用域默认是单例的
- 1.后台参数校验Spring Validation验证框架对参数的验证机制提供了@Validated(Spring JSR-303规范,是标
- 可以不用经过 Html.fromHtml 因为我的数据里面含有一点 html的标签。所以经过html转换了。 实现方法: TextView
- 一、JPype简述1.JPype是什么?JPype是一个能够让 python 代码方便地调用 Java 代码的工具,从而克服了 python
- Spring Boot包含许多附加功能,可帮助您在将应用程序投入生产时对其进行监视和管理。可以选择使用HTTP端点或JMX管理和监视您的应用
- 一、概述在软件开发中,有些对象由于创建成本高、访问时需要与其它进程交互等原因,直接访问会造成系统速度慢、复杂度增大等问题。这时可以使用代理模
- 前言回想写过的图书管理系统、租房系统、电影院卖票系统都是基于原生的JavaSE、OOP,没有用到任何框架,在层与层的关系中一个类要想获得与其
- 本文实例为大家分享了android实现圆环倒计时控件的具体代码,供大家参考,具体内容如下1.自定义属性<?xml version=&q
- 前言动态调整线上日志级别是一个非常常见的场景,借助apollo这种配置中心组件非常容易实现。作为apollo的官方技术支持,博主经常在技术群
- 1.Action中的validate()方法Struts2提供了一个Validateable接口,这个接口中只存在validat
- Mybatis批量插入index out of range错误往往我们看到网上关于各类关于批量插入报这种错误的文章都是传入的集合为null,
- 前言:Android开发中,自定义View实现自己想要的效果已成为一项必备的技能,当然自定义View也是Android开发中比较难的部分,涉
- 先看看效果图:中间的圆形头像和光环波形讲解请看:https://www.jb51.net/article/96508.htm周围的气泡布局,
- 单例模式算是设计模式中最容易理解,也是最容易手写代码的模式,但是其中涉及的知识点却一点也不少,所以经常作为面试题来考。一般单例都是五种写法:
- Mybatis获取参数值的两种方式:${},#{}${}本质:字符串拼接,注意:单引号要加上#{}:本质:占位符赋值一、 Mybatis获取