Android表格自定义控件
jiulong90 人气:0近期公司要做报表功能,在网上搜索下表格的样式后便自己写了一个自定义的表格控件,该表格控件能根据设置的数据中数据的最大值自动设置左侧信息栏显示的值,使得条形图能尽量的充满控件,条形图部分支持左右滑动,数据的长度可能超过控件本身所能容纳的长度,所以在绘制的时候做了判断,当需要绘制的部分不再控件范围内则不进行绘制,具体请阅读代码,目前只支持一个名称对应一条数据,如有不足之处,大家提出帮忙修改
使用方法如下:
在xml文件中定义控件属性
<RelativeLayout 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" android:background="#333333" tools:context=".MainActivity"> <com.example.administrator.widget.MyChatView android:id="@+id/chatView" android:layout_width="match_parent" android:layout_height="250dp" android:layout_marginTop="100dp" android:background="#FFFFFF" /> </RelativeLayout>
在Activity中设置控件要显示的数据、设置显示的样式
public class MainActivity extends AppCompatActivity { private MyChatView mMyChatView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMyChatView = (MyChatView) this.findViewById(R.id.chatView); mMyChatView.setData(getData1()) .setDataCompany("美元") .setDataTitle("测试") .setLeftTextColor(Color.RED) .setBottomTextColor(Color.BLUE) .setmDataTopTextColor(Color.RED) .setDataBackgroundColor(Color.parseColor("#ABCDEF")) .setDataColor(Color.RED) .setTitleColor(Color.BLUE) .setLeftTextSize(12) .setBottomTextSize(15) .setDataTopTextSize(10) .setTitleTextSize(20) .setSpanBottomText(15); } /** * 获取测试数据 * @return 测试用的数据 */ private List<Map<String, String>> getData1(){ List<Map<String, String>> data = new ArrayList<>(); HashMap<String, String> map; int temp; for (int i=0; i<100; i++) { map = new HashMap<>(); map.put(MyChatView.NAME, "name:"+i); temp = (int) (Math.random()*100); map.put(MyChatView.VALUE, temp +"."+i*i); data.add(map); } return data; } }
自定义控件代码:
public class MyChatView extends View { /** 数据集合中的 Map 集合存放信息的键 */ public static final String NAME = "name"; /** 数据集合中的 Map 集合存放数据的键 */ public static final String VALUE = "value"; /** 上下文 */ private Context mContext; /** 控件的高度 */ private int mHeight; /** 控件的宽度 */ private int mWidget; /** 数据 */ private List<Map<String, String>> mData; /** 数据单位 */ private String mDataCompany = "单位: "; /** 底部表格名称 */ private String mDataTitle = null; /** 底部信息栏文字的大小 */ private int mBottomTextSize; /** 左侧等分信息栏文字的大小 */ private int mLeftTextSize; /** 柱状图顶部文字的大小 */ private int mDataTopTextSize; /** 表格标题文字大小 */ private int mTitleTextSize; /** 左侧文字与数据区域的间隔 */ private int mSpanLeftText; /** 柱状图顶部文字与柱状图的间隔 */ int mSpanDataTopText; /** 底部信息字符串间隔 */ int mSpanBottomText; /** 底部信息字符串与控件底部间隔 */ int mSpanBottom; /** 绘制数据部分的背景颜色 */ private int mDataBackgroundColor = Color.WHITE; /** 底部信息字符串颜色 */ private int mBottomTextColor = Color.BLACK; /** 柱状图柱状部分颜色 */ private int mDataColor = Color.BLACK; /** 左边信息栏文字颜色 */ private int mLeftTextColor = Color.BLACK; /** 柱状图顶部文字颜色 */ private int mDataTopTextColor = Color.BLACK; /** 标题颜色 */ private int mTitleColor = Color.BLACK; /** 表格移动的位置 */ private int mChartMovedSize = 0; /** 用户按下时 X 方向位置 */ private int mDownX = 0; /** 用户松手是 X 方向位置 */ private int mUpX = 0; /** 表格 X 方向移动的最大距离 */ private int mChartMaxMovedLengthX; public MyChatView(Context context) { this(context, null); } public MyChatView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyChatView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; this.mBottomTextSize = dpToPx(context, 15); this.mLeftTextSize = dpToPx(context, 10); this.mDataTopTextSize = dpToPx(context, 10); this.mSpanLeftText = dpToPx(context, 2); this.mSpanDataTopText = dpToPx(context, 3); this.mSpanBottomText = dpToPx(context, 10); this.mSpanBottom = dpToPx(context, 8); this.mTitleTextSize = dpToPx(context, 20); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); this.mHeight = h; this.mWidget = w; } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: mUpX = (int) event.getX(); shouldMoveChart(); break; case MotionEvent.ACTION_UP: mUpX = (int) event.getX(); shouldMoveChart(); break; } return true; } /** * 判断移动的距离大于规定距离就移动表格 */ private void shouldMoveChart (){ if (mChartMaxMovedLengthX<mWidget) { this.mChartMaxMovedLengthX = (getXTotalLength() - mWidget*2/3); } int size = dpToPx(mContext, 2); if ((mUpX-mDownX)>=size || (mDownX-mUpX)>=size ) { mChartMovedSize += (mUpX - mDownX); mDownX = mUpX; if (mChartMovedSize>=0) { mChartMovedSize = 0; } if (mChartMovedSize<-mChartMaxMovedLengthX) { mChartMovedSize = -mChartMaxMovedLengthX; } this.invalidate(); } } private InnerDraw innerDraw; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (innerDraw==null) { innerDraw = new InnerDraw(canvas); } else { innerDraw.initData(canvas); } innerDraw.drawLeftMenue(); innerDraw.drawDataBackground(); innerDraw.drawDataTitle(); innerDraw.drawCompany(); int dataSize = mData.size(); for (int i=0; i<dataSize; i++) { //设置数据,必须在第一行 int state = innerDraw.startDrawBody(mData.get(i), i); if (state==0) { continue; } else if (state==-1) { break; } innerDraw.drawDataTopText(); innerDraw.drawDataBar(); innerDraw.drawBottomMessage(); innerDraw.endDrawBody(); } } /** * 根据文字大小获取文字高度 * @param paint 画笔 * @param textSize 要绘制的文字的大小 * @return 要绘制文字的高度 */ private int getTextHeight (Paint paint, int textSize){ String text = "测试"; Rect rect = new Rect(); paint.setTextSize(textSize); paint.getTextBounds(text, 0, text.length(), rect); return rect.height(); } /** * 根据文字大小获取文字宽度 * @param paint 画笔 * @param text 要绘制的文字 * @param textSize 要绘制的文字的大小 * @return 要绘制文字的宽度 */ private int getTextWidth (Paint paint, String text, int textSize){ Rect rect = new Rect(); paint.setTextSize(textSize); paint.getTextBounds(text, 0, text.length(), rect); return rect.width(); } /** * 获取左侧等分等分信息中文字最长的字符串 * @return 字符串,抹去数字后面小数点 */ private String getLeftValueMaxString (){ int size = mData.size(); String maxLengthString = "1"; String tempString; for (int i=0; i<size; i++) { tempString = mData.get(i).get(VALUE); if (tempString.length()>maxLengthString.length()) { maxLengthString = tempString; } } if (maxLengthString.contains(".")) { maxLengthString = maxLengthString.substring(0, maxLengthString.indexOf('.')+2); } return maxLengthString; } /** * 获取最长信息字符串 * @return 底部信息栏中最长字符串 */ private String getBottomMaxLegthString (){ int size = mData.size(); String maxString = "你好我好大家好才是真的好"; String tempString; for (int i=0; i<size; i++) { tempString = mData.get(i).get(NAME); if (maxString.length()>tempString.length()) { maxString = tempString; } } return maxString; } /** * 获取数据中的最大值 * @return 数据中的最大值 int float 类型 */ private int getMaxValue (){ int maxValue = Float.valueOf(mData.get(0).get(VALUE)).intValue(); int tempValue; for (Map<String, String> map : mData) { tempValue = Float.valueOf(map.get(VALUE)).intValue(); if (maxValue<tempValue) { maxValue = tempValue + tempValue/10; } } return maxValue; } /** * 获取表格 X 方向的总长 * @return X 方向总长 int */ private int getXTotalLength(){ int xTotalLength = 0; Paint paint = new Paint(); for (Map<String, String> map : mData) { xTotalLength += getTextWidth(paint, map.get(NAME), mBottomTextSize); xTotalLength = xTotalLength + mSpanBottomText; } return xTotalLength; } /** * 根据数据中的最大值将数据分成10等分,每等分为10的倍数 * @param maxValue 数据中的最大值 * @return 左侧等分栏的每一等分的数值 int 类型 */ private int getPiceValue (int maxValue){ int piceValue = 1; while (maxValue>10) { maxValue = maxValue/10; piceValue = piceValue * 10; } if (maxValue<=5) { piceValue = piceValue / 2; } return piceValue; } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) * @param context 上下文 * @param dpValue 要转换的 dp 值 * @return 转换后的 px 值 */ private int dpToPx(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } /** * 设置数据 * @param data 表中的数据 * data集合中的Map * "name"存放名称,使用MyCharView中的常量 NAME * "value"存放数据,数据为int类型的字符串,使用MyCharView中的常量 VALUE */ public MyChatView setData (List<Map<String, String>> data){ try { this.mData = data==null? new ArrayList<Map<String, String>>():data; }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置绘制数据部分背景颜色 * @param dataBackgroundColor 16进制 int 类型颜色 * @return 对象本身 */ public MyChatView setDataBackgroundColor (int dataBackgroundColor){ try { this.mDataBackgroundColor = dataBackgroundColor; }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置左边信息栏文字颜色 * @param leftTextColor 16进制 int 类型颜色 * @return 对象本身 */ public MyChatView setLeftTextColor (int leftTextColor){ try { this.mLeftTextColor = leftTextColor; }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置底部信息文字颜色 * @param bottomTextColor 16进制 int 类型颜色 * @return 对象本身 */ public MyChatView setBottomTextColor (int bottomTextColor){ try { this.mBottomTextColor = bottomTextColor; }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置柱状条的背景颜色 * @param dataColor 16进制 int 类型颜色 * @return 对象本身 */ public MyChatView setDataColor (int dataColor){ try { this.mDataColor = dataColor; }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置柱状条顶部文字颜色 * @param dataTopTextColor 16进制 int 类型颜色 * @return 对象本身 */ public MyChatView setmDataTopTextColor (int dataTopTextColor){ try { this.mDataTopTextColor = dataTopTextColor; }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置标题颜色 * @param titleColor 颜色16进制的 int 类型 * @return 对象本身 */ public MyChatView setTitleColor (int titleColor){ try { this.mTitleColor = titleColor; }catch (Exception e){ e.printStackTrace(); } return this; } /** * 设置底部信息文字大小 * @param bottomTextSize int 类型 dp * @return 对象本身 */ public MyChatView setBottomTextSize (int bottomTextSize){ try { this.mBottomTextSize = dpToPx(mContext, bottomTextSize); }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置左侧信息文字大小 * @param leftTextSize int 类型 dp * @return 对象本身 */ public MyChatView setLeftTextSize (int leftTextSize){ try { this.mLeftTextSize = dpToPx(mContext, leftTextSize); }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置柱状条顶部文字大小 * @param dataTopTextSize int 类型 dp * @return 对象本身 */ public MyChatView setDataTopTextSize (int dataTopTextSize){ try { this.mDataTopTextSize = dpToPx(mContext, dataTopTextSize); }catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置底部表格标题文字大小 * @param titleTextSize 标题文字大小 * @return 对象本身 */ public MyChatView setTitleTextSize (int titleTextSize){ try { this.mTitleTextSize = dpToPx(mContext,titleTextSize); } catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置表格数据单位 * @param dataCompany 数据单位 * @return 对象本身 */ public MyChatView setDataCompany (String dataCompany){ try { this.mDataCompany += dataCompany==null? "空":dataCompany; } catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置表格标题 * @param dataTitle 表格标题 * @return 对象本身 */ public MyChatView setDataTitle (String dataTitle){ try { mDataTitle = dataTitle; } catch (Exception e) { e.printStackTrace(); } return this; } /** * 设置底部信息栏文字的间隔 默认为 10dp * @param spanBottomText 间隔距离 dp * @return 对象本身 */ public MyChatView setSpanBottomText (int spanBottomText){ try { this.mSpanBottomText = dpToPx(mContext, spanBottomText); } catch (Exception e){ e.printStackTrace(); } return this; } //****************************** 绘制表格的类 ********************************** /** * 绘制控件界面的类 */ private class InnerDraw{ private Canvas canvas; private Paint paint; /** 记录绘制到的位置 */ private int bottomTextPainted; /** 底部标题高度 */ private int bottomTitleHeight; /** 底部信息的高度 */ private int bottomMessageHeight; /** 左侧信息栏最长文字 */ private String leftValueMaxString; /** 表格中柱状图上文字高度 */ private int dataTopTextHeight; /** 左侧信息栏宽度 */ private int leftValueWidth; /** 绘制数据部分的高度 */ private int chartDataHeight; /** 左侧等分栏的每等分高度 */ private int piceValueHeight; /** 左侧等分栏每等分的数值 */ private int piceValue; /** 柱状的宽度 */ private int dataBarWidth; /** 底部信息 */ String bottomMessage; /** 底部信息的宽度 */ private int bottomMessageWidth; /** 数据信息 */ Map<String, String> data; /** 要绘制信息的位置 */ int index; public InnerDraw (Canvas canvas){ initData(canvas); } /** * 初始化数据 */ public void initData(Canvas canvas){ this.canvas = canvas; this.paint = new Paint(); this.bottomTitleHeight = mDataTitle==null? 0:(getTextHeight(paint, mTitleTextSize)+mSpanBottom/2); this.bottomTextPainted = 0; this.bottomMessageHeight = getTextHeight(paint, mBottomTextSize) + mSpanBottom + bottomTitleHeight; this.leftValueMaxString = getLeftValueMaxString(); this.dataTopTextHeight = getTextHeight(paint, mDataTopTextSize); this.leftValueWidth = getTextWidth(paint, leftValueMaxString , mLeftTextSize) + mSpanLeftText; this.chartDataHeight = mHeight - bottomMessageHeight - mSpanBottom; this.piceValueHeight = (chartDataHeight-dataTopTextHeight-getTextHeight(paint, mSpanDataTopText))/10; this.piceValue = getPiceValue(getMaxValue()); this.dataBarWidth = getTextWidth(paint, getBottomMaxLegthString(), mBottomTextSize); this.bottomMessage = ""; this.bottomMessageWidth = 0; this.data = new HashMap<>(); this.index = 0; } /** * 绘制左侧等分栏 */ public void drawLeftMenue(){ paint.setColor(mLeftTextColor); paint.setTextSize(mLeftTextSize); int textLeft; int textTop; String valueStr; int strLength; String maxValueStr = String.valueOf(piceValue * 10); int textMaxLength = maxValueStr.length(); int topTextHeight = getTextHeight(paint, mDataTopTextSize); int leftTextHeight = getTextHeight(paint, mLeftTextSize); for (int i=0; i<=10; i++) { textLeft = 0; valueStr = String.valueOf(i * piceValue); strLength = valueStr.length(); if (strLength<textMaxLength) { textLeft = getTextWidth(paint, maxValueStr.substring(strLength), mLeftTextSize); } textTop = (10-i)*piceValueHeight + topTextHeight + mSpanDataTopText + leftTextHeight/2; canvas.drawText(valueStr+"", textLeft, textTop, paint); } } /** * 绘制表格中数据部分背景 */ public void drawDataBackground() { paint.setColor(mDataBackgroundColor); Rect rect = new Rect(leftValueWidth, 0, mWidget, chartDataHeight); canvas.drawRect(rect, paint); } /** * 绘制底部标题 */ public void drawDataTitle() { if (mDataTitle != null) { paint.setColor(mTitleColor); paint.setTextSize(mTitleTextSize); int titleWidget = getTextWidth(paint, mDataTitle, mTitleTextSize); float titleLeft = (mWidget - titleWidget) / 2; float titleTop = mHeight - mSpanBottom; canvas.drawText(mDataTitle, titleLeft, titleTop, paint); } } /** * 绘制数据单位 */ public void drawCompany() { paint.setTextSize(mLeftTextSize); paint.setColor(mLeftTextColor); int comPanyLeft = mWidget - getTextWidth(paint, mDataCompany + "111", mLeftTextSize); int companyBottom = dataTopTextHeight + getTextHeight(paint, mLeftTextSize); canvas.drawText(mDataCompany, comPanyLeft, companyBottom, paint); } /** * 开始绘制,设置数据,同事设置底部信息宽度 * @param data 数据信息 * @param index 绘制到的位置 * @return 状态码: -1 为结束循环,0 为继续下一个循环,1 为正常绘制 */ public int startDrawBody(Map<String, String> data, int index){ this.data = data; this.bottomMessage = data.get(NAME).trim(); this.bottomMessageWidth = getTextWidth(paint, bottomMessage, mBottomTextSize); this.index = index; int bottomMsgBegainDrawX = (index + 1) * mSpanBottomText + mChartMovedSize + leftValueWidth + bottomTextPainted; if ((bottomMsgBegainDrawX+bottomMessageWidth)<leftValueWidth) {//需要绘制的区域在绘制区域的左侧 bottomTextPainted += bottomMessageWidth; return 0; } if (bottomMsgBegainDrawX>mWidget) {//需要绘制的区域超出了控件的右边,结束绘制 return -1; } return 1; } /** * 结束绘制 */ public void endDrawBody(){ bottomTextPainted += bottomMessageWidth; } /** * 绘制底部信息栏 */ public void drawBottomMessage() { paint.setColor(mBottomTextColor); paint.setTextSize(mBottomTextSize); int bottomLeft = (index + 1) * mSpanBottomText + mChartMovedSize + leftValueWidth + bottomTextPainted; int bottomTop = chartDataHeight + bottomMessageHeight - bottomTitleHeight - dpToPx(mContext, 2); if (bottomLeft >= leftValueWidth && bottomLeft < mWidget) { canvas.drawText(bottomMessage, bottomLeft, bottomTop, paint); } else if ((bottomLeft+bottomMessageWidth)>leftValueWidth) { int index = (leftValueWidth-bottomLeft)*bottomMessage.length()/bottomMessageWidth+1; if (index>=0 && index <bottomMessage.length()) { canvas.drawText(bottomMessage.substring(index), leftValueWidth, bottomTop, paint); } } } /** * 绘制条形数据 */ public void drawDataBar() { paint.setColor(mDataColor); int dataValue = Float.valueOf(data.get(VALUE)).intValue(); int bottomLeft = (index + 1) * mSpanBottomText + mChartMovedSize + leftValueWidth + bottomTextPainted; int dataLeft = bottomLeft + bottomMessageWidth / 2 - dataBarWidth / 4; int dataBottom = chartDataHeight; int dataTop = chartDataHeight - (dataValue * (chartDataHeight - dataTopTextHeight - mSpanDataTopText) / piceValue) / 10; int dataRight = dataLeft + dataBarWidth / 2; if (dataLeft < leftValueWidth && dataRight > leftValueWidth) { dataLeft = leftValueWidth; } Rect dataRect = new Rect(dataLeft, dataTop, dataRight, dataBottom); if (dataRight>leftValueWidth) { canvas.drawRect(dataRect, paint); } } /** * 绘制条形数据顶部文字 */ public void drawDataTopText() { String topTextMessage = data.get(VALUE); int topTextWidth = getTextWidth(paint, topTextMessage, mDataTopTextSize); paint.setColor(mDataTopTextColor); paint.setTextSize(mDataTopTextSize); int bottomLeft = (index + 1) * mSpanBottomText + mChartMovedSize + leftValueWidth + bottomTextPainted; int topTextLeft = bottomLeft + (bottomMessageWidth - topTextWidth) / 2; int dataValue = Float.valueOf(data.get(VALUE)).intValue(); int dataTop = chartDataHeight - (dataValue*(chartDataHeight-dataTopTextHeight - mSpanDataTopText)/piceValue)/10; int topTextTop = dataTop - mSpanDataTopText * 2 / 3; if (topTextLeft >= leftValueWidth && bottomLeft < mWidget) { canvas.drawText(topTextMessage, topTextLeft, topTextTop, paint); } else if ((topTextLeft+topTextWidth)>leftValueWidth) { int index = (leftValueWidth-topTextLeft)*topTextMessage.length()/topTextWidth+1; if (index>=0 && index <topTextMessage.length()) { canvas.drawText(topTextMessage.substring(index), leftValueWidth, topTextTop, paint); } } } } }
实现效果如下图
加载全部内容