Android自定义左右滑动柱状图

参考:https://www.jianshu.com/p/1595ce6aa3a7

技术点记录

/**
* 是否可以滑动
*
* @param direction 方位,正数:向左滑动;负数:从左向右滑动;
* @return 是否可以
*/
@Override
public boolean canScrollHorizontally(int direction) { }

getMeasuredWidth()获取的是view原始的大小,是view在XML文件中配置或者是代码中设置的大小,即可以看到的大小。
getWidth()获取的是这个view最终显示的大小,即view实际大小。

getScrollX返回的是当前视图左上角坐标与视图初始位置x轴方向上的距离,为正数。

在这里插入图片描述

public class HistogramItemBean {

    private float value;
    private String suffix;

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }

    public float getValue() {
        return value;
    }

    public void setValue(float value) {
        this.value = value;
    }
}

public class HistogramListBean {

    private String groupName;
    private List<HistogramItemBean> list;

    public List<HistogramItemBean> getList() {
        return list;
    }

    public void setList(List<HistogramItemBean> list) {
        this.list = list;
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }
}
/**
 * 柱状图
 */
public class HistogramView extends View {

    private int width;
    private int height;

    //直方图视图总宽度
    private int histogramContentWidth;
    //坐标轴线宽度
    private int coordinateAxisWidth;

    //组名字体大小
    private int groupNameTextSize;
    //各组之间间距
    private int groupInterval;

    //组内子直方图间距
    private int histogramInterval;
    //直方图值文字大小
    private int histogramValueTextSize;
    //图标数值小数点位数
    private int histogramValueDecimalCount;
    //直方图宽度
    private int histogramWidth;
    //直方图最大高度
    private int maxHistogramHeight;
    //图标距离顶部距离
    private int chartPaddingTop;
    private int histogramPaddingStart;
    private int histogramPaddingEnd;

    //各组名称到X轴的距离
    private int distanceFromGroupNameToAxis;
    //直方图上方数值到直方图的距离
    private int distanceFromValueToHistogram;

    //轴线画笔
    private Paint coordinateAxisPaint;
    //组名画笔
    private Paint groupNamePaint;
    //直方图画笔
    private Paint histogramPaint;
    //直方图数值画笔
    private Paint histogramValuePaint;
    // 轴线颜色
    int coordinateAxisColor;
    // 组名字体颜色
    int groupNameTextColor;
    // 直方图数值文本颜色
    int histogramValueTextColor;

    //字体指标
    private Paint.FontMetrics groupNameFontMetrics;
    private Paint.FontMetrics histogramValueFontMetrics;

    //直方图绘制区域
    private Rect histogramPaintRect;
    //每组直方图的shade和color,即每组有几个直方图
    private SparseArray<int[]> histogramShaderColorArray;

    //滚动轴
    private Scroller scroller;
    //最小移动速度
    private int minimumVelocity;
    //最大移动速度
    private int maximumVelocity;
    //滑动速度跟踪器
    private VelocityTracker velocityTracker;
    //上次X轴位置
    private float lastX;

    //图标数据列表
    private List<HistogramListBean> dataList;
    private SparseArray<Float> childMaxValueArray;

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

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

