Android自动生成表格,丰富配置

前言

写完了android图表,一个朋友说他们公司需要做表格。问我能做吗?我答这有啥不能做。我就开始几个吧唧吧唧写,快写完了,朋友说表格在android体验不好。坑壁啊,最好放在github上。通过这次我对自定义有点体会,就想这篇文章。哈哈。文笔不好,将就的看吧。
github地址:https://github.com/huangyanbin/smartTable

效果

俗话说无图无真相,先上图:

支持图片以及Text drawpadding 四个方向

分页模式

网络数据注解模式

功能

  1. 快速配置自动生成表格;
  2. 自动计算表格宽高;
  3. 表格列标题组合;
  4. 可配置是否固定左序列、顶部序列、第一行、列标题、统计行;
  5. 自动统计,排序(也可自定义统计规则);
  6. 表格图文、序列号、列标题格式化;
  7. 表格各属性背景、文字、网格、padding等配置;
  8. 表格批注;
  9. 表格内容、列标题点击事件;
  10. 缩放模式(待优化)和滚动模式;
  11. 支持注解模式;
  12. 支持数据分页;
  13. 支持每个格子字体大小,颜色,背景设置。

分析

一看就知道需要是自定义View。首先一看表格。表格标题,顶部序号,左侧序号,列标题,统计行,表格内容。分析完了表格属性。但是我们要自动生成表格,表格数据一般都是一个List<Data>,马上就想到了注解。对的,每列对应的Data一个成员变量。开始动手了!

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

我们要用注解来解析数据,所以我们要定义注解。当然,我们也要支持普通模式。我们这里先做注解。

@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地址:https://github.com/huangyanbin/smartTable

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值