和之前双折线图一样,今天要讲述的单柱和双柱图如出一辙,理解之后原理是一样的,不过这里的算法可能要复杂一些
这里实现单柱和双柱都是通过画布和画笔绘制出来的,和之前折线图api有些差别。如果您觉得自定义麻烦,这里推荐一个GitHub的图表库,还是挺强大的
https://github.com/lecho/hellocharts-android
效果图如下
![单柱图]
简要介绍
这里有四个条件筛选,所以绘制图的时候随筛选条件变化而变化,纵坐标不变并且固定,横坐标和柱状图如果数据比较多会左右滑动。点击的时候会弹出柱状图的信息,并且柱状图和文字要加有明显加黑。下面横坐标会显示不下,所以要斜体显示,这里的需求都是我们产品这样设计(遇到这样的产品也是头疼,一个项目不下十个自定义控件),所以我觉得只有自定义才是最好的办法。
这里我们还是一步步的来,先说双柱图,单柱原理和双柱一样,不过在后面我还是会贴出单柱图的代码。大布局包括三部分,四个筛选条件是一个布局,下面是一个大控件,包括固定布局合格率复验率、柱状图纵坐标(固定)、弹出框,和柱状图主要部分chartView(纵坐标也可以画进去,但是需求是固定,故没有画入控件)。
charView的画法,里面包括两个主要方法onDraw()和onTouchevent(),功能分别是页面绘制和点击事件监听
- 具体矩形的大小和他们之间的距离可以自己设定,如果要适配屏幕也可以获取屏幕大小来设定
- 先画横坐标的坐标轴,这里加坐标轴有11条背景线,通过控件高度计算距离,和外面纵坐标之间的距离保持一致。下一步也一个难点(这样画斜体字api在网上找了很久才找到),这个画法什么角度的字都能画出来(如弧形,波浪形)
- 画柱状图时,首先先话百分比文字,计算好高度即可。下一步画百分比为0的时候一条黑色横线。最后一步画出填充色的矩形图即可,得计算宽高和双柱图中间的间隙。
- 监听点击事件时,这里通过手指触摸到屏幕的坐标,来计算坐标是在哪个双主图范围内,通过循环来找出双柱图集合下标并存储(重新绘制时会使用),调用invalidate()方法重新绘制,重新调用onDraw()会改变横坐标文字颜色和给矩形加边框,达到界面效果。
- 点击后回调给主控件,返回双柱图高点坐标x,y,主控件(包括固定布局和charView)通过坐标加载出弹框位置。
charView控件
package com.pcjz.dems.widget.chatview;
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.PorterDuff;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import com.pcjz.dems.R;
import com.pcjz.dems.common.utils.StringUtils;
import com.pcjz.dems.entity.reportform.ReportformRate;
import com.pcjz.dems.entity.will.ProjectAcceptanceStatisticsModel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class MyChartView1 extends View {
private int leftColor;//双柱左侧
private int rightColor;//双柱右侧
private int lineColor;//横轴线
private int selectLeftColor;//点击选中左侧
private int selectRightColor;//点击选中右侧
private int lefrColorBottom;//左侧底部
private int rightColorBottom;//右侧底部
private Paint mPaint, mChartPaint, mShadowPaint;//横轴画笔、柱状图画笔、阴影画笔
private int mWidth, mHeight, mStartWidth, mChartWidth, mSize;//屏幕宽度高度、柱状图起始位置、柱状图宽度
private Rect mBound;
private List<Float> list = new ArrayList<>();//柱状图高度占比
private getNumberListener listener;//点击接口
private int selectIndex = -1;//点击选中柱状图索引
private int textSelectIndex = -1;//点击文字选中的索引
private List<Integer> selectIndexRoles = new ArrayList<>();
private List<Integer> textSelectIndexs = new ArrayList<>();
private Paint mStroke;
private Paint mPercent;
private List<ReportformRate> mdatas;
public void setList(List<Float> list) {
this.list = list;
//柱状图的宽度
mSize = dp2px(20);
mStartWidth = dp2px(5) + mSize;
mChartWidth = dp2px(5);
invalidate();
}
public void setMdatas(List<ReportformRate> mdatas) {
this.mdatas = mdatas;
}
public void setListener(getNumberListener listener) {
this.listener = listener;
}
public MyChartView1(Context context) {
this(context, null);
}
public MyChartView1(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyChartView1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取我们自定义的样式属性
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView1, defStyleAttr, 0);
int n = array.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.MyChartView1_leftColor:
// 默认颜色设置为黑色
leftColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView1_selectLeftColor:
// 默认颜色设置为黑色
selectLeftColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView1_rightColor:
rightColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView1_selectRightColor:
selectRightColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView1_xyColor:
lineColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView1_leftColorBottom:
lefrColorBottom = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView1_rightColorBottom:
rightColorBottom = array.getColor(attr, Color.BLACK);
break;
}
}
array.recycle();
initSelects();
init();
}
private void initSelects() {
if (selectIndexRoles.size() != 0) {
selectIndexRoles.clear();
}
if (textSelectIndexs.size() != 0) {
textSelectIndexs.clear();
}
}
//初始化画笔
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(35);
mBound = new Rect();
mChartPaint = new Paint();
mChartPaint.setAntiAlias(true);
mShadowPaint = new Paint();
mShadowPaint.setAntiAlias(true);
mShadowPaint.setColor(Color.WHITE);
mStroke = new Paint();
mStroke.setAntiAlias(true);
mStroke.setStyle(Paint.Style.STROKE);
mStroke.setStrokeWidth(3);
mStroke.setColor(Color.parseColor("#333333"));
mPercent = new Paint();
mPercent.setAntiAlias(true);
}
//测量高宽度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
/* if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = widthSize * 1 / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = heightSize * 1 / 2;
}*/
width = widthSize;
height = heightSize;
setMeasuredDimension(width, height);
}
/*//计算高度宽度
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
initView();
}*/
private void initView(Canvas canvas) {
mWidth = getWidth();
mHeight = getHeight();
mSize = dp2px(20);
mStartWidth = dp2px(5) + mSize;
mChartWidth = dp2px(5);
}
//重写onDraw绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initView(canvas);
mPaint.setColor(lineColor);
//画坐标轴
for (int i = 0; i <= 10; i++) {
mPaint.setTextSize(dp2px(11));
canvas.drawLine(0, (mHeight * 24 / 30) - (mHeight * 18 / 30) * i / 10, mWidth, (mHeight * 24 / 30) - (mHeight * 18 / 30) * i / 10, mPaint);
}
if (mdatas != null) {
List<String> periodName = new ArrayList<>();
for (int i = 0; i < mdatas.size(); i++) {
if (mdatas.get(i).getPeriodName() != null) {
periodName.add(mdatas.get(i).getPeriodName());
} else if (mdatas.get(i).getTeamName() != null) {
periodName.add(mdatas.get(i).getTeamName());
} else if (mdatas.get(i).getCorporateName() != null) {
periodName.add(mdatas.get(i).getCorporateName());
}
}
if (periodName.size() != 0) {
for (int i = 0; i < periodName.size(); i++) {
//画数字
mPaint.setTextSize(dp2px(10));
mPaint.setTextAlign(Paint.Align.LEFT);
//获得textview的高度或宽度 计算文字所在的矩形mBound 来得到文字的宽高
mPaint.getTextBounds("月12345678", 0, String.valueOf(i).length(), mBound);
/*float[] pos = new float[20];
for (int j = 0; j < 20; j++) {
if (j % 2 == 0) {
pos[j] = mStartWidth + mBound.height() * j / 2;
} else {
pos[j] = mHeight * 24 / 30 + mBound.height() + mBound.height() * j / 2;
}
}*/
mPaint.getTextBounds("安托山花园一期一栋一层", 0, String.valueOf(i).length(), mBound);
Path paths = new Path();
paths.moveTo(mStartWidth - mBound.height() / 2, mHeight * 24 / 30 + mBound.height());
paths.lineTo(mStartWidth + mBound.height() * 10, mHeight * 24 / 30 + mBound.height() * 10);
if (textSelectIndexs.contains(i)) {
mPaint.setColor(Color.parseColor("#333333"));
textSelectIndexs.clear();
} else {
mPaint.setColor(lineColor);
}
//canvas.drawPosText(periodName.get(i), pos, mPaint);
canvas.drawTextOnPath(periodName.get(i), paths, 0, 0, mPaint);
mStartWidth += 5 + 3 * mSize;
}
}
//画柱状图
for (int i = 0; i < list.size(); i++) {
//添加百分比
mPercent.setTextSize(dp2px(8));
mPercent.setTextAlign(Paint.Align.CENTER);
DecimalFormat df = new DecimalFormat("#.#");
String format = df.format(list.get(i));
canvas.drawText(format + "%", mChartWidth + mSize / 2, (mHeight * 18 / 30) * (100 - list.get(i)) / 100 + mHeight * 6 / 30 - 10, mPercent);
//画柱状图
mChartPaint.setStyle(Paint.Style.FILL);
if (StringUtils.equals(format, "0")) {
canvas.drawLine(mChartWidth, mHeight * 24 / 30, mChartWidth + mSize, mHeight * 24 / 30, mStroke);
}
//如果包含就变色
if (selectIndexRoles.contains(i)) {
canvas.drawRect(mChartWidth, (mHeight * 18 / 30) * (100 - list.get(i)) / 100 + mHeight * 6 / 30, mChartWidth + mSize, mHeight * 24 / 30, mStroke);
//重新请求数据时避免之前点击的柱状图还处于变色状态
if (i % 2 == 1) {
selectIndexRoles.clear();
}
} else {
}
if (i % 2 == 0) {
mChartPaint.setColor(selectLeftColor);
} else {
mChartPaint.setColor(selectRightColor);
}
//画柱状图
//左上的坐标和右下的坐标确定矩形
canvas.drawRect(mChartWidth, (mHeight * 18 / 30) * (100 - list.get(i)) / 100 + mHeight * 6 / 30, mChartWidth + mSize, mHeight * 24 / 30, mChartPaint);
// ;// 长方形
mChartWidth += (i % 2 == 0) ? (5 + mSize) : (2 * mSize);
}
}
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
}
}
/**
* 注意:
* 当屏幕焦点变化时重新侧向起始位置,必须重写次方法,否则当焦点变化时柱状图会跑到屏幕外面
*/
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == VISIBLE) {
mSize = dp2px(20);
mStartWidth = dp2px(5) + mSize;
mChartWidth = dp2px(5);
}
}
/**
* 柱状图touch事件
* 获取触摸位置计算属于哪个月份的
*
* @param ev
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
int mChartWidthd = dp2px(5);
for (int i = 0; i < list.size(); i++) {
int x1 = mChartWidthd;
int x2 = mChartWidthd + mSize;
int y1 = (int) ((mHeight * 18 / 30) * (100 - list.get(i)) / 100 + mHeight * 6 / 30);
int y2 = mHeight * 24 / 30;
if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
if (i % 2 == 0) {
selectIndex = i;
//传入mData中的i, 和两个圆柱中间位置
textSelectIndex = i / 2;
if (list.get(i + 1) != null) {
if (list.get(i + 1) - list.get(i) >= 0){
listener.getNumber(i / 2, x2, (int) ((mHeight * 18 / 30) * (100 - list.get(i + 1)) / 100 + mHeight * 6 / 30 - 10));
} else {
listener.getNumber(i / 2, x2, y1 - 10);
}
}
} else {
selectIndex = i - 1;
textSelectIndex = (i - 1) / 2;
if (list.get(i) - list.get(i - 1) >= 0) {
//传入下标,横坐标中间和纵坐标上面的位置
listener.getNumber((i - 1) / 2, x2 - mSize - 3, y1 - 10);
} else {
listener.getNumber((i - 1) / 2, x2 - mSize - 3, (int) ((mHeight * 18 / 30) * (100 - list.get(i - 1)) / 100 + mHeight * 6 / 30 - 10));
}
}
selectIndexRoles.clear();
textSelectIndexs.clear();
selectIndexRoles.add(selectIndex);
selectIndexRoles.add(selectIndex + 1);
textSelectIndexs.add(textSelectIndex);
invalidate();
}
mChartWidthd += (i % 2 == 0) ? (5 + mSize) : (2 * mSize);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
public interface getNumberListener {
void getNumber(int number, int x, int y);
}
public int getLeftColor() {
return leftColor;
}
public void setLeftColor(int leftColor) {
this.leftColor = leftColor;
}
public int getRightColor() {
return rightColor;
}
public void setRightColor(int rightColor) {
this.rightColor = rightColor;
}
public int getLineColor() {
return lineColor;
}
public void setLineColor(int lineColor) {
this.lineColor = lineColor;
}
public int getSelectLeftColor() {
return selectLeftColor;
}
public void setSelectLeftColor(int selectLeftColor) {
this.selectLeftColor = selectLeftColor;
}
public int getSelectRightColor() {
return selectRightColor;
}
public void setSelectRightColor(int selectRightColor) {
this.selectRightColor = selectRightColor;
}
public int getLefrColorBottom() {
return lefrColorBottom;
}
public void setLefrColorBottom(int lefrColorBottom) {
this.lefrColorBottom = lefrColorBottom;
}
public int getRightColorBottom() {
return rightColorBottom;
}
public void setRightColorBottom(int rightColorBottom) {
this.rightColorBottom = rightColorBottom;
}
}
主控件
package com.pcjz.dems.widget.chatview;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.pcjz.dems.R;
import com.pcjz.dems.entity.reportform.ReportformRate;
import com.pcjz.dems.entity.will.ProjectAcceptanceStatisticsModel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class HistogramView extends LinearLayout {
private MyChartView1 myChartView;
private List<Float> chartList;
private RelativeLayout relativeLayout;
private LinearLayout llChart;
private Context mContext;
public List<ReportformRate> mData;
public HistogramView(Context context) {
super(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);
this.mContext = context;
}
public void refreshRecView(List<ReportformRate> data) {
this.mData = data;
View view = View.inflate(mContext, R.layout.view_histogram, null);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
setOrientation(VERTICAL);
addView(view, lp);
myChartView = (MyChartView1) findViewById(R.id.my_chart_view);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) myChartView.getLayoutParams();
params.width = initWidth(mData);
myChartView.setLayoutParams(params);
initChatView();
postInvalidate();
}
private int initWidth(List<ReportformRate> mData) {
WindowManager wm = (WindowManager) getContext()
.getSystemService(Context.WINDOW_SERVICE);
int screenWidth = wm.getDefaultDisplay().getWidth();
int width = dp2px(40) + (5 + 3 * dp2px(20)) * mData.size() + dp2px(20);
if (width < screenWidth) {
return screenWidth;
} else {
return width;
}
}
private void initChatView() {
myChartView.setMdatas(mData);
myChartView.setLefrColorBottom(ContextCompat.getColor(mContext, R.color.leftColorBottom));
myChartView.setLeftColor(ContextCompat.getColor(mContext, R.color.left_color));
myChartView.setRightColor(ContextCompat.getColor(mContext, R.color.right_color));
myChartView.setRightColorBottom(ContextCompat.getColor(mContext, R.color.rightColor));
myChartView.setSelectLeftColor(ContextCompat.getColor(mContext, R.color.left_color));
myChartView.setSelectRightColor(ContextCompat.getColor(mContext, R.color.right_color));
myChartView.setLineColor(ContextCompat.getColor(mContext, R.color.txt_gray_shallow));
chartList = new ArrayList<>();
relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout);
relativeLayout.removeView(llChart);
List<Float> reviewRateList = new ArrayList<>();
List<Float> qualifiedRateList = new ArrayList<>();
for (int i = 0; i < mData.size(); i++) {
reviewRateList.add(mData.get(i).getReviewRate() * 100);
qualifiedRateList.add(mData.get(i).getQualifiedRate() * 100);
}
for (int i = 0; i < mData.size(); i++) {
chartList.add(qualifiedRateList.get(i));
chartList.add(reviewRateList.get(i));
}
myChartView.setList(chartList);
myChartView.setListener(new MyChartView1.getNumberListener() {
@Override
public void getNumber(int number, int x, int y) {
relativeLayout.removeView(llChart);
//反射加载点击柱状图弹出布局
llChart = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.test_layout_shouru_zhichu, null);
TextView tvPeriodName = (TextView) llChart.findViewById(R.id.tv_PeriodName);
TextView tvQualifiedRate = (TextView) llChart.findViewById(R.id.tv_QualifiedRate);
TextView tvReviewRate = (TextView) llChart.findViewById(R.id.tv_ReviewRate);
LinearLayout linearLayout = (LinearLayout) llChart.findViewById(R.id.ll_kuang);
if (null != mData) {
if (mData.get(number).getPeriodName() != null) {
tvPeriodName.setText(mData.get(number).getPeriodName());
} else if (mData.get(number).getCorporateName() != null) {
tvPeriodName.setText(mData.get(number).getCorporateName());
} else if (mData.get(number).getTeamName() != null) {
tvPeriodName.setText(mData.get(number).getTeamName());
}
DecimalFormat df = new DecimalFormat("#.#");
tvQualifiedRate.setText("合格率:" + df.format(mData.get(number).getQualifiedRate() * 100) + "%");
tvReviewRate.setText("复验率:" + df.format(mData.get(number).getReviewRate() * 100) + "%");
llChart.measure(0, 0);//调用该方法后才能获取到布局的宽度
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
if (x - llChart.getMeasuredWidth() / 2 < 0) {
//如果到达了左边界,则为负值
params.leftMargin = 0;
linearLayout.setBackground(ContextCompat.getDrawable(getContext(), R.mipmap.kuang1));
} else {
params.leftMargin = x - llChart.getMeasuredWidth() / 2;
linearLayout.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.kuang));
}
if (y - llChart.getMeasuredHeight() < 0) {
params.topMargin = 0;
} else {
params.topMargin = y - llChart.getMeasuredHeight();
}
llChart.setLayoutParams(params);
}
relativeLayout.addView(llChart);
}
});
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/ll_layout">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/tv_accepte"
android:background="@color/leftColorBottom" />
<TextView
android:id="@+id/tv_accepte"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:gravity="center"
android:text="合格率"
android:textSize="14sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/tv_again_accept"
android:background="@color/rightColor" />
<TextView
android:id="@+id/tv_again_accept"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:gravity="center"
android:text="复验率"
android:textSize="14sp" />
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="40dp"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
style="@style/ReportformsNumber"
android:layout_weight="5"
android:paddingLeft="3dp"
android:paddingBottom="5dp"
android:gravity="bottom"
android:text="百分比" />
<TextView
style="@style/ReportformsNumber"
android:text="100" />
<TextView
style="@style/ReportformsNumber"
android:text="90" />
<TextView
style="@style/ReportformsNumber"
android:text="80" />
<TextView
style="@style/ReportformsNumber"
android:text="70" />
<TextView
style="@style/ReportformsNumber"
android:text="60" />
<TextView
style="@style/ReportformsNumber"
android:text="50" />
<TextView
style="@style/ReportformsNumber"
android:text="40" />
<TextView
style="@style/ReportformsNumber"
android:text="30" />
<TextView
style="@style/ReportformsNumber"
android:text="20" />
<TextView
style="@style/ReportformsNumber"
android:text="10" />
<TextView
style="@style/ReportformsNumber"
android:text="0"
android:layout_weight="0.9"/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="6" />
</LinearLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="40dp"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<com.pcjz.dems.widget.chatview.MyChartView1
android:id="@+id/my_chart_view"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</RelativeLayout>
</LinearLayout>
</HorizontalScrollView>
</FrameLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/ll_kuang"
android:background="@drawable/kuang"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_PeriodName"
android:textSize="12sp"
android:text="龙光花园一期"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_QualifiedRate"
android:text="合格率:73"
android:textSize="12sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:textSize="12sp"
android:id="@+id/tv_ReviewRate"
android:text="复验率:4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
单柱图画法
单柱图相对双柱图要简单很多,我这里单柱图是根据双柱图延伸出来的
package com.pcjz.dems.widget.chatview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import com.pcjz.dems.R;
import com.pcjz.dems.common.utils.StringUtils;
import com.pcjz.dems.entity.reportform.inspector.InspectorRate;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
/**
* created by yezhengyu on 2017/8/29 09:40
*/
public class SingleChartView extends View{
private Paint mPaint, mChartPaint, mPercent, mStroke;
private Rect mBound;
private int mStartWidth, mHeight, mWidth, mChartWidth, mSize;
private int lineColor, selectColor;
private List<Float> list = new ArrayList<>();
private getNumberListener listener;
private int selectIndex = -1;
private List<Integer> selectIndexRoles = new ArrayList<>();
private List<InspectorRate> mDatas;
public void setList(List<Float> list) {
this.list = list;
//柱状图的宽度
mSize = dp2px(24);
mStartWidth = dp2px(15) + mSize / 2;
mChartWidth = dp2px(15);
invalidate();
}
public SingleChartView(Context context) {
this(context, null);
}
public SingleChartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SingleChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView1, defStyleAttr, 0);
int n = array.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.MyChartView1_xyColor:
lineColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView1_selectLeftColor:
// 默认颜色设置为黑色
selectColor = array.getColor(attr, Color.BLACK);
break;
default:
bringToFront();
}
}
array.recycle();
if (selectIndexRoles.size() != 0) {
selectIndexRoles.clear();
}
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
width = widthSize;
height = heightSize;
setMeasuredDimension(width, height);
}
private void initView() {
mWidth = getWidth();
mHeight = getHeight();
mSize = dp2px(24);
mStartWidth = dp2px(15) + mSize / 2;
mChartWidth = dp2px(15);
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mBound = new Rect();
mChartPaint = new Paint();
mChartPaint.setAntiAlias(true);
mPercent = new Paint();
mPercent.setAntiAlias(true);
mStroke = new Paint();
mStroke.setAntiAlias(true);
mStroke.setStyle(Paint.Style.STROKE);
mStroke.setStrokeWidth(3);
mStroke.setColor(Color.parseColor("#333333"));
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
}
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == VISIBLE) {
mSize = dp2px(24);
mStartWidth = dp2px(15) + mSize / 2;
mChartWidth = dp2px(15);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initView();
mPaint.setColor(lineColor);
//画坐标轴
for (int i = 0; i <= 10; i++) {
mPaint.setTextSize(dp2px(11));
canvas.drawLine(0, (mHeight * 25 / 30) - (mHeight * 2 / 3) * i / 10, mWidth, (mHeight * 25 / 30) - (mHeight * 2 / 3) * i / 10, mPaint);
}
if (mDatas != null) {
List<String> name = new ArrayList<>();
for (int i = 0; i < mDatas.size(); i++) {
if (mDatas.get(i).getUserName() != null) {
name.add(mDatas.get(i).getUserName());
} else if (mDatas.get(i).getCorporateName() != null) {
name.add(mDatas.get(i).getCorporateName());
}
}
if (name.size() != 0) {
for (int i = 0; i < name.size(); i++) {
mPaint.setTextSize(dp2px(11));
mPaint.setTextAlign(Paint.Align.CENTER);
//获得textview的高度或宽度 计算文字所在的矩形mBound 来得到文字的宽高
mPaint.getTextBounds(String.valueOf(i + 1) + "月", 0, String.valueOf(i).length(), mBound);
if (selectIndexRoles.contains(i)) {
mPaint.setColor(Color.parseColor("#333333"));
} else {
mPaint.setColor(lineColor);
}
String text;
if (name.get(i).length() <= 4) {
text = name.get(i);
} else {
text = name.get(i).substring(0, 4) + "..";
}
canvas.drawText(text, mStartWidth, mHeight * 25 / 30 + 2 * mBound.height(), mPaint);
mStartWidth += mSize + 3 * mSize / 2;
}
}
//画柱状图
for (int i = 0; i < list.size(); i++) {
//添加百分比
mPercent.setTextSize(dp2px(8));
mPercent.setTextAlign(Paint.Align.CENTER);
DecimalFormat df = new DecimalFormat("#.#");
String format = df.format(list.get(i));
canvas.drawText(format + "%", mChartWidth + mSize / 2, (mHeight * 2 / 3) * (100 - list.get(i)) / 100 + mHeight * 5 / 30 - 10, mPercent);
mChartPaint.setStyle(Paint.Style.FILL);
if (StringUtils.equals(format, "0")) {
canvas.drawLine(mChartWidth, mHeight * 25 / 30, mChartWidth + mSize, mHeight * 25 / 30, mStroke);
}
//如果包含就变色
if (selectIndexRoles.contains(i)) {
canvas.drawRect(mChartWidth, (mHeight * 2 / 3) * (100 - list.get(i)) / 100 + mHeight * 5 / 30, mChartWidth + mSize, mHeight * 25 / 30, mStroke);
selectIndexRoles.clear();
} else {
}
mChartPaint.setColor(selectColor);
//画柱状图
//左上的坐标和右下的坐标确定矩形
canvas.drawRect(mChartWidth, (mHeight * 2 / 3) * (100 - list.get(i)) / 100 + mHeight * 5 / 30, mChartWidth + mSize, mHeight * 25 / 30, mChartPaint);
// ;// 长方形
mChartWidth += mSize + 3 * mSize / 2;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
int mChartWidthd = dp2px(15);
for (int i = 0; i < list.size(); i++) {
int x1 = mChartWidthd;
int x2 = mChartWidthd + mSize;
int y1 = (int) ((mHeight * 2 / 3) * (100 - list.get(i)) / 100 + mHeight * 5 / 30);
int y2 = mHeight * 25 / 30;
if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
selectIndex = i;
//传入mData中的i, 和两个圆柱中间位置, 和圆柱的顶点坐标
listener.getNumber(i, x2 - mSize / 2, y1 - 15);
selectIndexRoles.clear();
selectIndexRoles.add(selectIndex);
invalidate();
}
mChartWidthd += mSize + 3 * mSize / 2;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
public void setMdatas(List<InspectorRate> mDatas) {
this.mDatas = mDatas;
invalidate();
}
public void setListener(getNumberListener listener) {
this.listener = listener;
}
public interface getNumberListener {
void getNumber(int number, int x, int y);
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
public int getLineColor() {
return lineColor;
}
public void setLineColor(int lineColor) {
this.lineColor = lineColor;
}
public int getSelectColor() {
return selectColor;
}
public void setSelectColor(int selectColor) {
this.selectColor = selectColor;
}
}
package com.pcjz.dems.widget.chatview;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.pcjz.dems.R;
import com.pcjz.dems.common.utils.StringUtils;
import com.pcjz.dems.entity.reportform.inspector.InspectorRate;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
/**
* created by yezhengyu on 2017/8/30 16:02
*/
public class SingleHistogramView extends LinearLayout {
private Context mContext;
private List<InspectorRate> mData;
private SingleChartView mSingleView;
private List<Float> chartList;
private RelativeLayout relativeLayout;
private LinearLayout llChart;
private String type;
public SingleHistogramView(Context context) {
super(context, null);
}
public SingleHistogramView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SingleHistogramView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
}
public void refreshRecView(List<InspectorRate> mData, String type) {
this.type = type;
this.mData = mData;
View view = View.inflate(mContext, R.layout.view_single_histogram, null);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
setOrientation(VERTICAL);
addView(view, lp);
mSingleView = (SingleChartView) findViewById(R.id.my_single_chart_view);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mSingleView.getLayoutParams();
params.width = initWidth(mData);
mSingleView.setLayoutParams(params);
initChatView();
postInvalidate();
}
private int initWidth(List<InspectorRate> mData) {
WindowManager wm = (WindowManager) getContext()
.getSystemService(Context.WINDOW_SERVICE);
int screenWidth = wm.getDefaultDisplay().getWidth();
int width = dp2px(55) + 5 * dp2px(24) * mData.size() / 2;
if (width < screenWidth) {
return screenWidth;
} else {
return width;
}
}
private void initChatView() {
mSingleView.setMdatas(mData);
mSingleView.setLineColor(ContextCompat.getColor(mContext, R.color.txt_gray_shallow));
mSingleView.setSelectColor(ContextCompat.getColor(mContext, R.color.left_color));
chartList = new ArrayList<>();
relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout);
relativeLayout.removeView(llChart);
List<Float> rateList = new ArrayList<>();
if (StringUtils.equals(type, "quality")) {
for (int i = 0; i < mData.size(); i++) {
float conformRate = mData.get(i).getConformRate();
if ( conformRate >= 0 && conformRate <= 1) {
rateList.add(mData.get(i).getConformRate() * 100);
} else {
rateList.add(1.0F * 100);
}
}
} else {
for (int i = 0; i < mData.size(); i++) {
float qualifiedRate = mData.get(i).getQualifiedRate();
rateList.add(qualifiedRate * 100);
}
}
mSingleView.setList(rateList);
mSingleView.setListener(new SingleChartView.getNumberListener() {
@Override
public void getNumber(int number, int x, int y) {
relativeLayout.removeView(llChart);
//反射加载点击柱状图弹出布局
llChart = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.test_layout_shouru_zhichu, null);
TextView tvPeriodName = (TextView) llChart.findViewById(R.id.tv_PeriodName);
TextView tvQualifiedRate = (TextView) llChart.findViewById(R.id.tv_QualifiedRate);
LinearLayout linearLayout = (LinearLayout) llChart.findViewById(R.id.ll_kuang);
llChart.findViewById(R.id.tv_ReviewRate).setVisibility(GONE);
if (mData != null) {
if (mData.get(number).getUserName() != null) {
tvPeriodName.setText(mData.get(number).getUserName());
} else if (mData.get(number).getCorporateName() != null) {
tvPeriodName.setText(mData.get(number).getCorporateName());
}
DecimalFormat df = new DecimalFormat("#.#");
float rate;
if (type.equals("quality")) {
float conformRate = mData.get(number).getConformRate();
if (conformRate >= 0 && conformRate <= 1) {
rate = conformRate * 100;
} else {
rate = 1.0F * 100;
}
tvQualifiedRate.setText("符合率:" + df.format(rate) + "%");
} else {
float qualifiedRate = mData.get(number).getQualifiedRate();
rate = qualifiedRate * 100;
tvQualifiedRate.setText("合格率:" + df.format(rate) + "%");
}
}
llChart.measure(0, 0);//调用该方法后才能获取到布局的宽度
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
if (x - llChart.getMeasuredWidth() / 2 < 0) {
//如果到达了左边界,则为负值
params.leftMargin = 0;
linearLayout.setBackground(ContextCompat.getDrawable(getContext(), R.mipmap.kuang1));
} else {
params.leftMargin = x - llChart.getMeasuredWidth() / 2;
linearLayout.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.kuang));
}
params.topMargin = y - llChart.getMeasuredHeight();
llChart.setLayoutParams(params);
relativeLayout.addView(llChart);
}
});
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/ll_layout">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="40dp"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
style="@style/SingleNumber"
android:layout_weight="4"
android:paddingLeft="3dp"
android:paddingBottom="5dp"
android:gravity="bottom"
android:text="百分比" />
<TextView
style="@style/SingleNumber"
android:text="100" />
<TextView
style="@style/SingleNumber"
android:text="90" />
<TextView
style="@style/SingleNumber"
android:text="80" />
<TextView
style="@style/SingleNumber"
android:text="70" />
<TextView
style="@style/SingleNumber"
android:text="60" />
<TextView
style="@style/SingleNumber"
android:text="50" />
<TextView
style="@style/SingleNumber"
android:text="40" />
<TextView
style="@style/SingleNumber"
android:text="30" />
<TextView
style="@style/SingleNumber"
android:text="20" />
<TextView
style="@style/SingleNumber"
android:text="10" />
<TextView
style="@style/SingleNumber"
android:text="0"
android:layout_weight="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="5" />
</LinearLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="40dp"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<com.pcjz.dems.widget.chatview.SingleChartView
android:id="@+id/my_single_chart_view"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</RelativeLayout>
</LinearLayout>
</HorizontalScrollView>
</FrameLayout>
</LinearLayout>
最后给一句忠告,知其然更要知其然,脱离cv工程师。这样才能优秀,一起努力