本文出自:http://blog.csdn.net/dt235201314/article/details/77534468
一丶效果图
二丶需求分析及技术点
1.如效果图显示,当一样产品评论越多柱子越高可以展现热度,同一柱子不同颜色不同长度展示评论好坏对比,
自定义MarkView则显示详细数据,这就是分段堆积柱状图的优势所在。
2.MPAndroidChart实现存在的问题:1品类下面的总数不能随柱状图滑动而滑动;2.间距无法调整
3.技术点分析
组合自定义View的实现参考上文:
http://blog.csdn.net/dt235201314/article/details/77248347
MarkView的实现
PopupWindow的灵活使用
4.图解
三丶核心代码
1.造数据
public 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 List5.bar_item.xmldatas; 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 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(); } }
xml version="1.0" encoding="utf-8"?>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"> android:id="@+id/barView" android:layout_width="30dp" android:layout_height="220dp" /> android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:gravity="center" android:text="百度\n贴吧" /> 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%" />
6fragmen.xml(UI图)
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="自定义分段柱状图:" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:orientation="horizontal"
android:padding="5dp">
style="@style/barchartBar"
android:drawableLeft="@mipmap/iv_sentiment_left"
android:text="正面" />
style="@style/barchartBar"
android:drawableLeft="@mipmap/iv_sentiment_middle"
android:text="中性" />
style="@style/barchartBar"
android:drawableLeft="@mipmap/iv_sentiment_right"
android:text="负面" />
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:id="@+id/bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
android:id="@+id/tv_num5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="500000" />
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" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
android:id="@+id/tv_num4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="400000" />
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
android:id="@+id/tv_num3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="300000" />
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
android:id="@+id/tv_num2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="200000" />
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
android:id="@+id/tv_num1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="100000" />
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:background="@drawable/view_dash_line" />
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" />
android:id="@+id/bar_scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
android:id="@+id/bar_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
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="总数" />
7.遍历最大值,计算各横线处显示值
private void setYAxis(List8.根据柱状图高度显示MarkView(PopupWindow)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 + ""); }
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动画效果不佳,新动画效果研究中,后面更新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项目实战(九)——自定义带文字分段堆积柱状图
五丶跪求关注下载源码,200粉小目标
欢迎关注我的博客及微信公众号,后面会给大家带来更多相关MPAndroidChart无法解决的仿MPAndroidChart图标自定义控件
源码下载记得顺便Star哦~
下载链接:https://github.com/JinBoy23520/MPAndroidChartDemoByJin