android 自定义带增长动画和点击弹窗提示效果的柱状图

     项目中最近用到各种图表,本来打算用第三方的,例如MPAndroid,这是一个十分强大的图表库,应用起来十分方便,但是最终发现和设计不太一样,没办法,只能自己写了。今天将写好的柱状图的demo贴在这,该柱状图可根据数据的功能有一下几点:

     1. 根据数据的多少,动态的绘制柱状图柱子的条数;

     2. 柱状图每条柱子的绘制都有动态的动画效果;

     3. 每条柱子有点击事件,点击时弹出提示框,显示相关信息,规定时间后,弹窗自动消失。

     好了,先上演示图:


     下边贴出相关代码:

     自定义柱状图类:

package com.example.histogram;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.example.histogram.UI.UI;

import java.text.NumberFormat;

/**
 * Created by ZHANGZDon 2016/6/16 0016.
 * 柱状图
 */
public class HistoGram extends View implements Runnable {
    private Handler handler = new Handler(); // 用于延时更新,实现动画
    private float animHeight; // 进度条动画高度
    private Paint axisLinePaint; // 坐标轴画笔
    private Paint hLinePaint; // 内部水平虚线画笔
    private Paint textPaint; // 绘制文本的画笔
    private Paint recPaint; // 绘制柱状图阴影背景的画笔
    private Paint dataPaint; // 绘制柱状图的画笔
    private Paint textPaint2; // 绘制白色文本的画笔
    private Paint textPaint3; // 绘制坐标的画笔
    private Paint textPaint4;  // 绘制x轴上的白色竖线的画笔

    private String[] xTitleString; // x轴刻度
    private String[] yTitleString; // y轴刻度
    private String[] data; // 接口返回的indicatordata,用于计算柱子高度

    NumberFormat numberFormat; //用于格式化数字

    private float currentHeight; // 当前柱状图应有的高度,应由计算得来
    private int num = -1; // 画多少条柱子,因为存在刚开机数据不足24条的情况
    private float mRelativePxInHeight;
    private float mRelativePxInWidth;
    private OnChartClickListener listener;
    private int mDist;

    public void setNum(int num) {
        this.num = num;
        invalidate();
    }

    public void setData(String[] data) {
        this.data = data;
        invalidate();
    }

    public void setxTitleString(String[] title) {
        this.xTitleString = title;
        invalidate();


    }

    public HistoGram(Context context) {

        this(context, null);
    }

    public HistoGram(Context context, AttributeSet attrs) {
        this(context, attrs, 0);

    }

    public void setTitle(String[] title) {
        this.xTitleString = title;
    }

