Android 强大的表格库—SmartTable使用中遇到的问题解决

最近在开发中遇到要在客户端展示表格的需求,要实现以下几点功能:

  • 支持解析list和二维数组(因为服务端返回的数据有这两种类型)
  • 表格内容多的时候支持上下左右滑动
  • 第一列支持固定(左右滑动的时候第一列不动)
  • 格子宽度高度要支持最小宽高度,最大宽高度配置,宽高度自适应文字大小
  • 表格文字大小、颜色、粗细支持配置
  • 表格文字支持换行
  • 表格每个格子背景颜色支持配置
  • 表格内容除了文字可能还需要自己画一些自定义的view 如斜线,按钮等
  • 表格内容上下左右边距支持配置,表格内容对齐方式支持配置
  • 网格线配置

最终要实现的效果如下

看到这个需求头都大了,而且给的时间也短,自己实现的话感觉太难了,那怎么办,先找找有没有造好的轮子啊!于是乎,各种百度、谷歌、GitHub,最终找到了一个非常强大的库—— SmartTable

相关介绍

这个库再gitHub上已经有3.8k的star, 一看就很靠谱,下面贴下它的功能介绍

  • 快速配置自动生成表格;
  • 自动计算表格宽高;
  • 表格列标题组合;
  • 表格固定左序列、顶部序列、第一行、列标题、统计行;
  • 自动统计,排序(自定义统计规则);
  • 表格图文、序列号、列标题格式化;
  • 表格各组成背景、文字、网格、padding等配置;
  • 表格批注;
  • 表格内容、列标题点击事件;
  • 缩放模式和滚动模式;
  • 注解模式;
  • 内容多行显示;
  • 分页模式;
  • 首尾动态添加数据;
  • 丰富的格式化;
    支持二维数组展示(用于类似日程表,电影选票等);
  • 导入excel(支持颜色,字体,背景,批注,对齐,图片等基本Excel属性);
  • 表格合并单元(支持注解合并,支持自动合并);
  • 支持其他刷新框架SmartRefreshLayout;
  • 可配置表格最小宽度(小于该宽度自动适配);
  • 支持直接List或数组字段转列;
  • 支持Json数据直接转换成表格;
  • 支持表格网格指定行列显示;
  • 支持自动生成表单。

可以说是非常强大了,而且完全符合我的要求啊!
它的介绍和使用大家直接看gitHub就行了,介绍的非常详细,这里就不介绍了,下面讲一讲遇到的问题,以及如何解决的。

遇到的问题

问题1:在RecyclerView中表格高度设置wrap_content会出现高度显示不全的问题
问题2:在ViewPager中使用无法左滑滑到下一个pager(右滑可以)

这两个问题对我来说很致命啊,看了issues也有好多人提出这两个问题,都没有好的解决办法,而且作者两年没更新了。

PS:不想看如何解决问题的,可以直接把文章最后的源码拷贝过去

问题解决

下面只能自己去查看源码解决问题

问题1解决

RecyclerView中设置wrap_content有时候高度显示不全,上下滑动后高度还会变动,这个问题应该是高度计算❌了,那就要查看onMeasure方法,下面是SmartTable中onMeasure方法

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
        requestReMeasure();
    }

阅读代码,发现requestReMeasure方法存在问题

private void requestReMeasure() {
    //不是精准模式 且已经测量了
    if (!isExactly && getMeasuredHeight() != 0 && tableData != null) {
        if (tableData.getTableInfo().getTableRect() != null) {
            int defaultHeight = tableData.getTableInfo().getTableRect().height()
                    + getPaddingTop();
            int defaultWidth = tableData.getTableInfo().getTableRect().width();
            int[] realSize = new int[2];
            getLocationInWindow(realSize);
            DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
            int screenWidth = dm.widthPixels;
            int screenHeight = dm.heightPixels;
            int maxWidth = screenWidth - realSize[0];
            int maxHeight = screenHeight - realSize[1];
            defaultHeight = Math.min(defaultHeight, maxHeight);
            defaultWidth = Math.min(defaultWidth, maxWidth);
            if (this.defaultHeight != defaultHeight
                    || this.defaultWidth != defaultWidth) {
                this.defaultHeight = defaultHeight;
                this.defaultWidth = defaultWidth;
                post(new Runnable() {
                    @Override
                    public void run() {
                        requestLayout();
                    }
                });

            }
        }
    }
}