    public HistogramView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet attributeSet) {
        //硬件加速
        setLayerType(View.LAYER_TYPE_HARDWARE, null);
        @SuppressLint("Recycle")
        TypedArray typedArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.HistogramView);
        //轴线宽度和颜色
        coordinateAxisWidth = typedArray.getDimensionPixelSize(R.styleable.HistogramView_coordinateAxisWidth, DisplayUtil.dp2px(2));
        coordinateAxisColor = typedArray.getColor(R.styleable.HistogramView_coordinateAxisColor, Color.parseColor("#434343"));
        //组名大小和颜色、组名距离轴线距离、组间距、组内间距
        groupNameTextSize = typedArray.getDimensionPixelSize(R.styleable.HistogramView_groupNameTextSize, DisplayUtil.dp2px(15));
        groupNameTextColor = typedArray.getColor(R.styleable.HistogramView_groupNameTextColor, Color.parseColor("#CC202332"));
        distanceFromGroupNameToAxis = typedArray.getDimensionPixelSize(R.styleable.HistogramView_distanceFormGroupNameToAxis, DisplayUtil.dp2px(15));
        groupInterval = typedArray.getDimensionPixelSize(R.styleable.HistogramView_groupInterval, DisplayUtil.dp2px(30));
        histogramInterval = typedArray.getDimensionPixelSize(R.styleable.HistogramView_histogramInterval, DisplayUtil.dp2px(10));
        //直方图数值大小和颜色、小数点位数、数值距离直方图距离
        histogramValueTextSize = typedArray.getDimensionPixelSize(R.styleable.HistogramView_histogramValueTextSize, DisplayUtil.dp2px(12));
        histogramValueTextColor = typedArray.getColor(R.styleable.HistogramView_histogramValueTextColor, Color.parseColor("#CC202332"));
        histogramValueDecimalCount = typedArray.getInt(R.styleable.HistogramView_histogramValueDecimalCount, 0);
        distanceFromValueToHistogram = typedArray.getDimensionPixelSize(R.styleable.HistogramView_distanceFromValueToHistogram, DisplayUtil.dp2px(10));
        //直方图宽度、距离顶部、距离开始、距离结束
        histogramWidth = typedArray.getDimensionPixelSize(R.styleable.HistogramView_histogramHistogramWidth, DisplayUtil.dp2px(20));
        chartPaddingTop = typedArray.getDimensionPixelSize(R.styleable.HistogramView_chartPaddingTop, DisplayUtil.dp2px(10));
        histogramPaddingStart = typedArray.getDimensionPixelSize(R.styleable.HistogramView_histogramPaddingStart, DisplayUtil.dp2px(15));
        histogramPaddingEnd = typedArray.getDimensionPixelSize(R.styleable.HistogramView_histogramPaddingEnd, DisplayUtil.dp2px(15));
        typedArray.recycle();

        coordinateAxisPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        coordinateAxisPaint.setStyle(Paint.Style.FILL);
        coordinateAxisPaint.setStrokeWidth(coordinateAxisWidth);
        coordinateAxisPaint.setColor(coordinateAxisColor);

        groupNamePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        groupNamePaint.setTextSize(groupNameTextSize);
        groupNamePaint.setColor(groupNameTextColor);
        groupNameFontMetrics = groupNamePaint.getFontMetrics();

        histogramValuePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        histogramValuePaint.setTextSize(histogramValueTextSize);
        histogramValuePaint.setColor(histogramValueTextColor);
        histogramValueFontMetrics = histogramValuePaint.getFontMetrics();

        histogramPaintRect = new Rect();
        histogramPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scroller = new Scroller(getContext(), new LinearInterpolator());
        ViewConfiguration configuration = ViewConfiguration.get(getContext());
        minimumVelocity = configuration.getScaledMinimumFlingVelocity();
        maximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        maxHistogramHeight = height - groupNameTextSize - distanceFromGroupNameToAxis - coordinateAxisWidth - distanceFromValueToHistogram - histogramValueTextSize - chartPaddingTop;
    }

    /**
     * 是否可以滑动
     *
     * @param direction 方位,正数:向左滑动;负数:向右滑动;
     * @return 是否可以
     */
    @Override
    public boolean canScrollHorizontally(int direction) {
        if (direction > 0) {
            return (histogramContentWidth - getScrollX() - width + histogramPaddingStart + histogramPaddingEnd) > 0;
        } else {
            return getScrollX() > 0;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        initVelocityTracker();
        velocityTracker.addMovement(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //终止动画
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                lastX = event.getX();
                return true;
            case MotionEvent.ACTION_MOVE:
                //X轴增量
                int deltaX = (int) (event.getX() - lastX);
                lastX = event.getX();
                if (deltaX > 0 && canScrollHorizontally(-1)) {
                    scrollBy(-Math.min(getMaxCanScrollX(-1), deltaX), 0);
                } else if (deltaX < 0 && canScrollHorizontally(1)) {
                    scrollBy(Math.min(getMaxCanScrollX(1), -deltaX), 0);
                }
                break;
            case MotionEvent.ACTION_UP:
                velocityTracker.computeCurrentVelocity(1000, maximumVelocity);
                int velocityX = (int) velocityTracker.getXVelocity();
                fling(velocityX);
                recycleVelocityTracker();
                break;
            case MotionEvent.ACTION_CANCEL:
                recycleVelocityTracker();
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 初始化滑动速度跟踪器
     */
    private void initVelocityTracker() {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
        }
    }

    /**
     * 回收滑动速度跟踪器
     */
    private void recycleVelocityTracker() {
        if (velocityTracker != null) {
            velocityTracker.recycle();
            velocityTracker = null;
        }
    }

    /**
     * 惯性滑动
     *
     * @param velocityX 横轴滑动速度
     */
    private void fling(int velocityX) {
        if (Math.abs(velocityX) > minimumVelocity) {
            if (Math.abs(velocityX) > maximumVelocity) {
                velocityX = maximumVelocity * velocityX / Math.abs(velocityX);
            }
            scroller.fling(getScrollX(), getScrollY(), -velocityX, 0, 0, histogramContentWidth + histogramPaddingStart - width, 0, 0);
        }
    }

    /**
     * 最大可滑动距离
     *
     * @param direction 滑动方向,正数:由又向左滑动;
     * @return 可滑动距离
     */
    private int getMaxCanScrollX(int direction) {
        if (direction > 0) {
            int size = histogramContentWidth - getScrollX() - width + histogramPaddingStart + histogramPaddingEnd;
            return size > 0 ? size : 0;
        } else if (direction < 0) {
            return getScrollX();
        } else {
            return 0;
        }
    }

    /**
     * 计算拖动位移量
     */
    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), 0);
        }
    }

    /**
     * 设置直方图数据
     *
     * @param dataList 直方图数据
     */
    public void setDataList(List<HistogramListBean> dataList) {
        this.dataList = dataList;
        if (childMaxValueArray == null) {
            childMaxValueArray = new SparseArray<>();
        } else {
            childMaxValueArray.clear();
        }
        histogramContentWidth = 0;
        for (HistogramListBean listBean : dataList) {
            List<HistogramItemBean> itemBeans = listBean.getList();
            if (itemBeans != null && itemBeans.size() > 0) {
                for (int i = 0; i < itemBeans.size(); i++) {
                    histogramContentWidth += (histogramWidth + histogramInterval);
                    HistogramItemBean itemBean = itemBeans.get(i);
                    Float childMaxValue = childMaxValueArray.get(i);
                    if (childMaxValue == null || childMaxValue < itemBean.getValue()) {
                        childMaxValueArray.put(i, itemBean.getValue());
                    }
                }
                histogramContentWidth += (groupInterval - histogramInterval);
            }
        }
        histogramContentWidth += (-groupInterval);
        postInvalidate();
    }

    /**
     * 设置直方图颜色
     *
     * @param colors 颜色
     */
    public void setHistogramColor(int[]... colors) {
        if (colors != null && colors.length > 0) {
            if (histogramShaderColorArray == null) {
                histogramShaderColorArray = new SparseArray<>();
            } else {
                histogramShaderColorArray.clear();
            }
            for (int i = 0; i < colors.length; i++) {
                histogramShaderColorArray.put(i, colors[i]);
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (width == 0 || height == 0) {
            return;
        }
        int scrollX = getScrollX();
        int axisBottom = height - groupNameTextSize - distanceFromGroupNameToAxis - coordinateAxisWidth / 2;
        canvas.drawLine(coordinateAxisWidth / 2 + scrollX, 0, coordinateAxisWidth / 2 + scrollX, axisBottom, coordinateAxisPaint);
        canvas.drawLine(scrollX, axisBottom, width + scrollX, axisBottom, coordinateAxisPaint);
        if (dataList != null && dataList.size() > 0) {
            int xAxisOffset = histogramPaddingStart;
            for (HistogramListBean listBean : dataList) {
                List<HistogramItemBean> itemBeanList = listBean.getList();
                if (itemBeanList != null && itemBeanList.size() > 0) {
                    int groupWidth = 0;
                    for (int i = 0; i < itemBeanList.size(); i++) {
                        HistogramItemBean itemBean = itemBeanList.get(i);
                        histogramPaintRect.left = xAxisOffset;
                        histogramPaintRect.right = histogramPaintRect.left + histogramWidth;
                        int childHistogramHeight;
                        if (itemBean.getValue() <= 0 || childMaxValueArray.get(i) <= 0) {
                            childHistogramHeight = 0;
                        } else {
                            childHistogramHeight = (int) (itemBean.getValue() / childMaxValueArray.get(i) * maxHistogramHeight);
                        }
                        histogramPaintRect.top = height - childHistogramHeight - coordinateAxisWidth - distanceFromGroupNameToAxis - groupNameTextSize;
                        histogramPaintRect.bottom = histogramPaintRect.top + childHistogramHeight;
                        int[] histogramShaderColor = histogramShaderColorArray.get(i);
                        LinearGradient gradient = null;
                        if (histogramShaderColor != null && histogramShaderColor.length > 0) {
                            gradient = getHistogramShader(histogramPaintRect.left, chartPaddingTop + distanceFromValueToHistogram + histogramValueTextSize,
                                    histogramPaintRect.right, histogramPaintRect.bottom, histogramShaderColor);
                        }
                        histogramPaint.setShader(gradient);
                        canvas.drawRect(histogramPaintRect, histogramPaint);
                        //直方图数值
                        String childHistogramValue = StringUtil.NumericScaleByFloor(String.valueOf(itemBean.getValue()), histogramValueDecimalCount);
                        float valueX = xAxisOffset + (histogramWidth - histogramValuePaint.measureText(childHistogramValue)) / 2;
                        float valueY = histogramPaintRect.top - distanceFromGroupNameToAxis + (histogramValueFontMetrics.bottom) / 2;
                        canvas.drawText(childHistogramValue, valueX, valueY, histogramValuePaint);

                        int delta = (i < itemBeanList.size() - 1) ? (histogramWidth + histogramInterval) : histogramWidth;
                        groupWidth += delta;
                        xAxisOffset += (((itemBeanList.size() - 1) == i) ? (delta + groupWidth) : delta);
                    }
                    //组名
                    String groupName = listBean.getGroupName();
                    float groupNameWidth = groupNamePaint.measureText(groupName);
                    float groupNameX = xAxisOffset - groupWidth - groupInterval + (groupWidth - groupNameWidth) / 2;
                    float groupNameY = height - groupNameFontMetrics.bottom / 2;
                    canvas.drawText(groupName, groupNameX, groupNameY, groupNamePaint);
                }
            }
        }
    }

    /**
     * 线性渐变
     *
     * @param x0     起点x轴
     * @param y0     起点y轴
     * @param x1     终点x轴
     * @param y1     终点y轴
     * @param colors 渐变色数组
     * @return LinearGradient
     */
    private LinearGradient getHistogramShader(float x0, float y0, float x1, float y1, int[] colors) {
        return new LinearGradient(x0, y0, x1, y1, colors, null, Shader.TileMode.CLAMP);
    }

}

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context="com.thinta.modules.basicDataRead.AddressAndNo.ui.Fragment2">

    <com.thinta.views.HistogramView
        android:id="@+id/histogram"
        android:layout_marginTop="50dp"
        android:layout_width="match_parent"
        android:layout_height="280dp"
        android:layout_marginEnd="20dp"
        android:layout_marginStart="20dp"
        app:chartPaddingTop="15dp"
        app:coordinateAxisColor="#FF727884"
        app:coordinateAxisWidth="1dp"
        app:distanceFormGroupNameToAxis="10dp"
        app:distanceFromValueToHistogram="10dp"
        app:groupInterval="30dp"
        app:groupNameTextColor="#FF727884"
        app:groupNameTextSize="13sp"
        app:histogramHistogramWidth="20dp"
        app:histogramInterval="15dp"
        app:histogramPaddingEnd="15dp"
        app:histogramPaddingStart="15dp"
        app:histogramValueDecimalCount="ZERO"
        app:histogramValueTextColor="#FF727884"
        app:histogramValueTextSize="10sp" />

</FrameLayout>


Random random = new Random();
        int groupSize = random.nextInt(5) + 10;
        List<HistogramListBean> groupDataList = new ArrayList<>();
        // 生成测试数据 
        for (int i = 0; i < groupSize; i++) {
            List<HistogramItemBean> childDataList = new ArrayList<>();
            HistogramListBean groupData = new HistogramListBean();
            groupData.setGroupName("第" + (i + 1) + "组");
            HistogramItemBean childData1 = new HistogramItemBean();
            childData1.setSuffix("分");
            childData1.setValue(random.nextInt(50) + 51);
            childDataList.add(childData1);

            HistogramItemBean childData2 = new HistogramItemBean();
            childData2.setSuffix("%");
            childData2.setValue(random.nextInt(50) + 51);
            childDataList.add(childData2);
            groupData.setList(childDataList);
            groupDataList.add(groupData);
        }
        histogramView.setDataList(groupDataList);
        int[] color1 = new int[]{getResources().getColor(R.color.gold), getResources().getColor(R.color.colorPrimary)};

        int[] color2 = new int[]{getResources().getColor(R.color.pink), getResources().getColor(R.color.yellow)};
        // 设置直方图颜色
        histogramView.setHistogramColor(color1, color2);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值