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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352