这里大概解释下代码
首先获取表格需要的宽度和高度

int defaultHeight = tableData.getTableInfo().getTableRect().height()
        + getPaddingTop();
int defaultWidth = tableData.getTableInfo().getTableRect().width();

然后得到表格在屏幕的位置

int[] realSize = new int[2];
getLocationInWindow(realSize);

然后再根据屏幕大小算出表格可用的最大宽度和高度

DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
int screenWidth = dm.widthPixels;
int screenHeight = dm.heightPixels;
int maxWidth = screenWidth - realSize[0];
int maxHeight = screenHeight - realSize[1];

然后和表格需要的高度宽度比较,选一个最小的作为表格高度

 defaultHeight = Math.min(defaultHeight, maxHeight);
 defaultWidth = Math.min(defaultWidth, maxWidth);

很显然这代码没考虑到在RecyclerView中可以滚动的情况,在RecyclerView中我们需要展示表格需要的实际高度,所以defaultHeight = Math.min(defaultHeight, maxHeight);这行代码我们不需要,将这行代码去掉后果然好了!
为了考虑在其他非RecyclerView等可滑动的控件中使用,我给SmartTable加了个是否支持上下滑动的属性

    private boolean canVerticalScroll = true;
    public void setCanVerticalScroll(boolean canVerticalScroll){
        this.canVerticalScroll = canVerticalScroll;
    }

然后requestReMeasure方法修改如下

//如果表格支持竖向滑动,则高度由屏幕位置决定,如果不支持竖向滑动,则默认高度就是表格的高度
if(canVerticalScroll) {
    defaultHeight = Math.min(defaultHeight, maxHeight);
}

RecyclerView等可滑动的控件中我们不需要表格支持竖向滑动,所以我们要调用SmartTable.setCanVerticalScroll(false),那么这个问题就算解决了。

问题2解决

ViewPager中使用无法左滑到下一个pager(右滑可以),这个问题大家应该跟我一样首先想到的就是滑动冲突了呗,这方面就需要了解事件分发机制了,我们看看作者是如何处理的,查看SmartTable的dispatchTouchEvent方法,发现它直接交给matrixHelper处理了

/**
     * 分发事件
     * 在这里会去调用MatrixHelper onDisallowInterceptEvent方法
     * 判断是否阻止parent拦截自己的事件
     *
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        matrixHelper.onDisallowInterceptEvent(this, event);
        return super.dispatchTouchEvent(event);
    }

matrixHelper.onDisallowInterceptEvent方法

 /**
     * 判断是否需要接收触摸事件
     */
    @Override
    public void onDisallowInterceptEvent(View view, MotionEvent event) {

        ViewParent parent = view.getParent();
        if (zoomRect == null || originalRect == null) {
            parent.requestDisallowInterceptTouchEvent(false);
            return;
        }
        switch (event.getAction()&MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                pointMode = 1;
                //ACTION_DOWN的时候,赶紧把事件hold住
                mDownX = event.getX();
                mDownY = event.getY();
                if(originalRect.contains((int)mDownX,(int)mDownY)){ //判断是否落在图表内容区中
                    parent.requestDisallowInterceptTouchEvent(true);
                }else{
                    parent.requestDisallowInterceptTouchEvent(false);
                }

                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                //判断是否是多指操作
                pointMode += 1;
                parent.requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (pointMode > 1) {
                    parent.requestDisallowInterceptTouchEvent(true);
                    return;
                }
                float disX = event.getX() - mDownX;
                float disY = event.getY() - mDownY;
                boolean isDisallowIntercept = true;
                if (Math.abs(disX) > Math.abs(disY)) {
                    if ((disX > 0 && toRectLeft()) || (disX < 0 && toRectRight())) { //向右滑动
                        isDisallowIntercept = false;
                    }
                } else {
                    if ((disY > 0 && toRectTop()) || (disY < 0 && toRectBottom())) {
                        isDisallowIntercept = false;
                    }
                }
                parent.requestDisallowInterceptTouchEvent(isDisallowIntercept);
                break;
            case MotionEvent.ACTION_POINTER_UP:
                pointMode -= 1;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                pointMode = 0;
                parent.requestDisallowInterceptTouchEvent(false);
        }

    }