    public HistoGram(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    /**
     * 进行相关初始化操作
     * @param context
     * @param attrs
     */
    private void init(Context context, AttributeSet attrs) {
        axisLinePaint = new Paint();
        hLinePaint = new Paint();
        textPaint = new Paint();
        recPaint = new Paint();
        dataPaint = new Paint();
        textPaint2 = new Paint();
        textPaint3 = new Paint();
        textPaint4 = new Paint();

        numberFormat = NumberFormat.getNumberInstance();
        numberFormat.setMinimumFractionDigits(3);  //设置打印时保留三位小数
        axisLinePaint.setColor(Color.parseColor("#dbdde4"));  //设置坐标轴的颜色为白色
        hLinePaint.setARGB(51, 255, 255, 255);
        textPaint.setColor(Color.parseColor("#8593a1"));
//        textPaint.setTextSize(29);
        textPaint.setTextSize(UI.dip2px(getContext(), 12));
        recPaint.setColor(Color.parseColor("#f2f5fc"));
        dataPaint.setColor(Color.CYAN);
        textPaint2.setColor(Color.WHITE);
        textPaint2.setTextSize(UI.dip2px(getContext(), 12));
        textPaint3.setColor(Color.parseColor("#000000"));
        textPaint3.setTextSize(UI.dip2px(getContext(), 9));
        textPaint4.setColor(Color.parseColor("#8593a1"));
        textPaint4.setTextSize(UI.dip2px(getContext(), 6));

        axisLinePaint.setAntiAlias(true);
        hLinePaint.setAntiAlias(true);
        textPaint.setAntiAlias(true);
        recPaint.setAntiAlias(true);
        dataPaint.setAntiAlias(true);
        textPaint2.setAntiAlias(true);
        textPaint3.setAntiAlias(true);
        textPaint4.setAntiAlias(true);
    }


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

        if(data == null || xTitleString == null || num < 0 ) {
            return;
        }

        //绘制y轴刻度
        Paint.FontMetrics metrics = textPaint3.getFontMetrics();
        int decent = (int) metrics.descent;
        float width = getWidth();
        float height = getHeight();

        //根据原型图得出,图中每px高度在实际中的相对尺寸
        mRelativePxInHeight = height / 470;
        //根据原型图得出,图中每px宽度在实际中的相对尺寸
        mRelativePxInWidth = width / 690;

        textPaint3.setTextAlign(Paint.Align.RIGHT);


        //绘制纵坐标
        yTitleString = new String[6];
        yTitleString[5] = "0";
        yTitleString[4] = "20";
        yTitleString[3] = "40";
        yTitleString[2] = "60";
        yTitleString[1] = "80";
        yTitleString[0] = "100";

        for (int i = 0; i < yTitleString.length; i++) {
            canvas.drawText(yTitleString[i], 88 * mRelativePxInWidth, (72 + i * 56) * mRelativePxInHeight + decent, textPaint3);
        }


        //绘制x轴刻度
        textPaint3.setTextAlign(Paint.Align.CENTER);
        textPaint4.setTextAlign(Paint.Align.CENTER);
        TextPaint textPaint = new TextPaint();
        textPaint.setColor(Color.parseColor("#000000"));
        textPaint.setTextSize(UI.dip2px(getContext(), 9));
        //计算柱子之间的间隔
        //最左侧位置100 * mRelativePxInWidth,最右侧位置630 ePxInWidth,
        float totalWidth = 630 - 100;
        // 柱子与之子之间的间隔
        mDist = (int) (totalWidth / (xTitleString.length + 1));
        for (int i = 0; i < xTitleString.length; i++) {
            //绘制白色竖线
            canvas.drawLine((100 + (i+1) * mDist) * mRelativePxInWidth, 348 * mRelativePxInHeight, (100 + (i+1) * mDist) * mRelativePxInWidth, 352 * mRelativePxInHeight, axisLinePaint);
            //绘制x轴文字
            canvas.drawText(xTitleString[i], (100 + (i+1) * mDist) * mRelativePxInWidth, 370 * mRelativePxInHeight, textPaint3);
        }


//        绘制矩形阴影
        for (int i = 0; i < num; i++) {
            RectF rectF = new RectF();
//            rectF.left = 111 * relativePxInWidth + i * 22 * relativePxInWidth;
//            rectF.right = 121 * relativePxInWidth + i * 22 * relativePxInWidth;
            rectF.left = 95 * mRelativePxInWidth + (i+1) * mDist * mRelativePxInWidth;
            rectF.right = 105 * mRelativePxInWidth +(i+1) * mDist * mRelativePxInWidth;
            rectF.top = 70 * mRelativePxInHeight;
            rectF.bottom = 338 * mRelativePxInHeight;
            canvas.drawRoundRect(rectF, 10, 10, recPaint);
        }

        //        绘制x轴坐标线
        for (int i = 0; i < 6; i++) {
            canvas.drawLine(100 * mRelativePxInWidth, (66 + i * 56) * mRelativePxInHeight + decent, 630 * mRelativePxInWidth, (66 + i * 56) * mRelativePxInHeight + decent, axisLinePaint);
        }


//        延时绘制,实现动画效果。数字越大,延时越久,动画效果就会越慢
        handler.postDelayed(this, 1);
        for (int i = 0; i < num; i++) {
            RectF dataRectF = new RectF();
            dataRectF.left = 95 * mRelativePxInWidth + (i + 1) * mDist * mRelativePxInWidth;
            dataRectF.right = 105 * mRelativePxInWidth + (i + 1) * mDist * mRelativePxInWidth;

            dataPaint.setColor(Color.parseColor("#3ac2d9"));
            //获取柱子高度
            currentHeight = Float.parseFloat(data[num - 1 - i]);
            if (currentHeight == 0) {
                dataRectF.top = 346 * mRelativePxInHeight;
            } else if (currentHeight == 100) {
                dataRectF.top = 70 * mRelativePxInHeight;
            } else {
                if (animHeight >= currentHeight) {
                    dataRectF.top = 346 * mRelativePxInHeight - currentHeight / 100 * 276 * mRelativePxInHeight;
                } else {
                    dataRectF.top = 346 * mRelativePxInHeight - 276 * mRelativePxInHeight * (animHeight / 100);
                }
            }
            dataRectF.bottom = 346 * mRelativePxInHeight;
//                限制最高高度
            if (dataRectF.top < 70 * mRelativePxInHeight) {
                dataRectF.top = 70 * mRelativePxInHeight;
            }
            canvas.drawRoundRect(dataRectF, 10, 10, dataPaint);

        }
    }

