目前一个项目需要用到图表,并且需要一些酷炫屌炸天的效果,无意中搜索到git上的一个关于图表的开源库WilliamChart (应该是好多库都做了这件事,在次先拜谢各位大神牛们的,希望有一天我也可以做到)
话不多说,先上图:
可以看到,一共四种图表:线性图表,正常的直方图,水平直方图以及栈直方图(就是一个条条分成了三段的那个),很漂亮有木有,关键是他还有很牛逼的动画效果(想要看到牛逼的效果,可以下载demo以及库,运行到真机看看,真的很惊艳)
首先提供WilliamChat项目以及demo的下载地址(页面中Ctrl+F关键字“WilliamChat”):
https://github.com/Trinea/android-open-project
另外,demo还需要support-v7的支持,下面讲一下怎么使demo在你的机器上跑起来:
a.将demo,WilliamChat,以及v7都导入到eclipse,v7在sdk的extras\android\support\v7\appcompat目录
b.将v7以及WilliamChat项目作为demo的library,右击demo->properties->Android->add,将WilliamChat和v7都加入进来
如果app是在服务器上用hudson编译,需要集成jar包,推荐使用fatjar,官方还是031版本,有大牛提供了032版本,eclipse的luna版本可用。
032版fatjar下载地址如下:
http://download.youkuaiyun.com/detail/finnfu/8638761
如果大家实在不想做,我已经做好这个jar包,拿去用吧:
http://download.youkuaiyun.com/download/finnfu/8642755
本文首先介绍一下demo,熟悉一下这个的WilliamChat图标库的基本用法,争取下一篇可以分析一下这个开源库的基本实现。
如果已经在真机上跑起来的童鞋可以看到,上面是四个图表,分别是线性图表,正常直方图,横向直方图,栈图表(本文以此为例,其它三个用法一样)
最下面的一排是五个按钮,前四个是设置按钮,最后一个是播放按钮。首先介绍一下各个按钮的作用
右一是play按钮:第一次点击是图表更新的动画,第二次点击是图表消失以及再次出现的动画,动画过程中play按钮为不可点击状态。
右二是动画效果的设置按钮,包括图表消失,出现以及更新的动画,demo中展示了4种,库中一共有10种
右三是图表各项出现/消失顺序的设置按钮,是从左到右/从右到左/从中间到两边
右四是图表出现的起始点/消失的终点设置,动画的开始位置以及结束位置
右五是透明度设置,动画出现/消失的透明度渐变
另外,当点击了各个设置按钮后,play按钮会高亮,此时点击为消失/出现的动画;图表各项都可点击出现数值(tips)
以tackchart为例介绍,贴出其变量的介绍:
<span style="font-size:14px;"><span style="white-space:pre"> </span>/*
* StackBar
*/
//纵轴的坐标值范围
private final static int STACKBAR_MAX = 100;
private final static int STACKBAR_MIN = 0;
//没一个条条对应的字符串,每个字符串为三个字符,对应每个条条的三小段
private final static String[] stackBarLabels = { "YAK", "ANT", "GNU",
"OWL", "APE", "JAY", "COD"};
//第一行数组为每个条第一小段(最上面)的实际数值,依次类推
private final static float[][] stackBarValues = {
{ 30f, 40f, 25f, 25f, 40f, 25f, 25f},
{ 30f, 30f, 25f, 40f, 25f, 30f, 40f,},
{ 30f, 30f, 25f, 25f, 25f, 25f, 25f} };
private static StackBarChartView mStackBarChart;
private Paint mStackBarThresholdPaint;
//tips,点击数据区域会显示的数值或者字母
private TextView mStackBarTooltip;</span>
透明度的设置:
<span style="font-size:14px;"><span style="white-space:pre"> </span>/*
* 点击设置透明度按钮时调用
* @param index 3个透明度等级
* setImageAlpha是在API 16中新加的,设置范围是0-255
* setAlpha从API 1就用,但是在API16中不推荐使用,设置范围是0f-1f
*/
@SuppressLint("NewApi")
private void setAlpha(int index) {
switch (index) {
case 0:
mCurrAlpha = -1;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
mAlphaBtn.setImageAlpha(255);
else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB)
mAlphaBtn.setAlpha(1f);
break;
case 1:
mCurrAlpha = 2;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
mAlphaBtn.setImageAlpha(115);
else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB)
mAlphaBtn.setAlpha(.6f);
break;
case 2:
mCurrAlpha = 1;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
mAlphaBtn.setImageAlpha(55);
else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB)
mAlphaBtn.setAlpha(.3f);
break;
default:
break;
}
}</span>
动画出现消失位置的设置:
<span style="font-size:14px;"><span style="white-space:pre"> </span>/*
* 点击位置设置按钮时调用
* @param index 9处位置设置
* mCurrStartX:0左,1右,-1无效
* mCurrStartY:0底,1顶,-1无效
* 一共8个组合:
* (-1,0):底部 (-1,1):顶部 (0,-1):左边 (1,-1):右边
* (0,0):左下 (0,1):左上 (1,0):右下 (1,1):右上
*/
private void setEnterPosition(int index) {
switch (index) {
case 0:
mCurrStartX = -1f;
mCurrStartY = 0f;
mEnterBtn.setImageResource(R.drawable.enterb);
break;
case 1:
mCurrStartX = 0f;
mCurrStartY = 0f;
mEnterBtn.setImageResource(R.drawable.enterbl);
break;
case 2:
mCurrStartX = 0f;
mCurrStartY = -1f;
mEnterBtn.setImageResource(R.drawable.enterl);
break;
case 3:
mCurrStartX = 0f;
mCurrStartY = 1f;
mEnterBtn.setImageResource(R.drawable.entertl);
break;
case 4:
mCurrStartX = -1f;
mCurrStartY = 1f;
mEnterBtn.setImageResource(R.drawable.entert);
break;
case 5:
mCurrStartX = 1f;
mCurrStartY = 1f;
mEnterBtn.setImageResource(R.drawable.entertr);
break;
case 6:
mCurrStartX = 1f;
mCurrStartY = -1f;
mEnterBtn.setImageResource(R.drawable.enterr);
break;
case 7:
mCurrStartX = 1f;
mCurrStartY = 0f;
mEnterBtn.setImageResource(R.drawable.enterbr);
break;
case 8:
mCurrStartX = .5f;
mCurrStartY = .5f;
mEnterBtn.setImageResource(R.drawable.enterc);
break;
default:
break;
}
}</span>
图表出现/消失/更新的动画效果设置:
<span style="font-size:14px;"><span style="white-space:pre"> </span>/*
* 点击设置消失动画的按钮调用
* @param index 此处用了4种,library里面一共定义了10种
*/
private void setEasing(int index) {
switch (index) {
case 0:
mCurrEasing = new CubicEaseOut();
mEaseBtn.setImageResource(R.drawable.ease_cubic);
break;
case 1:
mCurrEasing = new QuintEaseOut();
mEaseBtn.setImageResource(R.drawable.ease_quint);
break;
case 2:
mCurrEasing = new BounceEaseOut();
mEaseBtn.setImageResource(R.drawable.ease_bounce);
break;
case 3:
mCurrEasing = new ElasticEaseOut();
mEaseBtn.setImageResource(R.drawable.ease_elastic);
default:
break;
}
}</span>
图表中各项出现/消失/更新的顺序设置,这里需要之一的是顺序与图表数据个数是相关的,所以在自己实现的时候需要注意:
<span style="font-size:14px;"><span style="white-space:pre"> </span>/*
* 点击顺序设置按钮时调用
* @param index 4种消失顺序
* 顺序采用数组来定义:beginOrder endOrder middleOrder
* mCurrOverlapFactor为1代表同时开始
* 注意:当图表的数据发生改变的时候,上述定义顺序的数组也要相应更改
*/
private void setOverlap(int index) {
switch (index) {
case 0:
mCurrOverlapFactor = 1;
mCurrOverlapOrder = beginOrder;
mOrderBtn.setImageResource(R.drawable.ordere);
break;
case 1:
mCurrOverlapFactor = .5f;
mCurrOverlapOrder = beginOrder;
mOrderBtn.setImageResource(R.drawable.orderf);
break;
case 2:
mCurrOverlapFactor = .5f;
mCurrOverlapOrder = endOrder;
mOrderBtn.setImageResource(R.drawable.orderl);
break;
case 3:
mCurrOverlapFactor = .5f;
mCurrOverlapOrder = middleOrder;
mOrderBtn.setImageResource(R.drawable.orderm);
break;
default:
break;
}
}</span>
<span style="font-size:14px;"><span style="white-space:pre"> </span>/*
* 图标的消失出场动画以及更新动画都会调用这个函数
* @param boolean newAnim
* 1.为 true的时候调用的当前值mCurr
* 2.为false的时候调用的old值
* 3.在demo中可以看到,点击了设置以后,动画消失还是之前的效果,动画出现是才是设置后的效果,就因为函数传入的是false
*/
private Animation getAnimation(boolean newAnim) {
if (newAnim)
return new Animation().setAlpha(mCurrAlpha).setEasing(mCurrEasing)
.setOverlap(mCurrOverlapFactor, mCurrOverlapOrder)
.setStartPoint(mCurrStartX, mCurrStartY);
else
return new Animation().setAlpha(mOldAlpha).setEasing(mOldEasing)
.setOverlap(mOldOverlapFactor, mOldOverlapOrder)
.setStartPoint(mOldStartX, mOldStartY);
}</span>
getAnimation会在play的onclick以及updateStackBarChart(以stackchart为例)调用,下面来分析一下这这两个函数。在play按钮的onclick中,终点关注一下mNewInstance的变化,初始化为false,点击了设置按钮置为true,点击play置反。dismissAllTooltips为所有的tips消失,dismiss为所有的图表消失,关注一下mExitEndAction
<span style="font-size:14px;"><pre name="code" class="java"><span style="white-space:pre"> </span>/*
* 设置play按钮的监听事件,点击发生如下事件
* 1.数值显示消失
* 2.如果mNewInstance为true则执行图表收起再出现的动画
* 3.如果mNewInstance为false则执行图表更新的动画
*/
mPlayBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mPlayBtn.setImageResource(R.drawable.play);
mPlayBtn.setBackgroundResource(R.drawable.button);
mPlayBtn.setEnabled(false);
mLineChart.dismissAllTooltips();
mLineTooltip = null;
mBarChart.dismissAllTooltips();
mBarTooltip = null;
mHorBarChart.dismissAllTooltips();
mHorBarTooltip = null;
mStackBarChart.dismissAllTooltips();
mStackBarTooltip = null;
if (mNewInstance) {
mLineChart.dismiss(getAnimation(false).setEndAction(null));
mBarChart.dismiss(getAnimation(false).setEndAction(null));
mHorBarChart
.dismiss(getAnimation(false).setEndAction(null));
mStackBarChart.dismiss(getAnimation(false).setEndAction(
mExitEndAction));
} else {
updateValues(mLineChart);
updateValues(mBarChart);
updateValues(mHorBarChart);
updateValues(mStackBarChart);
}
mNewInstance = !mNewInstance;
}
});
/*
* 在动画消失后500毫秒延迟执行
* 1.mCurr值赋给old值
* 2.调用update使图表出现
* */
private final Runnable mExitEndAction = new Runnable() {
@Override
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
mOldOverlapFactor = mCurrOverlapFactor;
mOldOverlapOrder = mCurrOverlapOrder;
mOldEasing = mCurrEasing;
mOldStartX = mCurrStartX;
mOldStartY = mCurrStartY;
mOldAlpha = mCurrAlpha;
updateLineChart();
updateBarChart();
updateHorBarChart();
updateStackBarChart();
}
}, 500);
}
};</span>
再来看一下updateStackBarChart函数,各种初始化完毕,最后调用getAnimation函数show出来,可以关注一下mEnterEndAction:
<span style="font-size:14px;"><span style="white-space:pre"> </span>/*
* 1.每个数据对应一个bar,bar添加到stackBarSet,stackBarSet添加到mStackBarChart,三个for循环对应三段
* 2.以及图表的相关设置:每个图表的每一段的颜色,各种间距,圆角半径,刻度,坐标轴,动画等
* */
private void updateStackBarChart() {
mStackBarChart.reset();
BarSet stackBarSet = new BarSet();
Bar bar;
for (int i = 0; i < stackBarLabels.length; i++) {
bar = new Bar(stackBarLabels[i], stackBarValues[0][i]);
if (i == 2)
bar.setColor(this.getResources().getColor(
R.color.stackbar_fill1_h));
else
bar.setColor(this.getResources().getColor(
R.color.stackbar_fill1));
stackBarSet.addBar(bar);
}
mStackBarChart.addData(stackBarSet);
stackBarSet = new BarSet();
for (int i = 0; i < stackBarLabels.length; i++) {
bar = new Bar(stackBarLabels[i], stackBarValues[1][i]);
if (i == 2)
bar.setColor(this.getResources().getColor(
R.color.stackbar_fill2_h));
else
bar.setColor(this.getResources().getColor(
R.color.stackbar_fill2));
stackBarSet.addBar(bar);
}
mStackBarChart.addData(stackBarSet);
stackBarSet = new BarSet();
for (int i = 0; i < stackBarLabels.length; i++) {
bar = new Bar(stackBarLabels[i], stackBarValues[2][i]);
if (i == 2)
bar.setColor(this.getResources().getColor(
R.color.stackbar_fill3_h));
else
bar.setColor(this.getResources().getColor(
R.color.stackbar_fill3));
stackBarSet.addBar(bar);
}
mStackBarChart.setBackgroundColor(getResources().getColor(R.color.stackbar_background));
mStackBarChart.addData(stackBarSet);
//每个图表之前的间距
mStackBarChart.setBarSpacing(Tools.fromDpToPx(20));
//圆角的半径
mStackBarChart.setRoundCorners(Tools.fromDpToPx(5));
//step为纵轴的刻度值(与setAxisBorderValues里面的step设置一样),距离左右的边距
mStackBarChart.setStep(10).setBorderSpacing(Tools.fromDpToPx(5))
//垂直坐标的最大值最小值以及刻度
.setAxisBorderValues(STACKBAR_MIN, STACKBAR_MAX, 25)
//X.Y轴的相关设置,是否有轴线(有/没有),刻度在的位置(里/外/无)
.setXAxis(false).setXLabels(XController.LabelPosition.OUTSIDE)
.setYAxis(true).setYLabels(YController.LabelPosition.OUTSIDE)
//画图的相关设置
.setThresholdLine(89, mStackBarThresholdPaint)
//刻度的格式
.setLabelsFormat(new DecimalFormat("###'%'"))
//进场动画的设置以及动画结束后play按钮的变化
.show(getAnimation(true).setEndAction(mEnterEndAction))
// .show()
;
}</span>
<span style="font-size:14px;">//在图表出现动画结束后调用,play按钮的置为可点击
private final Runnable mEnterEndAction = new Runnable() {
@Override
public void run() {
mPlayBtn.setEnabled(true);
}
};</span>
<span style="font-size:14px;"><span style="white-space:pre"> </span>initMenu();
initLineChart();
initBarChart();
initHorBarChart();
initStackBarChart();
updateLineChart();
updateBarChart();
updateHorBarChart();
updateStackBarChart();</span>
看一下initMenu(),initStackBarChart() ,updateStackBarChart()
initMenu主要就是设置四大设置按钮以及play按钮的监听,设置的点击事件触发开始介绍的那些设置函数。
updateStackBarChart已经已经介绍过,就是图表的出现,会在play按钮点击事件触发,以及图表消失触发。
initStackBarChart就是图表的一些相关初始化工作以及相关监听器的设置
<span style="font-size:14px;">/*
* 1.图表空白区域的点击事件监听
* 2.图表图形(数据)区域的点击事件监听
* 3.画笔的设置
*/
private void initStackBarChart() {
mStackBarChart = (StackBarChartView) findViewById(R.id.stackbarchart);
mStackBarChart.setOnEntryClickListener(stackBarEntryListener);
mStackBarChart.setOnClickListener(stackBarClickListener);
mStackBarThresholdPaint = new Paint();
mStackBarThresholdPaint.setColor(this.getResources().getColor(
R.color.stackbar_line));
mStackBarThresholdPaint.setPathEffect(new DashPathEffect(new float[] {
10, 20 }, 0));
mStackBarThresholdPaint.setStyle(Paint.Style.STROKE);
mStackBarThresholdPaint.setAntiAlias(true);
mStackBarThresholdPaint.setStrokeWidth(Tools.fromDpToPx(.75f));
}</span>
说一下上面两个监听器:stackBarEntryListener和stackBarClickListener:
<span style="font-size:14px;">/*
* 图表图形(数据)区域的监听器
* 1.首次点击调用showStackBarTooltip,出现tip
* 2.非首次点击调用,调用dismissStackBarTooltip,消除之前的tip,产生新的tip
*/
private final OnEntryClickListener stackBarEntryListener = new OnEntryClickListener() {
@Override
public void onClick(int setIndex, int entryIndex, Rect rect) {
if (mStackBarTooltip == null)
showStackBarTooltip(setIndex, entryIndex, rect);
else
dismissStackBarTooltip(setIndex, entryIndex, rect);
}
};</span>
<span style="font-size:14px;">/*
* 图表空白区域的监听,点击会消除tip
* 其中第二个参数传入-1,所以只会消除tips
* */
private final OnClickListener stackBarClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (mStackBarTooltip != null)
dismissStackBarTooltip(-1, -1, null);
}
};</span>
调用showStackBarTooltip和dismissStackBarTooltip
<span style="font-size:14px;">/*
* 第一次点击图表(此时没有tip),用来在点击位置new一个tip并显示
* @param entryIndex 横轴坐标的字符串在barLabels字符数组中的下表
* @param setIndex 字符串的的字符的下表
* @param rect 为栈图表上的矩形区域,将其layoutParams设置给mStackBarTooltip
*/
@SuppressLint("NewApi")
private void showStackBarTooltip(int setIndex, int entryIndex, Rect rect) {
mStackBarTooltip = (TextView) getLayoutInflater().inflate(
R.layout.stackbar_tooltip, null);
mStackBarTooltip.setText("" + stackBarLabels[entryIndex].charAt(setIndex));
LayoutParams layoutParams = new LayoutParams(rect.width(),
rect.height());
layoutParams.leftMargin = rect.left;
layoutParams.topMargin = rect.top;
mStackBarTooltip.setLayoutParams(layoutParams);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
mStackBarTooltip.setPivotX(0);
mStackBarTooltip.setAlpha(0);
mStackBarTooltip.setScaleX(0);
mStackBarTooltip.animate().setDuration(200).alpha(1).scaleX(1)
.setInterpolator(enterInterpolator);
}
mStackBarChart.showTooltip(mStackBarTooltip);
}
/*
* 点击空白区域/数据区域都会调用
* 如果点击数据区域,会传入setIndex,entryIndex,rect,所以是tips在原位置消失,新位置出现
* 点击空白区域时,参数为-1和null,所以只会执行tips消失的操作
* */
@SuppressLint("NewApi")
private void dismissStackBarTooltip(final int setIndex,
final int entryIndex, final Rect rect) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mStackBarTooltip.animate().setDuration(100).scaleX(0).alpha(0)
.setInterpolator(exitInterpolator)
.withEndAction(new Runnable() {
@Override
public void run() {
mStackBarChart.removeView(mStackBarTooltip);
mStackBarTooltip = null;
if (entryIndex != -1)
showStackBarTooltip(setIndex, entryIndex, rect);
}
});
} else {
mStackBarChart.dismissTooltip(mStackBarTooltip);
mStackBarTooltip = null;
if (entryIndex != -1)
showStackBarTooltip(setIndex, entryIndex, rect);
}
}</span>
以上,这个demo基本介绍完了,其它三种图表类似。有时间再研究一下这个库的实现,到时候再与大家分享。