研究了好久,觉得这代码没毛病啊,符合内部处理滑动冲突逻辑啊。那不是滑动冲突还能是什么问题呢❓❓❓

后来阅读源码发现作者重写了view的canScrollVertically、computeVerticalScrollRange、computeVerticalScrollOffset、computeVerticalScrollExtent、computeHorizontalScrollRange、computeHorizontalScrollOffset、computeHorizontalScrollExtent这几个方法,从方法名上可以看出这几个方法好像和滑动有关,还有一个canScrollHorizontally方法作者没有重写,这几个方法是什么意思呢❓下面是View中canScrollHorizontally的源码

 /**
     * Check if this view can be scrolled horizontally in a certain direction.
     *
     * @param direction Negative to check scrolling left, positive to check scrolling right.
     * @return true if this view can be scrolled in the specified direction, false otherwise.
     */
    public boolean canScrollHorizontally(int direction) {
        final int offset = computeHorizontalScrollOffset();
        final int range = computeHorizontalScrollRange() - computeHorizontalScrollExtent();
        if (range == 0) return false;
        if (direction < 0) {
            return offset > 0;
        } else {
            return offset < range - 1;
        }
    }

翻译一下注释
检查此视图是否可以在某个方向上水平滚动。
参数direction 负数表示向上滚动,正数表示向下滚动。
返回 如果此视图可以按指定方向滚动,则为true;否则为false。

我们可以看到这个方法里调用了其他三个方法
computeHorizontalScrollRange 表格控件水平滚动范围,也就是控件大小吧
computeHorizontalScrollOffset 表示水平方向已经滑动出屏幕的大小
computeHorizontalScrollExtent表示在屏幕中显示的大小

那么这里的代码

 final int range = computeHorizontalScrollRange() - computeHorizontalScrollExtent();

意思就是算出屏幕外的大小

  if (direction < 0) {
            return offset > 0;
        } else {
            return offset < range - 1;
        }

这段代码意思是
如果是向上滚动(手指向下滑动),那么offset > 0则可以继续滚动
如果是向下滚动(手指向上滚动),则要比较offsetrange大小,已经滚动的距离小于在屏幕外的大小则可以继续滚动, range - 1是为了代码容错。
这应该很好理解,竖方向的滑动代码类似,就不解释了。那么我们看看作者对这几个方法实现

@Override
    public boolean canScrollVertically(int direction) {
        if (direction < 0) {
            return matrixHelper.getZoomRect().top != 0;
        } else {
            return matrixHelper.getZoomRect().bottom > matrixHelper.getOriginalRect().bottom;
        }

    }

这里的意思是如果是向上滚动(手指向下滑动),那看 matrixHelper.getZoomRect().top是否是0就行了,向下滚动则要根据getZoomRect().bottom和getOriginalRect().bottom坐标比较来判断是否滚到底了。这里重写了canScrollVertically而且没用到另外三个方法,那么这三个方法应该是没用的

 @Override
    public int computeVerticalScrollRange() {

        final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();
        int scrollRange = matrixHelper.getZoomRect().bottom;
        final int scrollY = -matrixHelper.getZoomRect(). bottom;
        final int overScrollBottom = Math.max(0, scrollRange - contentHeight);
        if (scrollY < 0) {
            scrollRange -= scrollY;
        } else if (scrollY > overScrollBottom) {
            scrollRange += scrollY - overScrollBottom;
        }

        return scrollRange;
    }

    @Override
    public int computeVerticalScrollOffset() {

        return Math.max(0, -matrixHelper.getZoomRect().left);
    }

    @Override
    public int computeVerticalScrollExtent() {

        return super.computeVerticalScrollExtent();

    }

