android RecyclerView实现柱状图

android RecyclerView实现柱状图

先上效果图:
在这里插入图片描述

思路

  • 可以把每一个柱形图看作RecyclerView的单个item
  • 横向的RecyclerView,而且每个item宽度是固定的
  • item的高度,需要确定整个数据最大高度,然后按照比例来确定

实现

知识补充

不想看的可以直接跳过

setStackFromEnd

先讲关于RecyclerView的几个属性:layoutManager.setStackFromEnd(boolean)layoutManager.setReverseLayout(boolean);

 public void setStackFromEnd(boolean stackFromEnd) {
        assertNotInLayoutOrScroll(null);
        if (mStackFromEnd == stackFromEnd) {
            return;
        }
        mStackFromEnd = stackFromEnd;
        requestLayout();
    }

setStackFromEnd()是设置数据填充是否从底部开始的,mStackFromEnd默认为flase。

setReverseLayout
public void setReverseLayout(boolean reverseLayout) {
        assertNotInLayoutOrScroll(null);
        if (reverseLayout == mReverseLayout) {
            return;
        }
        mReverseLayout = reverseLayout;
        requestLayout();
    }

setReverseLayout用于反转项目遍历和布局顺序。当设置为true时,会将第一项放置在ui的末尾类似,如此类推。mReverseLayout默认为false

组合使用
setStackFromEnd(false) && setReverseLayout(false)

这两种就是默认情况,代码如下

recyclerView = (RecyclerView) findViewById(R.id.rv_bill);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
                this, LinearLayoutManager.HORIZONTAL, false);
        linearLayoutManager.setStackFromEnd(false);
        List<BillBean> billBeanArrayList = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            billBeanArrayList.add(new BillBean(i + ""));
        }
        BillListAdapter listAdapter = new BillListAdapter(this, billBeanArrayList);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(listAdapter);

效果如下:正常的显示
在这里插入图片描述

setStackFromEnd(true) && setReverseLayout(false)
recyclerView = (RecyclerView) findViewById(R.id.rv_bill);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
                this, LinearLayoutManager.HORIZONTAL, false);
        linearLayoutManager.setStackFromEnd(true);
        List<BillBean> billBeanArrayList = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            billBeanArrayList.add(new BillBean(i + ""));
        }
        BillListAdapter listAdapter = new BillListAdapter(this, billBeanArrayList);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(listAdapter);

效果:列表的排序顺序还是正常的,但是数据先从末端排列开始,所以会自动展示在末尾。
在这里插入图片描述

setStackFromEnd(false) && setReverseLayout(true)
 recyclerView = (RecyclerView) findViewById(R.id.rv_bill);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
                this, LinearLayoutManager.HORIZONTAL, true);
        linearLayoutManager.setStackFromEnd(false);
        List<BillBean> billBeanArrayList = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            billBeanArrayList.add(new BillBean(i + ""));
        }
        BillListAdapter listAdapter = new BillListAdapter(this, billBeanArrayList);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(listAdapter);

效果:倒过来排序,显示的是最开始的
在这里插入图片描述

setStackFromEnd(true) && setReverseLayout(true)

        recyclerView = (RecyclerView) findViewById(R.id.rv_bill);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
                this, LinearLayoutManager.HORIZONTAL, true);
        linearLayoutManager.setStackFromEnd(true);
        List<BillBean> billBeanArrayList = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            billBeanArrayList.add(new BillBean(i + ""));
        }
        BillListAdapter listAdapter = new BillListAdapter(this, billBeanArrayList);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(listAdapter);

效果如下:顺序倒排,但是显示栈底的

在这里插入图片描述

实现部分

recyclerView部分

这里账单展示样式肯定是最近月份的要优先展示,所以要反向设置mLayoutManager.setReverseLayout(true);

 recyclerView = (RecyclerView) findViewById(R.id.rv_bill);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
                this, LinearLayoutManager.HORIZONTAL, true);
        BillListAdapter listAdapter = new BillListAdapter(this, billBeanArrayList);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(listAdapter);
item实现部分