    //实现柱子增长的动画效果
    @Override
    public void run() {
        animHeight += 1;

        if (animHeight >= 276 * mRelativePxInHeight) {
            return;
        } else {
            invalidate();
        }
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                //获取点击坐标
                float x = event.getX();
                float y = event.getY();
                //判断点击点的位置
                float leftx = 0;
                float rightx = 0;

                for (int i = 0; i < num; i++) {
                    leftx = 95 * mRelativePxInWidth + (i+ 1) * mDist * mRelativePxInWidth - mDist/2 * mRelativePxInWidth;
                    rightx = 105 * mRelativePxInWidth + (i+ 1) * mDist * mRelativePxInWidth + mDist/2 * mRelativePxInWidth;
                    if (x < leftx) {
                        continue;
                    }
                    if (leftx <= x && x <= rightx) {
                        //获取点击的柱子区域的y值
                        float top = 346 * mRelativePxInHeight - Float.parseFloat(data[num - 1 - i])/ 100 * 276 * mRelativePxInHeight;
                        float bottom = 346 * mRelativePxInHeight;


                        if (y >= top && y <= bottom) {
                            //判断是否设置监听
                            //将点击的第几条柱子,点击柱子顶部的坐值,用于弹出dialog提示数据,还要返回百分比currentHeidht = Float.parseFloat(data[num - 1 - i])
                            if(listener != null) {
                                Log.e("ss","x" + x +";y:" + y);
                                listener.onClick(i + 1, leftx + mDist/2,top,Float.parseFloat(data[num - 1 - i]));
                            }
                            break;

                        }
                    }

                }
                break;
            }
            case MotionEvent.ACTION_MOVE:
                Log.e("touch", "ACTION_MOVE");
                break;

            case MotionEvent.ACTION_UP:
                Log.e("touch", "ACTION_UP");
                break;
        }

        return true;
    }

    /**
     * 柱子点击时的监听接口
     */
    public interface OnChartClickListener {
        void onClick(int num, float x, float y, float value);

    }

    /**
     * 设置柱子点击监听的方法
     * @param listener
     */
    public void setOnChartClickListener(OnChartClickListener listener)  {
        this.listener = listener;
    }


}

  在xml文件中的应用:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   android:orientation="vertical"
    tools:context="com.example.histogram.MainActivity">
    
    <TextView
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center"
        android:text="繁忙度指示图(%)"
        android:textSize="15sp"
        android:textColor="#000000"
        />
    

    <com.example.histogram.HistoGram
        android:id="@+id/staticview"
        android:layout_width="400dp"
        android:layout_height="500dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="14dp"
        android:layout_marginTop="5dp"/>
</LinearLayout>

   在activity中的实现:

package com.example.histogram;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.PopupWindow;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private PopupWindow mPopupWindow;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final HistoGram histoGram = (HistoGram) findViewById(R.id.staticview);
        String[] data ={"100","20","40","20","80","20","60","30","5","20","60","30","5","5","20","60","30","5"};
        final String[] title = {"1","2","3","4","5","6","7","8","9","6","7","8","9","9","6","7","8","9"};

        histoGram.setNum(title.length);
        histoGram.setData(data);
        histoGram.setxTitleString(title);
        histoGram.setOnChartClickListener(new HistoGram.OnChartClickListener() {
            @Override
            public void onClick(int num, float x, float y, float value) {

                //显示提示窗
                View inflate = View.inflate(MainActivity.this, R.layout.popupwindow, null);
                TextView  textView = (TextView) inflate.findViewById(R.id.main_tv);
                textView.setText(value + "%\n" + title[num - 1]);
                if(mPopupWindow != null) {
                    mPopupWindow.dismiss();
                }
                mPopupWindow = new PopupWindow(inflate,140, 60, true);
                mPopupWindow.setTouchable(true);
                Log.e("ss","num" + num +";x" + x+";y"+ y + ";value" + value
                        +";(int)((- histoGram.getHeight()) + y - 65)"
                        +(int)((- histoGram.getHeight()) + y - 65)
                + "histoGram.getHeight()" + histoGram.getHeight());
                // 设置好参数之后再show
//                Toast.makeText(MainActivity.this, "num" + num +";x" + x+";y"+ y + ";value" + value
//                        +";popupWindow.getWidth()"+ mPopupWindow.getWidth()+";"+ mPopupWindow.getHeight(), Toast.LENGTH_SHORT).show();
                mPopupWindow.showAsDropDown(histoGram,(int)(x - 65),(int)((- histoGram.getHeight()) + y - 65) );
                mPopupWindow.setBackgroundDrawable(getResources().getDrawable(R.mipmap.databg_busyness));

                new Handler().postDelayed(new Runnable(){
                    public void run() {

                        mPopupWindow.dismiss();
                    }
                }, 1000);



            }
        });

    }
}

     好了,这就是所有代码,写的比较简单,也存在一些问题,希望大家多多指教吐舌头



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值