而且这里的computeVerticalScrollOffset方法里return Math.max(0, -matrixHelper.getZoomRect().left);一看就是错误的啊,应该是top而不是left,因为你是计算竖方向已经滑动的距离啊!
我们再看水平滚动的三个方法

    @Override
    public int computeHorizontalScrollRange() {
        final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int scrollRange = matrixHelper.getZoomRect().right;
        final int scrollX = -matrixHelper.getZoomRect().right;
        final int overScrollRight = Math.max(0, scrollRange - contentWidth);
        if (scrollX < 0) {
            scrollRange -= scrollX;
        } else if (scrollX > overScrollRight) {
            scrollRange += scrollX - overScrollRight;
        }
        return scrollRange;
    }

    @Override
    public int computeHorizontalScrollOffset() {
        return Math.max(0, -matrixHelper.getZoomRect().top);
    }


    @Override
    public int computeHorizontalScrollExtent() {
        return super.computeHorizontalScrollExtent();
    }

这里没有重写canScrollHorizontally方法,那么这三个方法都会被调用。
同样这里的computeHorizontalScrollOffset里应该是left不是top,作者应该搞反了。
改了之后发现还是没法滑动,computeHorizontalScrollExtent方法返回super我们不用看,那么应该就是computeHorizontalScrollRange方法还是存在问题的。
经过debug,我发先在ViewPager中向左滑动时,View的canScrollHorizontally方法一直返回true,也就是SmartTable是可以滚动的,那么肯定不会触发ViewPager的滚动

问题就在这里了!!!

computeHorizontalScrollRange这个方法返回的应该就是控件宽度吧,因为View的源码中就是返回getWidth()的,那这里应该是返回是表格的宽度呀,可是经过测试,右滑时,这里返回的等于控件的大小,左滑时要远大于控件大小。

尝试解决

办法1:
仿照canScrollVertically方法重写canScrollHorizontally方法

@Override
   public boolean canScrollHorizontally(int direction) {
       if (direction < 0) {
           return matrixHelper.getZoomRect().left != 0;
       } else {
           return matrixHelper.getZoomRect().right > matrixHelper.getOriginalRect().right;
       }

   }

办法2:
修改computeHorizontalScrollRange方法,返回表格的宽度

@Override
    public int computeHorizontalScrollRange() {
       return  matrixHelper.getZoomRect().width();
    }

这两个方法尝试了都是可以的
我相信作者没有使用我那两个办法,肯定是有他的原因的,但是我还没看出原因是啥。
那么作者的代码到底问题在哪呢,再看下代码

    @Override
    public int computeHorizontalScrollRange() {
        final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int scrollRange = matrixHelper.getZoomRect().right;
        final int scrollX = -matrixHelper.getZoomRect().right;
        final int overScrollRight = Math.max(0, scrollRange - contentWidth);
        if (scrollX < 0) {
            scrollRange -= scrollX;
        } else if (scrollX > overScrollRight) {
            scrollRange += scrollX - overScrollRight;
        }
        return scrollRange;
    }

我发现scrollX应该代表着水平方向的滑动距离,那这里应该不对啊,应该是-matrixHelper.getZoomRect().left才对吧!改完之后,发现真的解决了!虽然我还不明白作者为什么用这种方式计算HorizontalScrollRange。。。

总结

最终解决了这两个问题,也从中学到了一些东西,目前使用没发现其他问题,如果你也遇到了这两个问题,可以参考这两篇文章。