在这里插入图片描述
看图可以分为上面4块,可以直接用LinearLayout套住,或者自定义实现上面4块。这里采用自定义:
业务分析:先要考虑红色块的高度问题,金额最大和最低会可能相差很大的,这里最好获取列表最大值作为基准,每个金额去除以最大金额,获得自身高度的百分比。
业务显示数据类:根据要显示的内容,需要定义一个实体类来控制显示的内容。这里模拟为月账单业务,需要月份、最大金额(可自己先算好)、月金额、月份颜色、月金额颜色、柱状块颜色、线颜色(颜色也可以通过自定义属性设置)。

  package com.hujin.wan;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

public class BarItemView extends View {

    private Context mContext;
    private Paint linePaint;
    private int barColir;
    private int lineColor;
    private int moneyColor;
    private int timeColor;
    private Paint barPaint;
    private Paint moneyPaint;
    private Paint timePaint;
    private int width;
    private int heigt;
    private int lineHeigt;
    private int lineWidth;
    private int barHeight;
    private int maxBarHeight;
    private float animValue;
    private int animBarHeight;
    private int pos;

    public BarItemView(Context context) {
        this(context, null);
    }


    public BarItemView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }


    public BarItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;

        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.BarItemView);
        try {
            barColir = typedArray.getColor(R.styleable.BarItemView_barColor, Color.RED);
            lineColor = typedArray.getColor(R.styleable.BarItemView_lineColor, Color.GRAY);
            moneyColor = typedArray.getColor(R.styleable.BarItemView_moneyColor, Color.GRAY);
            timeColor = typedArray.getColor(R.styleable.BarItemView_textColor, Color.GRAY);
        } finally {
            typedArray.recycle();
        }
        init();
    }
    private BillBean billBean;
    private boolean hasAnimation;

    public void setValue(BillBean bean){
        this.billBean = bean;
    }

    public synchronized void updateValue(int position, BillBean bean, boolean hasAnimation) {
        this.pos = position;
        this.billBean = bean;
        this.hasAnimation=true;
        if(hasAnimation) {
            doAnimation();
        }
        postInvalidate();
    }

    ValueAnimator animator;
    private void doAnimation() {
        if (animator != null && animator.isRunning()) {
            animator.cancel();
        }
        animator = ValueAnimator.ofFloat(0, 1);
        animator.setDuration(1000);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }

    private void init() {
        initLinePaint();
        initBarPaint();
        initTimePaint();
        initMoneyPaint();
    }

    private void initMoneyPaint() {
        moneyPaint = new Paint();
        moneyPaint.setAntiAlias(true);
        moneyPaint.setColor(moneyColor);
        moneyPaint.setStyle(Paint.Style.FILL);
        moneyPaint.setStrokeWidth(2);

    }

    private void initTimePaint() {
        timePaint = new Paint();
        timePaint.setAntiAlias(true);
        timePaint.setColor(timeColor);
        timePaint.setStyle(Paint.Style.FILL);
        timePaint.setStrokeWidth(2);

    }

    private void initBarPaint() {
        barPaint = new Paint();
        barPaint.setAntiAlias(true);
        barPaint.setColor(barColir);
        barPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    }

    private void initLinePaint() {
        linePaint = new Paint();
        linePaint.setAntiAlias(true);
        linePaint.setColor(lineColor);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(2);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //宽度固定的,不需要测量了,高度由外表recyleview控制
        heigt = getMeasuredHeight();
        Log.e("距离1", "" + DisplayUtils.dip2px(60));
        width = lineWidth = (int) DisplayUtils.dip2px(60);
        //高度从上到下 ,下面越大
        lineHeigt = heigt - DisplayUtils.dip2px(32);
        //  宽度在外面限制了
        //计算 bar的最大高度 == 总高 - (月份整体高度+金额文字高度+线的高度+margin距离 * 2)
        Paint paint = new Paint();
        Rect rect = new Rect();
        paint.setTextSize(36);
        paint.getTextBounds("测试", 0, 1, rect);
        int textHeight = rect.height();
        Log.e("距离3", "" + textHeight);
        //bar最大高度 等于最大高度依次减去其他
        maxBarHeight = heigt - DisplayUtils.dip2px(32 + textHeight + 6 * 2 + 1);
        setMeasuredDimension(lineWidth, heigt);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //真正的宽高
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if(hasAnimation) {
            animBarHeight = (int) (maxBarHeight * animValue);
        }else {
            animBarHeight = maxBarHeight;
        }
        drawLine(canvas);
        drawTime(canvas);
        drawBar(canvas);
        drawMoney(canvas);

    }

    private int marginForMoney = DisplayUtils.dip2px(6);

    private void drawMoney(Canvas canvas) {
        canvas.save();
        //动态的
        int height = lineHeigt - barHeight;

        Rect rect = new Rect();
        String money = String.valueOf(billBean.countAmt);
        timePaint.setTextSize(36);
        timePaint.getTextBounds(money, 0, money.length(), rect);

        //获得字符串的宽度
        float strNameFix = timePaint.measureText(money) / 2;
        canvas.drawText(money, width / 2 - rect.width() / 2, height - marginForMoney, timePaint);
    }

    private int barWidth = (int) DisplayUtils.dip2px(30);


    private void drawBar(Canvas canvas) {
        canvas.save();
        int barStartWidth = width / 2 - barWidth / 2;
        int barEndWidth = width / 2 + barWidth / 2;
        //倒角
        float arcRect = 15.0f;
        //高度
      //  Log.e("animBarHeight ==",pos+"  "+animBarHeight+""+"  value=="+animValue);
        barHeight = (int) (animBarHeight * billBean.percent);
        //这里要判断

        RectF oval1 = new RectF(barStartWidth,lineHeigt - barHeight + arcRect,barEndWidth,lineHeigt);
        RectF oval2 = new RectF(barStartWidth+arcRect,lineHeigt - barHeight,barEndWidth-arcRect,lineHeigt);
        RectF oval3 = new RectF(barStartWidth,lineHeigt -barHeight ,barStartWidth+2*arcRect,lineHeigt - barHeight+2*arcRect);
        RectF oval4 = new RectF(barEndWidth - 2*arcRect,lineHeigt -barHeight,barEndWidth,lineHeigt - barHeight+2*arcRect);
        canvas.drawRect(oval1, barPaint);
        canvas.drawRect(oval2, barPaint);
        canvas.drawArc(oval3,180,90,true,barPaint);
        canvas.drawArc(oval4,270,90,true,barPaint);
    }

    /**
     * 绘制 月份
     *
     * @param canvas
     */
    private void drawTime(Canvas canvas) {
        canvas.save();
        Rect rect = new Rect();
        String month = billBean.months;
        timePaint.setTextSize(34);
        timePaint.getTextBounds(month, 0, month.length(), rect);
        //获得字符串的宽度
        float strNameFix = timePaint.measureText(month) / 2;
        canvas.drawText(month, width / 2 - rect.width() / 2, getHeight() - DisplayUtils.dip2px(16) + rect.height() / 2, timePaint);

    }

    private void drawLine(Canvas canvas) {
        Path path = new Path();
        path.moveTo(0, lineHeigt);
        path.lineTo(lineWidth, lineHeigt);
        canvas.drawPath(path, linePaint);
    }

}

实体类(颜色再自定义属性里面)

public double countAmt;

    public String months;

    public boolean isAnim;

    public float percent;

    public BillBean(double countAmt, String months, boolean isAnim,double maxCount) {
        this.countAmt = countAmt;
        this.months = months;
        this.isAnim = isAnim;
        this.percent = (float) (countAmt/maxCount);
    }

item布局

<com.hujin.wan.BarItemView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/biv_item"
    app:lineColor="@color/colorAccent"
    android:layout_width="60dp"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary" />

假数据部分

 List<BillBean> billBeanArrayList = new ArrayList<>();
        for (int i = 1; i < 13; i++) {
            billBeanArrayList.add(new BillBean(100*i,i+"月",true,12*100));
        }

最终效果

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值