寫完了
android
圖表,一個朋友說他們公司須要作表格。問我能作嗎?我答這有啥不能作。我就開始幾個吧唧吧唧寫,快寫完了,朋友說表格在android
體驗很差。坑壁啊,最好放在github
上。經過此次我對自定義有點體會,就想這篇文章。哈哈。文筆很差,將就的看吧。
github地址:github.com/huangyanbin…android
俗話說無圖無真相,先上圖:git
支持圖片以及
Text drawpadding
四個方向
github![]()
分頁模式
canvas![]()
網絡數據註解模式數組
padding
等配置;一看就知道須要是自定義
View
。首先一看錶格。表格標題,頂部序號,左側序號,列標題,統計行,表格內容。分析完了表格屬性。可是咱們要自動生成表格,表格數據通常都是一個List<Data>
,立刻就想到了註解。對的,每列對應的Data
一個成員變量。開始動手了!bash
首先咱們自定義一個
View
SmartTable
,裏面分別有表格標題,頂部序號,左側序號,表格內容。等等,咋沒有統計行,統計行我放在表格內容裏面了。裏面泛型T是啥鬼,答曰表格數據類型。網絡
public class SmartTable<T> extends View{
//標題
private ITableTitle tableTitle;
//頂部序號
private XSequence<T> xAxis;
//左側序號
private YSequence<T> yAxis;
// 表格內容
private TableProvider<T> provider;複製代碼
咱們要用註解來解析數據,因此咱們要定義註解。固然,咱們也要支持普通模式。咱們這裏先作註解。ide
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SmartTable {
String name() default "";
boolean count() default false;
int pageSize() default Integer.MAX_VALUE;
int currentPage() default 0;
}複製代碼
爲啥要寫這麼多屬性,解釋一下:
name
表明表格標題,count
是打開統計,pageSize
和currentPage
是用於分頁的。放在類名上。字體再就是須要顯示的列數據註解。優化
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface SmartColumn {
/**
* 名稱
* @return
*/
String name() default "";
/**
* id 越小越在前面
* @return
*/
int id() default 0;
String parent() default "";
/**
* 設置是否查詢下一級
* @return
*/
ColumnType type() default ColumnType.Own;
boolean autoCount() default false;
}
public enum ColumnType {
Own,Child;
}複製代碼
爲啥須要設置
Id
。由於表格列位置能夠排序,列排序是根據Id
大小來排的。
ColumnType
是啥鬼,好比數據User
對象有個Father
屬性,Father
有name
.咱們可能須要顯示father.name
。咱們就能夠用ColumnType.child
就會進去這個對象裏面遞歸的查詢是否有smartColumn
註解。autoCount
是否開啓統計,由於大部分列是不須要統計的。parent
:表格常常幾列爲一大列,parent
就是指定父列名稱。咱們開始來解析註解,首先解析
SmartTable
註解,將註解屬性放入TableData
,而後獲取Class
的Fields
,迭代去獲取列信息:
public class AnnotationParser<T> {
//獲取解析以後表格數據
public PageTableData<T> parse(List<T> dataList){
if(dataList!= null && dataList.size() >0) {
T firstData = dataList.get(0);
if(firstData != null) {
Class clazz = firstData.getClass();
Annotation tableAnnotation = clazz.getAnnotation(SmartTable.class);
if(tableAnnotation != null){
//將註解的SmartTable的屬性放入TableData保存
SmartTable table = (SmartTable) tableAnnotation;
List<Column> columns = new ArrayList<>();
PageTableData<T> tableData = new PageTableData<>(table.name(),dataList,columns);
tableData.setCurrentPage(table.currentPage());
tableData.setPageSize(table.pageSize());
tableData.setShowCount(table.count());
FieldGenericHandler genericHandler = new FieldGenericHandler();
Map<String,Column> parentMap = new HashMap<>();
getColumnAnnotation(clazz, null,columns, genericHandler, parentMap);
//根據ID排序列
Collections.sort(columns);
return tableData;
}
}
}
return null;複製代碼
接下來就是遞歸去解析
SmartColumn
註解。
//遞歸去獲取SmartColumn註解
private void getColumnAnnotation(Class clazz, String parentFieldName, List<Column> columns, FieldGenericHandler genericHandler, Map<String, Column> parentMap) {
Field[] fields = clazz.getDeclaredFields();
//迭代Field
for(Field field:fields){
field.setAccessible(true);
//獲取屬性的類型
Class<?> fieldClass = field.getType();
Annotation fieldAnnotation = field.getAnnotation(SmartColumn.class);
if(fieldAnnotation != null){
SmartColumn smartColumn = (SmartColumn) fieldAnnotation;
ColumnType type = smartColumn.type();
if(type == ColumnType.Own) {
//將SmartColumn屬性放入Column對象
String name = smartColumn.name();
int id = smartColumn.id();
String parent = smartColumn.parent();
boolean isAutoCount = smartColumn.autoCount();
if (name.equals("")) {
name = field.getName();
}
String fieldName =parentFieldName != null? (parentFieldName+field.getName()) :field.getName();
//生成列信息
Column<?> column = genericHandler.getGenericColumn(fieldClass, name, fieldName);
column.setId(id);
column.setAutoCount(isAutoCount);
if (!parent.equals("")) {
//若是父列有的話,就直接使用
Column parentColumn = parentMap.get(parent);
if (parentColumn == null) {
//建立父列
List<Column> childColumns = new ArrayList<>();
childColumns.add(column);
parentColumn = new Column(parent, childColumns);
parentColumn.setId(id);
columns.add(parentColumn);
parentMap.put(parent, parentColumn);
}
//添加子列
parentColumn.addChildren(column);
if (id < parentColumn.getId()) {
parentColumn.setId(id);
}
}else{
//添加到columns
columns.add(column);
}
}else{
//由於是下層,全部用.鏈接起來 好比:father.name
String fieldName = (parentFieldName != null ?parentFieldName:"")
+field.getName()+".";
//遞歸去獲取下層
getColumnAnnotation(fieldClass,fieldName,columns,genericHandler,parentMap);
}
}
}
}
}複製代碼
將
List<Data>
轉換成每列數據.每列都須要去經過反射獲取真實的值。而後保存到Column List<T> datas
裏面.這裏只是解析數據部分
/**
* 遞歸解析獲取數據
*
*/
public T getData(Object o) throws NoSuchFieldException, IllegalAccessException {
Class clazz = o.getClass();
//解析
String[] fieldNames = fieldName.split("\\.");
String firstFieldName = fieldNames.length == 0 ? fieldName : fieldNames[0];
Field field = clazz.getDeclaredField(firstFieldName);
if (field != null) {
Object child = o;
if (fieldNames.length == 0 || fieldNames.length == 1) {
return getFieldValue(field, o);
}
for (int i = 0; i < fieldNames.length; i++) {
if (child == null) {
return null;
}
Class childClazz = child.getClass();
Field childField = childClazz.getDeclaredField(fieldNames[i]);
if (childField == null) {
return null;
}
if (i == fieldNames.length - 1) {
return getFieldValue(childField, child);
} else {
field.setAccessible(true);
child = field.get(child);
}
}
}
return null;
}複製代碼
獲取完成數據以後,發現每列的寬是由最長哪一個決定的,每列是寬是由行高決定的。忽然發現這個無語。咱們須要算出每行的寬和高。在計算表格的寬高時,咱們把想要的每行寬和高保存下來,這裏時計算高。高= 頂部序列號高+ 每行的高...+統計行高
/**
* 計算table高度
* @param tableData
* @param config
* @return
*/
private int getTableHeight(TableData<T> tableData,TableConfig config){
Paint paint = config.getPaint();
int topHeight = 0;
//是否顯示頂部序列號
if(config.isShowXSequence()) {
//計算頂部序列號高 加上設置的cell上下左右padding
topHeight = DrawUtils.getTextHeight(config.getXSequenceStyle(), paint)
+ 2 * config.getVerticalPadding();
}
int titleHeight = tableData.getTitleDrawFormat().measureHeight(config);
TableInfo tableInfo = tableData.getTableInfo();
tableInfo.setTitleHeight(titleHeight);
tableInfo.setTopHeight(topHeight);
int totalContentHeight = 0;
//把以前保存每行的高拿出來相加
for(int height :tableInfo.getLineHeightArray()){
totalContentHeight+=height;
}
int totalTitleHeight = titleHeight*tableInfo.getMaxLevel();
int totalHeight = topHeight +totalTitleHeight+totalContentHeight;
//計算底部統計行的高
if(tableData.isShowCount()) {
int countHeight = DrawUtils.getTextHeight(config.getCountStyle(), paint)
+ 2 * config.getVerticalPadding();
tableInfo.setCountHeight(countHeight);
totalHeight+=countHeight;
}
return totalHeight;
}複製代碼
好像沒看見咋計算行高的啊?在解析數據時,就開始在計算行高了。那
drawFormat
是什麼鬼?由於每列有些須要顯示圖片,有的須要顯示文字,需求不一樣,因此定義了這個接口,提供每列的格式化。寬高也就能夠獲取到了,把canvas
和paint
都交出來了。有了這個一切均可以實現。
/**
* 設置每行的高度
* 以及計算總數
*
* @param config 配置
* @param lineHeightArray 儲存高度數組
* @param position 位置
*/
private void setRowHeight(TableConfig config, int[] lineHeightArray, int position,T t) {
if(t != null && isAutoCount && countFormat ==null){
if(LetterUtils.isBasicType(t)){
if(LetterUtils.isNumber(this)) {
countFormat = new NumberCountFormat<>();
}else{
countFormat = new DecimalCountFormat<>();
}
}else{
countFormat = new StringCountFormat<>(this);
}
}
if(countFormat != null){
countFormat.count(t);
}
//看這裏 比較行高
int height = drawFormat.measureHeight(this, position, config);
if (height > lineHeightArray[position]) {
lineHeightArray[position] = height;
}
}複製代碼
/**
* 繪製格式化
*/
public interface IDrawFormat<T> {
/**
*測量寬
*/
int measureWidth(Column<T> column, TableConfig config);
/**
*測量高
*/
int measureHeight(Column<T> column,int position, TableConfig config);
//繪製
void draw(Canvas c,Column<T> column,T t,String value,int left,int top,int right,int bottom,int position,TableConfig config);
//繪製背景
boolean drawBackground(Canvas c, CellInfo<T> cellInfo, int left, int top, int right, int bottom, TableConfig config);
}複製代碼
我還定義了序號格式化,
ISequenceFormat
用於格式化序號,ITitleDrawFormat
用於計算列標題高寬以及繪製,ICountFormat
自定義列的統計規則(好比你能夠定義若是是value
是"美女"就加一),IBackgroundFormat
自定義背景和字體。這是整個SmartTable
強大之處。
github地址:github.com/huangyanbin…