源码

最后附上修改后的SmartTable源码

package com.bin.david.form.core;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;

import com.bin.david.form.component.IComponent;
import com.bin.david.form.component.ITableTitle;
import com.bin.david.form.component.TableProvider;
import com.bin.david.form.component.TableTitle;
import com.bin.david.form.component.XSequence;
import com.bin.david.form.component.YSequence;
import com.bin.david.form.data.column.Column;
import com.bin.david.form.data.table.PageTableData;
import com.bin.david.form.data.table.TableData;
import com.bin.david.form.data.TableInfo;
import com.bin.david.form.data.format.selected.ISelectFormat;
import com.bin.david.form.data.style.FontStyle;
import com.bin.david.form.listener.OnColumnClickListener;
import com.bin.david.form.listener.OnTableChangeListener;
import com.bin.david.form.matrix.MatrixHelper;
import com.bin.david.form.utils.DensityUtils;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;


/**
 * Created by huang on 2017/10/30.
 * 表格
 */

public class SmartTable<T> extends View implements OnTableChangeListener {

    private XSequence<T> xAxis;
    private YSequence<T> yAxis;
    private ITableTitle tableTitle;
    private TableProvider<T> provider;
    private Rect showRect;
    private Rect tableRect;
    private TableConfig config;
    private TableParser<T> parser;
    private TableData<T> tableData;
    private int defaultHeight = 300;
    private int defaultWidth = 300;
    private TableMeasurer<T> measurer;
    private AnnotationParser<T> annotationParser;
    protected Paint paint;
    private MatrixHelper matrixHelper;
    private boolean isExactly = true; //是否是测量精准模式
    private AtomicBoolean isNotifying = new AtomicBoolean(false); //是否正在更新数据
    private boolean isYSequenceRight;
    private boolean canVerticalScroll = true;


    public SmartTable(Context context) {
        super(context);
        init();
    }

