MPAndroidChart專案實戰(八)——自定義分段堆積柱狀圖
本文出自: ofollow,noindex">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; } }); }
動畫效果不佳,新動畫效果研究中,後面更新