Android自動生成表格,豐富配置

前言

寫完了android圖表,一個朋友說他們公司須要作表格。問我能作嗎?我答這有啥不能作。我就開始幾個吧唧吧唧寫,快寫完了,朋友說表格在android體驗很差。坑壁啊,最好放在github上。經過此次我對自定義有點體會,就想這篇文章。哈哈。文筆很差,將就的看吧。
github地址:github.com/huangyanbin…android

效果

俗話說無圖無真相,先上圖:git

支持圖片以及Text drawpadding 四個方向
github

分頁模式
canvas

網絡數據註解模式數組

功能

  1. 快速配置自動生成表格;
  2. 自動計算表格寬高;
  3. 表格列標題組合;
  4. 可配置是否固定左序列、頂部序列、第一行、列標題、統計行;
  5. 自動統計,排序(也可自定義統計規則);
  6. 表格圖文、序列號、列標題格式化;
  7. 表格各屬性背景、文字、網格、padding等配置;
  8. 表格批註;
  9. 表格內容、列標題點擊事件;
  10. 縮放模式(待優化)和滾動模式;
  11. 支持註解模式;
  12. 支持數據分頁;
  13. 支持每一個格子字體大小,顏色,背景設置。

分析

一看就知道須要是自定義View。首先一看錶格。表格標題,頂部序號,左側序號,列標題,統計行,表格內容。分析完了表格屬性。可是咱們要自動生成表格,表格數據通常都是一個List<Data>,立刻就想到了註解。對的,每列對應的Data一個成員變量。開始動手了!bash

frist

首先咱們自定義一個View SmartTable,裏面分別有表格標題,頂部序號,左側序號,表格內容。等等,咋沒有統計行,統計行我放在表格內容裏面了。裏面泛型T是啥鬼,答曰表格數據類型。網絡

public class SmartTable<T> extends View{
    //標題
    private ITableTitle tableTitle;
    //頂部序號
    private XSequence<T> xAxis;
    //左側序號
    private YSequence<T> yAxis;
    // 表格內容
    private TableProvider<T> provider;複製代碼

two

咱們要用註解來解析數據,因此咱們要定義註解。固然,咱們也要支持普通模式。咱們這裏先作註解。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是打開統計,pageSizecurrentPage是用於分頁的。放在類名上。字體

再就是須要顯示的列數據註解。優化

@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屬性,Fathername.咱們可能須要顯示father.name。咱們就能夠用ColumnType.child就會進去這個對象裏面遞歸的查詢是否有smartColumn註解。
autoCount 是否開啓統計,由於大部分列是不須要統計的。
parent:表格常常幾列爲一大列,parent就是指定父列名稱。

咱們開始來解析註解,首先解析SmartTable註解,將註解屬性放入TableData,而後獲取ClassFields,迭代去獲取列信息:

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是什麼鬼?由於每列有些須要顯示圖片,有的須要顯示文字,需求不一樣,因此定義了這個接口,提供每列的格式化。寬高也就能夠獲取到了,把canvaspaint都交出來了。有了這個一切均可以實現。

/**
     * 設置每行的高度
     * 以及計算總數
     *
     * @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…

相關文章
相關標籤/搜索