MPAndroidChart项目实战(八)——自定义分段堆积柱状图

时间:2024-04-08 14:35:36

本文出自:http://blog.csdn.net/dt235201314/article/details/77534468

一丶效果图

MPAndroidChart项目实战(八)——自定义分段堆积柱状图


二丶需求分析及技术点

1.如效果图显示,当一样产品评论越多柱子越高可以展现热度,同一柱子不同颜色不同长度展示评论好坏对比,

自定义MarkView则显示详细数据,这就是分段堆积柱状图的优势所在。

2.MPAndroidChart实现存在的问题:1品类下面的总数不能随柱状图滑动而滑动;2.间距无法调整

3.技术点分析

组合自定义View的实现参考上文:

http://blog.csdn.net/dt235201314/article/details/77248347

MarkView的实现

PopupWindow的灵活使用

4.图解

MPAndroidChart项目实战(八)——自定义分段堆积柱状图


三丶核心代码

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>
MPAndroidChart项目实战(八)——自定义分段堆积柱状图

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>
MPAndroidChart项目实战(八)——自定义分段堆积柱状图

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项目实战(九)——自定义带文字分段堆积柱状图 
五丶跪求关注下载源码,200粉小目标
欢迎关注我的博客及微信公众号,后面会给大家带来更多相关MPAndroidChart无法解决的仿MPAndroidChart图标自定义控件
源码下载记得顺便Star哦~
下载链接:https://github.com/JinBoy23520/MPAndroidChartDemoByJin