MPAndroidChart项目实战(八)——自定义分段堆积柱状
本文出自:http://blog.csdn.net/dt235201314/article/details/77534468
一丶效果图
image.png二丶需求分析及技术点
1.如效果图显示,当一样产品评论越多柱子越高可以展现热度,同一柱子不同颜色不同长度展示评论好坏对比,
自定义MarkView则显示详细数据,这就是分段堆积柱状图的优势所在。
2.MPAndroidChart实现存在的问题:1品类下面的总数不能随柱状图滑动而滑动;2.间距无法调整
3.技术点分析
组合自定义View的实现参考上文:
http://blog.csdn.net/dt235201314/article/details/77248347
MarkView的实现
PopupWindow的灵活使用
4.图解
image.png三丶核心代码
1.造数据
public List<Source> parseData() {
list = new ArrayList<>();
Random r = new Random();
for (int i= 0;i<=6;i++){
Source source = new Source();
source.setBadCount(r.nextInt(100));
source.setGoodCount(r.nextInt(100));
source.setOtherCount(r.nextInt(100));
source.setScale(r.nextInt(100));
source.setSource("品类" + i);
source.setAllCount(source.getBadCount() + source.getGoodCount() + source.getOtherCount());
list.add(source);
}
return list;
}
2.分段堆积柱状图实体类相关属性(get,set方法略)
public class BarEntity {
public String title = "";
private float positivePer;
public String negativeColor = "#FEB356";
private float neutralPer ;
public String neutralColor = "#51D6C5";
private float negativePer;
public String positiveColor = "#3FA0FF";
private float Allcount;
private float scale;
/*填充区域比例*/
private float fillScale;
3.分段堆积柱状图View(绘制分段柱状图)
public class BarView extends View {
private BarEntity data;
private Paint paint;
private float animTimeCell = 0;
public BarView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BarView(Context context) {
super(context);
init();
}
public BarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
}
public void setData(BarEntity data) {
this.data = data;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (data != null) {
//高度填充
fillHeight = data.getFillScale() * getHeight();
if (fillHeight != 0) {
paint.setColor(Color.TRANSPARENT);
canvas.drawRect(0f, 0f, getWidth(), fillHeight, paint);
canvas.translate(0f, fillHeight);
}
//负面
paint.setColor(Color.parseColor(data.negativeColor));
canvas.drawRect(0f, 0f, getWidth(), data.getNegativePer() * getHeight(), paint);
canvas.translate(0f, data.getNegativePer() * getHeight());
//中性
paint.setColor(Color.parseColor(data.neutralColor));
canvas.drawRect(0f, 0f, getWidth(), data.getNeutralPer() * getHeight(), paint);
canvas.translate(0f, data.getNeutralPer() * getHeight());
//正面
paint.setColor(Color.parseColor(data.positiveColor));
canvas.drawRect(0f, 0f, getWidth(), data.getPositivePer() * getHeight(), paint);
canvas.translate(0f, data.getPositivePer() * getHeight());
}
}
private float fillHeight;
public float getFillHeight() {
return fillHeight;
}
public void startAnim(int animTime) {
final ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0.0F, 1.0F).setDuration(animTime);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
animTimeCell = (Float) anim.getAnimatedValue();
invalidate();
}
});
}
}
4.自定义View容器
public class BarGroup extends LinearLayout {
private List<BarEntity> datas;
public BarGroup(Context context) {
super(context);
init();
}
public BarGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BarGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOrientation(HORIZONTAL);
}
public void setDatas(List<BarEntity> datas) {
if (datas != null) {
this.datas = datas;
}
}
public void setHeight(float maxValue,int height) {
if (datas != null) {
for (int i = 0; i < datas.size(); i++) {
/*通过柱状图的最大值和相对比例计算出每条柱状图的高度*/
float barHeight = datas.get(i).getAllcount()/maxValue*height;
View view = LayoutInflater.from(getContext()).inflate(R.layout.bar_item, null);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(DensityUtil.dip2px(getContext(),30),height);
// view.setLayoutParams(lp);
((BarView) view.findViewById(R.id.barView)).setData(datas.get(i));
(view.findViewById(R.id.barView)).setLayoutParams(lp);
((TextView)view.findViewById(R.id.title)).setText(getFeedString(datas.get(i).getTitle()));
DecimalFormat mFormat=new DecimalFormat("##.#");
((TextView)view.findViewById(R.id.percent)).setText(mFormat.format(datas.get(i).getAllcount()));
addView(view);
}
}
}
/*字符串換行*/
private String getFeedString(String text){
StringBuilder sb = new StringBuilder(text);
sb.insert(2,"\n");
return sb.toString();
}
}
5.bar_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingRight="50dp">
<com.barchart.mpchartdemo.view.BarView
android:id="@+id/barView"
android:layout_width="30dp"
android:layout_height="220dp" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="百度\n贴吧" />
<TextView
android:id="@+id/percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="99.9%" />
</LinearLayout>
image.png
6fragmen.xml(UI图)
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="自定义分段柱状图:" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:orientation="horizontal"
android:padding="5dp">
<TextView
style="@style/barchartBar"
android:drawableLeft="@mipmap/iv_sentiment_left"
android:text="正面" />
<TextView
style="@style/barchartBar"
android:drawableLeft="@mipmap/iv_sentiment_middle"
android:text="中性" />
<TextView
style="@style/barchartBar"
android:drawableLeft="@mipmap/iv_sentiment_right"
android:text="负面" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
<TextView
android:id="@+id/tv_num5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="500000" />
<View
android:id="@+id/left_base_line"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
<TextView
android:id="@+id/tv_num4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="400000" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
<TextView
android:id="@+id/tv_num3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="300000" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
<TextView
android:id="@+id/tv_num2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="200000" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
<TextView
android:id="@+id/tv_num1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="100000" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
</LinearLayout>
<View
android:id="@+id/base_line"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:layout_marginTop="30dp"
android:background="#E6E6E6" />
</LinearLayout>
<HorizontalScrollView
android:id="@+id/bar_scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.barchart.mpchartdemo.view.BarGroup
android:id="@+id/bar_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</HorizontalScrollView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/bar_scroll"
android:layout_marginLeft="20dp"
android:layout_marginTop="-20dp"
android:text="总数" />
</RelativeLayout>
image.png
7.遍历最大值,计算各横线处显示值
private void setYAxis(List<SourceEntity.Source> list) {
sourceMax = list.get(0).getAllCount();
for (int i = 0; i < list.size() - 1; i++) {
if (list.get(i).getAllCount() > sourceMax) {
sourceMax = list.get(i).getAllCount();
}
}
((TextView) itemView.findViewById(R.id.tv_num1)).setText((int) sourceMax / 5 + "");
((TextView) itemView.findViewById(R.id.tv_num2)).setText((int) sourceMax * 2 / 5 + "");
((TextView) itemView.findViewById(R.id.tv_num3)).setText((int) sourceMax * 3 / 5 + "");
((TextView) itemView.findViewById(R.id.tv_num4)).setText((int) sourceMax * 4 / 5 + "");
((TextView) itemView.findViewById(R.id.tv_num5)).setText((int) sourceMax + "");
}
8.根据柱状图高度显示MarkView(PopupWindow)
private int initPopHeitht = 0;
private void showPop(final View barItem, final float top) {
if (popupWindow != null)
popupWindow.dismiss();
popupWindow = null;
popupWindow = new PopupWindow(popView,
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, false);
popupWindow.setBackgroundDrawable(new BitmapDrawable());
popupWindow.setOutsideTouchable(true);
popupWindow.showAsDropDown(barItem, barItem.getWidth() / 2, -((int) top + initPopHeitht));
if (initPopHeitht == 0) {
popView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
popView.getViewTreeObserver().removeOnPreDrawListener(this);
initPopHeitht = popView.getHeight();
popupWindow.update(barItem, barItem.getWidth() / 2, -((int) top + initPopHeitht),
popupWindow.getWidth(), popupWindow.getHeight());
return false;
}
});
}
}
9.填充数据,柱状图点击监听,高度测量,等
public void setBarChart(){
barGroup = (BarGroup) itemView.findViewById(R.id.bar_group);
root = (HorizontalScrollView) itemView.findViewById(R.id.bar_scroll);
popView = LayoutInflater.from(getContext()).inflate(
R.layout.pop_bg, null);
final SourceEntity sourceEntity = new SourceEntity();
sourceEntity.parseData();
setYAxis(sourceEntity.getList());
barGroup.removeAllViews();
List<BarEntity> datas = new ArrayList<>();
final int size = sourceEntity.getList().size();
for (int i = 0; i < size; i++) {
BarEntity barEntity = new BarEntity();
SourceEntity.Source entity = sourceEntity.getList().get(i);
String negative = mFormat.format(entity.getBadCount() / sourceMax);
barEntity.setNegativePer(Float.parseFloat(negative));
String neutral = mFormat.format(entity.getOtherCount() / sourceMax);
barEntity.setNeutralPer(Float.parseFloat(neutral));
String positive = mFormat.format(entity.getGoodCount() / sourceMax);
barEntity.setPositivePer(Float.parseFloat(positive));
barEntity.setTitle(entity.getSource());
barEntity.setScale(entity.getScale());
barEntity.setAllcount(entity.getAllCount());
/*计算柱状图透明区域的比例*/
barEntity.setFillScale(1 - entity.getAllCount() / sourceMax);
datas.add(barEntity);
}
barGroup.setDatas(datas);
//计算间距
barGroup.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
barGroup.getViewTreeObserver().removeOnPreDrawListener(this);
int height = itemView.findViewById(R.id.bg).getMeasuredHeight();
final View baseLineView = itemView.findViewById(R.id.left_base_line);
int baseLineTop = baseLineView.getTop();
barGroup.setHeight(sourceMax, height - baseLineTop - baseLineView.getHeight() / 2);
barGroup.postDelayed(new Runnable() {
@Override
public void run() {
BarView barItem = (BarView) barGroup.getChildAt(0).findViewById(R.id.barView);
baseLineHeiht = itemView.findViewById(R.id.base_line).getTop();
lp = (RelativeLayout.LayoutParams) root.getLayoutParams();
left = baseLineView.getLeft();
lp.leftMargin = (int) (left + getContext().getResources().getDisplayMetrics().density * 3);
lp.topMargin = Math.abs(baseLineHeiht - barItem.getHeight());
root.setLayoutParams(lp);
// final int initHeight = barItem.getHeight();
// final ObjectAnimator anim = ObjectAnimator.ofFloat(barItem, "zch", 0.0F, 1.0F).setDuration(1500);
// final LinearLayout.LayoutParams barLP= (LinearLayout.LayoutParams) barItem.getLayoutParams();
// anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
// @Override
// public void onAnimationUpdate(ValueAnimator valueAnimator) {
// float cVal = (Float) anim.getAnimatedValue();
// barLP.height = (int) (initHeight * cVal);
// barItem.setLayoutParams(barLP);
// }
// });
// anim.start();
}
}, 0);
for (int i = 0; i < size; i++) {
final BarView barItem = (BarView) barGroup.getChildAt(i).findViewById(R.id.barView);
final int finalI = i;
barItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final float top = view.getHeight() - barItem.getFillHeight();
SourceEntity.Source ss = sourceEntity.getList().get(finalI);
String showText = "正面:" + (int) ss.getGoodCount() + "条\n"
+ "中性:" + (int) ss.getOtherCount() + "条\n"
+ "负面:" + (int) ss.getBadCount() + "条";
((TextView) popView.findViewById(R.id.txt)).setText(showText);
showPop(barItem, top);
}
});
}
return false;
}
});
}
动画效果不佳,新动画效果研究中,后面更新
四丶往期相关文章推荐
MPAndroidChart常见设置属性(一)——应用层
MPAndroidChart项目实战(一)——实现对比性柱状图
MPAndroidChart项目实战(二)——双平滑曲线(双折线图)和MarkView实现
MPAndroidChart项目实战(三)——饼状图实现和文字重合问题解决
MPAndroidChart项目实战(四)——柱状图实现及X轴文字不显示问题和柱状图上显示文字
MPAndroidChart X轴文字斜着显示
MPAndroidChart项目实战(五)——组合图实现趋势图
MPAndroidChart项目实战(六)——自定义1MPAndroidChart滑动冲突解决(搞不定产品设计师就只能搞自己)
MPAndroidChart项目实战(七)——自定义横向柱状图
MPAndroidChart项目实战(八)——自定义分段堆积柱状图
MPAndroidChart项目实战(九)——自定义带文字分段堆积柱状图
源码下载记得顺便Star哦~
下载链接:https://github.com/JinBoy23520/MPAndroidChartDemoByJin