    public SmartTable(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SmartTable(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        FontStyle.setDefaultTextSpSize(getContext(), 13);
        config = new TableConfig();
        config.dp10 = DensityUtils.dp2px(getContext(), 10);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        showRect = new Rect();
        tableRect = new Rect();
        xAxis = new XSequence<>();
        yAxis = new YSequence<>();
        parser = new TableParser<>();
        provider = new TableProvider<>();
        config.setPaint(paint);
        measurer = new TableMeasurer<>();
        tableTitle = new TableTitle();
        tableTitle.setDirection(IComponent.TOP);
        matrixHelper = new MatrixHelper(getContext());
        matrixHelper.setOnTableChangeListener(this);
        matrixHelper.register(provider);
        matrixHelper.setOnInterceptListener(provider.getOperation());

    }

    /**
     * 绘制
     * 首先通过计算的table大小,计算table title大小
     * 再通过 matrixHelper getZoomProviderRect计算实现缩放和位移的Rect
     * 再绘制背景
     * 绘制XY序号列
     * 最后绘制内容
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        if (!isNotifying.get()) {
            setScrollY(0);
            showRect.set(getPaddingLeft(), getPaddingTop(),
                    getWidth() - getPaddingRight(),
                    getHeight() - getPaddingBottom());
            if (tableData != null) {
                Rect rect = tableData.getTableInfo().getTableRect();
                if (rect != null) {
                    if (config.isShowTableTitle()) {
                        measurer.measureTableTitle(tableData, tableTitle, showRect);
                    }
                    tableRect.set(rect);
                    Rect scaleRect = matrixHelper.getZoomProviderRect(showRect, tableRect,
                            tableData.getTableInfo());
                    if (config.isShowTableTitle()) {
                        tableTitle.onMeasure(scaleRect, showRect, config);
                        tableTitle.onDraw(canvas, showRect, tableData.getTableName(), config);
                    }
                    drawGridBackground(canvas, showRect, scaleRect);
                    if (config.isShowYSequence()) {
                        yAxis.onMeasure(scaleRect, showRect, config);
                        if (isYSequenceRight) {
                            canvas.save();
                            canvas.translate(showRect.width(), 0);
                            yAxis.onDraw(canvas, showRect, tableData, config);
                            canvas.restore();
                        } else {
                            yAxis.onDraw(canvas, showRect, tableData, config);
                        }
                    }
                    if (config.isShowXSequence()) {
                        xAxis.onMeasure(scaleRect, showRect, config);
                        xAxis.onDraw(canvas, showRect, tableData, config);
                    }
                    if (isYSequenceRight) {
                        canvas.save();
                        canvas.translate(-yAxis.getWidth(), 0);
                        provider.onDraw(canvas, scaleRect, showRect, tableData, config);
                        canvas.restore();
                    } else {
                        provider.onDraw(canvas, scaleRect, showRect, tableData, config);
                    }
                }
            }
        }
    }

    /**
     * 绘制表格边框背景
     *
     * @param canvas
     */
    private void drawGridBackground(Canvas canvas, Rect showRect, Rect scaleRect) {
        config.getContentGridStyle().fillPaint(paint);
        if (config.getTableGridFormat() != null) {
            config.getTableGridFormat().drawTableBorderGrid(canvas, Math.max(showRect.left, scaleRect.left),
                    Math.max(showRect.top, scaleRect.top),
                    Math.min(showRect.right, scaleRect.right),
                    Math.min(scaleRect.bottom, showRect.bottom), paint);
        }
    }

    /**
     * 获取表格配置
     * 可以使用TableConfig进行样式的配置,包括颜色,是否固定,开启统计行等
     *
     * @return 表格配置
     */
    public TableConfig getConfig() {
        return config;
    }

    /**
     * 设置解析数据
     *
     * @param data 表格数据
     */
    public PageTableData<T> setData(List<T> data) {
        if (annotationParser == null) {
            annotationParser = new AnnotationParser<>(config.dp10);
        }
        PageTableData<T> tableData = annotationParser.parse(data);
        if (tableData != null) {
            setTableData(tableData);
        }
        return tableData;
    }


    /**
     * 设置表格数据
     *
     * @param tableData
     */
    public void setTableData(TableData<T> tableData) {
        if (tableData != null) {
            this.tableData = tableData;
            notifyDataChanged();
        }
    }

    public ITableTitle getTableTitle() {
        return tableTitle;
    }


    /**
     * 通知更新
     */
    public void notifyDataChanged() {

        if (tableData != null) {
            config.setPaint(paint);
            //开启线程
            isNotifying.set(true);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //long start = System.currentTimeMillis();
                    parser.parse(tableData);
                    if(measurer == null) {
                        return;
                    }
                    TableInfo info = measurer.measure(tableData, config);
                    xAxis.setHeight(info.getTopHeight());
                    yAxis.setWidth(info.getyAxisWidth());
                    requestReMeasure();
                    postInvalidate();
                    isNotifying.set(false);
                    //long end = System.currentTimeMillis();
                    //Log.e("smartTable","notifyDataChanged timeMillis="+(end-start));
                }

            }).start();

        }
    }

    /**
     * 添加数据
     * 通过这个方法可以实现动态添加数据,参数isFoot可以实现首尾添加
     *
     * @param t      新增数据
     * @param isFoot 是否在尾部添加
     */
    public void addData(final List<T> t, final boolean isFoot) {
        if (t != null && t.size() > 0) {
            isNotifying.set(true);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    parser.addData(tableData, t, isFoot);
                    measurer.measure(tableData, config);
                    requestReMeasure();
                    postInvalidate();
                    isNotifying.set(false);

                }
            }).start();
        }
    }


    /**
     * 通知重绘
     * 增加锁机制,避免闪屏和数据更新异常
     */
    @Override
    public void invalidate() {
        if (!isNotifying.get()) {
            super.invalidate();
        }

    }

    /**
     * 通知重新测量大小
     */
    private void requestReMeasure() {
        //不是精准模式 且已经测量了
        if (!isExactly && getMeasuredHeight() != 0 && tableData != null) {
            if (tableData.getTableInfo().getTableRect() != null) {
                int defaultHeight = tableData.getTableInfo().getTableRect().height()
                        + getPaddingTop();
                int defaultWidth = tableData.getTableInfo().getTableRect().width();
                int[] realSize = new int[2];
                getLocationInWindow(realSize);
                DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
                int screenWidth = dm.widthPixels;
                int screenHeight = dm.heightPixels;
                int maxWidth = screenWidth - realSize[0];
                int maxHeight = screenHeight - realSize[1];
                //如果可以竖向滑动,则高度由屏幕位置决定,如果不可以竖向滑动,则默认高度就是表格的高度
                if(canVerticalScroll) {
                    defaultHeight = Math.min(defaultHeight, maxHeight);
                }
                defaultWidth = Math.min(defaultWidth, maxWidth);
                //Log.e("SmartTable","old defaultHeight"+this.defaultHeight+"defaultWidth"+this.defaultWidth);
                if (this.defaultHeight != defaultHeight
                        || this.defaultWidth != defaultWidth) {
                    this.defaultHeight = defaultHeight;
                    this.defaultWidth = defaultWidth;
                    // Log.e("SmartTable","new defaultHeight"+defaultHeight+"defaultWidth"+defaultWidth);
                    post(new Runnable() {
                        @Override
                        public void run() {
                            requestLayout();
                        }
                    });

                }
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
        requestReMeasure();
    }

    /**
     * 计算组件宽度
     */

    private int measureWidth(int widthMeasureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY) {//精确模式
            result = specSize;
        } else {
            isExactly = false;
            result = defaultWidth;//最大尺寸模式,getDefaultWidth方法需要我们根据控件实际需要自己实现
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * 计算组件高度
     */

    private int measureHeight(int measureSpec) {

        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            isExactly = false;
            result = defaultHeight;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * 将触摸事件交给Iouch处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return matrixHelper.handlerTouchEvent(event);
    }

    /**
     * 分发事件
     * 在这里会去调用MatrixHelper onDisallowInterceptEvent方法
     * 判断是否阻止parent拦截自己的事件
     *
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        matrixHelper.onDisallowInterceptEvent(this, event);
        return super.dispatchTouchEvent(event);
    }


    /**
     * 表格移动缩放改变回调
     *
     * @param scale      缩放值
     * @param translateX X位移值
     * @param translateY Y位移值
     */
    @Override
    public void onTableChanged(float scale, float translateX, float translateY) {
        if (tableData != null) {
            config.setZoom(scale);
            tableData.getTableInfo().setZoom(scale);
            invalidate();
        }
    }

    /**
     * 获取列点击事件
     */
    public OnColumnClickListener getOnColumnClickListener() {
        return provider.getOnColumnClickListener();
    }

    /**
     * 设置列点击事件,实现对列的监听
     *
     * @param onColumnClickListener 列点击事件
     */
    public void setOnColumnClickListener(OnColumnClickListener onColumnClickListener) {
        this.provider.setOnColumnClickListener(onColumnClickListener);
    }

    /**
     * 列排序
     * 你可以调用这个方法,对所有数据进行排序,排序根据设置的column排序
     *
     * @param column    列
     * @param isReverse 是否反序
     */
    public void setSortColumn(Column column, boolean isReverse) {
        if (tableData != null && column != null) {
            column.setReverseSort(isReverse);
            tableData.setSortColumn(column);
            setTableData(tableData);
        }
    }

    public Rect getShowRect() {
        return showRect;
    }


    /**
     * 获取绘制表格内容者
     *
     * @return 绘制表格内容者
     */
    public TableProvider<T> getProvider() {
        return provider;
    }


    /**
     * 获取表格数据
     * TableData是解析数据之后对数据的封装对象,包含table column,rect等信息
     *
     * @return 表格数据
     */
    public TableData<T> getTableData() {
        return tableData;
    }


    /**
     * 开启缩放
     *
     * @param zoom 是否缩放
     */
    public void setZoom(boolean zoom) {

        matrixHelper.setCanZoom(zoom);
        invalidate();

    }

    /**
     * 开启缩放设置缩放值
     *
     * @param zoom    是否缩放
     * @param maxZoom 最大缩放值
     * @param minZoom 最小缩放值
     */
    public void setZoom(boolean zoom, float maxZoom, float minZoom) {

        matrixHelper.setCanZoom(zoom);
        matrixHelper.setMinZoom(minZoom);
        matrixHelper.setMaxZoom(maxZoom);
        invalidate();

    }


    /**
     * 获取缩放移动辅助类
     * 如果你需要更多的移动功能,可以使用它
     *
     * @return 缩放移动辅助类
     */
    public MatrixHelper getMatrixHelper() {
        return matrixHelper;
    }


    /**
     * 设置选中格子格式化
     *
     * @param selectFormat 选中格子格式化
     */
    public void setSelectFormat(ISelectFormat selectFormat) {
        this.provider.setSelectFormat(selectFormat);
    }


    @Override
    public int computeHorizontalScrollRange() {
        final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int scrollRange = matrixHelper.getZoomRect().right;
        final int scrollX = -matrixHelper.getZoomRect().left;
        final int overScrollRight = Math.max(0, scrollRange - contentWidth);
        if (scrollX < 0) {
            scrollRange -= scrollX;
        } else if (scrollX > overScrollRight) {
            scrollRange += scrollX - overScrollRight;
        }
        return scrollRange;
    }

    @Override
    public boolean canScrollVertically(int direction) {
        if (direction < 0) {
            return matrixHelper.getZoomRect().top != 0;
        } else {
            return matrixHelper.getZoomRect().bottom > matrixHelper.getOriginalRect().bottom;
        }

    }

    @Override
    public int computeHorizontalScrollOffset() {
        return Math.max(0, -matrixHelper.getZoomRect().left);
    }


    @Override
    public int computeHorizontalScrollExtent() {
        return super.computeHorizontalScrollExtent();
    }

    @Override
    public int computeVerticalScrollRange() {

        final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();
        int scrollRange = matrixHelper.getZoomRect().bottom;
        final int scrollY = -matrixHelper.getZoomRect().top;
        final int overScrollBottom = Math.max(0, scrollRange - contentHeight);
        if (scrollY < 0) {
            scrollRange -= scrollY;
        } else if (scrollY > overScrollBottom) {
            scrollRange += scrollY - overScrollBottom;
        }

        return scrollRange;
    }

    @Override
    public int computeVerticalScrollOffset() {

        return Math.max(0, -matrixHelper.getZoomRect().top);
    }

    @Override
    public int computeVerticalScrollExtent() {

        return super.computeVerticalScrollExtent();

    }

    public XSequence<T> getXSequence() {
        return xAxis;
    }

    public YSequence getYSequence() {
        return yAxis;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (tableData != null && getContext() != null) {
            if (((Activity) getContext()).isFinishing()) {
                release();
            }
        }
    }

    /**
     * 可以在Activity onDestroy释放
     */
    private void release() {
        if (matrixHelper != null){
            matrixHelper.unRegisterAll();
        }
        annotationParser = null;
        measurer = null;
        provider = null;
        matrixHelper = null;
        provider = null;
        if (tableData != null) {
            tableData.clear();
            tableData = null;
        }
        xAxis = null;
        yAxis = null;
    }

    public boolean isYSequenceRight() {
        return isYSequenceRight;
    }

    public void setYSequenceRight(boolean YSequenceRight) {
        isYSequenceRight = YSequenceRight;
    }

    public void setCanVerticalScroll(boolean canVerticalScroll){
        this.canVerticalScroll = canVerticalScroll;
    }
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342