Android 強大的表格庫—SmartTable使用中遇到的問題解決

最近在開發(fā)中遇到要在客戶端展示表格的需求赏胚,要實現(xiàn)以下幾點功能:

  • 支持解析list和二維數(shù)組(因為服務(wù)端返回的數(shù)據(jù)有這兩種類型)
  • 表格內(nèi)容多的時候支持上下左右滑動
  • 第一列支持固定(左右滑動的時候第一列不動)
  • 格子寬度高度要支持最小寬高度凛澎,最大寬高度配置,寬高度自適應(yīng)文字大小
  • 表格文字大小、顏色、粗細支持配置
  • 表格文字支持換行
  • 表格每個格子背景顏色支持配置
  • 表格內(nèi)容除了文字可能還需要自己畫一些自定義的view 如斜線,按鈕等
  • 表格內(nèi)容上下左右邊距支持配置创夜,表格內(nèi)容對齊方式支持配置
  • 網(wǎng)格線配置

最終要實現(xiàn)的效果如下

看到這個需求頭都大了,而且給的時間也短仙逻,自己實現(xiàn)的話感覺太難了挥下,那怎么辦揍魂,先找找有沒有造好的輪子啊棚瘟!于是乎,各種百度喜最、谷歌偎蘸、GitHub,最終找到了一個非常強大的庫—— SmartTable

相關(guān)介紹

這個庫再gitHub上已經(jīng)有3.8k的star瞬内, 一看就很靠譜迷雪,下面貼下它的功能介紹

  • 快速配置自動生成表格;
  • 自動計算表格寬高虫蝶;
  • 表格列標題組合章咧;
  • 表格固定左序列、頂部序列能真、第一行赁严、列標題、統(tǒng)計行粉铐;
  • 自動統(tǒng)計疼约,排序(自定義統(tǒng)計規(guī)則);
  • 表格圖文蝙泼、序列號程剥、列標題格式化;
  • 表格各組成背景汤踏、文字织鲸、網(wǎng)格、padding等配置溪胶;
  • 表格批注搂擦;
  • 表格內(nèi)容、列標題點擊事件载荔;
  • 縮放模式和滾動模式盾饮;
  • 注解模式;
  • 內(nèi)容多行顯示懒熙;
  • 分頁模式丘损;
  • 首尾動態(tài)添加數(shù)據(jù);
  • 豐富的格式化;
    支持二維數(shù)組展示(用于類似日程表,電影選票等)工扎;
  • 導(dǎo)入excel(支持顏色徘钥,字體,背景肢娘,批注呈础,對齊舆驶,圖片等基本Excel屬性);
  • 表格合并單元(支持注解合并而钞,支持自動合并)沙廉;
  • 支持其他刷新框架SmartRefreshLayout;
  • 可配置表格最小寬度(小于該寬度自動適配)臼节;
  • 支持直接List或數(shù)組字段轉(zhuǎn)列撬陵;
  • 支持Json數(shù)據(jù)直接轉(zhuǎn)換成表格;
  • 支持表格網(wǎng)格指定行列顯示网缝;
  • 支持自動生成表單巨税。

可以說是非常強大了,而且完全符合我的要求胺垭草添!
它的介紹和使用大家直接看gitHub就行了,介紹的非常詳細扼仲,這里就不介紹了驳棱,下面講一講遇到的問題焰盗,以及如何解決的系洛。

遇到的問題

問題1:在RecyclerView中表格高度設(shè)置wrap_content會出現(xiàn)高度顯示不全的問題
問題2:在ViewPager中使用無法左滑滑到下一個pager(右滑可以)

這兩個問題對我來說很致命啊碎绎,看了issues也有好多人提出這兩個問題,都沒有好的解決辦法阅畴,而且作者兩年沒更新了倡怎。

PS:不想看如何解決問題的,可以直接把文章最后的源碼拷貝過去

問題解決

下面只能自己去查看源碼解決問題

問題1解決

RecyclerView中設(shè)置wrap_content有時候高度顯示不全贱枣,上下滑動后高度還會變動监署,這個問題應(yīng)該是高度計算?了,那就要查看onMeasure方法纽哥,下面是SmartTable中onMeasure方法

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

閱讀代碼钠乏,發(fā)現(xiàn)requestReMeasure方法存在問題

private void requestReMeasure() {
    //不是精準模式 且已經(jīng)測量了
    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);

然后再根據(jù)屏幕大小算出表格可用的最大寬度和高度

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等可滑動的控件中我們不需要表格支持豎向滑動锅必,所以我們要調(diào)用SmartTable.setCanVerticalScroll(false),那么這個問題就算解決了惕艳。

問題2解決

ViewPager中使用無法左滑到下一個pager(右滑可以)搞隐,這個問題大家應(yīng)該跟我一樣首先想到的就是滑動沖突了唄驹愚,這方面就需要了解事件分發(fā)機制了,我們看看作者是如何處理的劣纲,查看SmartTable的dispatchTouchEvent方法逢捺,發(fā)現(xiàn)它直接交給matrixHelper處理了

/**
     * 分發(fā)事件
     * 在這里會去調(diào)用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)){ //判斷是否落在圖表內(nèi)容區(qū)中
                    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);
        }

    }

研究了好久味廊,覺得這代碼沒毛病啊蒸甜,符合內(nèi)部處理滑動沖突邏輯啊。那不是滑動沖突還能是什么問題呢???

后來閱讀源碼發(fā)現(xiàn)作者重寫了view的canScrollVertically余佛、computeVerticalScrollRange、computeVerticalScrollOffset窍荧、computeVerticalScrollExtent辉巡、computeHorizontalScrollRange、computeHorizontalScrollOffset蕊退、computeHorizontalScrollExtent這幾個方法郊楣,從方法名上可以看出這幾個方法好像和滑動有關(guān),還有一個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;
        }
    }

翻譯一下注釋
檢查此視圖是否可以在某個方向上水平滾動净蚤。
參數(shù)direction 負數(shù)表示向上滾動,正數(shù)表示向下滾動输硝。
返回 如果此視圖可以按指定方向滾動今瀑,則為true;否則為false点把。

我們可以看到這個方法里調(diào)用了其他三個方法
computeHorizontalScrollRange 表格控件水平滾動范圍橘荠,也就是控件大小吧
computeHorizontalScrollOffset 表示水平方向已經(jīng)滑動出屏幕的大小
computeHorizontalScrollExtent表示在屏幕中顯示的大小

那么這里的代碼

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

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

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

這段代碼意思是
如果是向上滾動(手指向下滑動),那么offset > 0則可以繼續(xù)滾動
如果是向下滾動(手指向上滾動)郎逃,則要比較offsetrange大小哥童,已經(jīng)滾動的距離小于在屏幕外的大小則可以繼續(xù)滾動, range - 1是為了代碼容錯褒翰。
這應(yīng)該很好理解贮懈,豎方向的滑動代碼類似,就不解釋了优训。那么我們看看作者對這幾個方法實現(xiàn)

@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就行了,向下滾動則要根據(jù)getZoomRect().bottom和getOriginalRect().bottom坐標比較來判斷是否滾到底了型宙。這里重寫了canScrollVertically而且沒用到另外三個方法撬呢,那么這三個方法應(yīng)該是沒用的

 @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);一看就是錯誤的啊,應(yīng)該是top而不是left妆兑,因為你是計算豎方向已經(jīng)滑動的距離盎昀埂毛仪!
我們再看水平滾動的三個方法

    @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方法,那么這三個方法都會被調(diào)用芯勘。
同樣這里的computeHorizontalScrollOffset里應(yīng)該是left不是top箱靴,作者應(yīng)該搞反了。
改了之后發(fā)現(xiàn)還是沒法滑動荷愕,computeHorizontalScrollExtent方法返回super我們不用看衡怀,那么應(yīng)該就是computeHorizontalScrollRange方法還是存在問題的。
經(jīng)過debug安疗,我發(fā)先在ViewPager中向左滑動時抛杨,View的canScrollHorizontally方法一直返回true,也就是SmartTable是可以滾動的,那么肯定不會觸發(fā)ViewPager的滾動荐类。

問題就在這里了2老帧!玉罐!

computeHorizontalScrollRange這個方法返回的應(yīng)該就是控件寬度吧屈嗤,因為View的源碼中就是返回getWidth()的,那這里應(yīng)該是返回是表格的寬度呀吊输,可是經(jīng)過測試饶号,右滑時,這里返回的等于控件的大小季蚂,左滑時要遠大于控件大小茫船。

嘗試解決

辦法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;
    }

我發(fā)現(xiàn)scrollX應(yīng)該代表著水平方向的滑動距離濒生,那這里應(yīng)該不對啊,應(yīng)該是-matrixHelper.getZoomRect().left才對吧幔欧!改完之后罪治,發(fā)現(xiàn)真的解決了!雖然我還不明白作者為什么用這種方式計算HorizontalScrollRange礁蔗。觉义。。

總結(jié)

最終解決了這兩個問題浴井,也從中學(xué)到了一些東西晒骇,目前使用沒發(fā)現(xiàn)其他問題,如果你也遇到了這兩個問題,可以參考這兩篇文章洪囤。

源碼

最后附上修改后的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); //是否正在更新數(shù)據(jù)
    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計算實現(xiàn)縮放和位移的Rect
     * 再繪制背景
     * 繪制XY序號列
     * 最后繪制內(nèi)容
     *
     * @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進行樣式的配置,包括顏色瘤缩,是否固定喇完,開啟統(tǒng)計行等
     *
     * @return 表格配置
     */
    public TableConfig getConfig() {
        return config;
    }

    /**
     * 設(shè)置解析數(shù)據(jù)
     *
     * @param data 表格數(shù)據(jù)
     */
    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;
    }


    /**
     * 設(shè)置表格數(shù)據(jù)
     *
     * @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();

        }
    }

    /**
     * 添加數(shù)據(jù)
     * 通過這個方法可以實現(xiàn)動態(tài)添加數(shù)據(jù),參數(shù)isFoot可以實現(xiàn)首尾添加
     *
     * @param t      新增數(shù)據(jù)
     * @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();
        }
    }


    /**
     * 通知重繪
     * 增加鎖機制剥啤,避免閃屏和數(shù)據(jù)更新異常
     */
    @Override
    public void invalidate() {
        if (!isNotifying.get()) {
            super.invalidate();
        }

    }

    /**
     * 通知重新測量大小
     */
    private void requestReMeasure() {
        //不是精準模式 且已經(jīng)測量了
        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方法需要我們根據(jù)控件實際需要自己實現(xiàn)
            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);
    }

    /**
     * 分發(fā)事件
     * 在這里會去調(diào)用MatrixHelper onDisallowInterceptEvent方法
     * 判斷是否阻止parent攔截自己的事件
     *
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        matrixHelper.onDisallowInterceptEvent(this, event);
        return super.dispatchTouchEvent(event);
    }


    /**
     * 表格移動縮放改變回調(diào)
     *
     * @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();
    }

    /**
     * 設(shè)置列點擊事件,實現(xiàn)對列的監(jiān)聽
     *
     * @param onColumnClickListener 列點擊事件
     */
    public void setOnColumnClickListener(OnColumnClickListener onColumnClickListener) {
        this.provider.setOnColumnClickListener(onColumnClickListener);
    }

    /**
     * 列排序
     * 你可以調(diào)用這個方法,對所有數(shù)據(jù)進行排序牺丙,排序根據(jù)設(shè)置的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;
    }


    /**
     * 獲取繪制表格內(nèi)容者
     *
     * @return 繪制表格內(nèi)容者
     */
    public TableProvider<T> getProvider() {
        return provider;
    }


    /**
     * 獲取表格數(shù)據(jù)
     * TableData是解析數(shù)據(jù)之后對數(shù)據(jù)的封裝對象坏逢,包含table column,rect等信息
     *
     * @return 表格數(shù)據(jù)
     */
    public TableData<T> getTableData() {
        return tableData;
    }


    /**
     * 開啟縮放
     *
     * @param zoom 是否縮放
     */
    public void setZoom(boolean zoom) {

        matrixHelper.setCanZoom(zoom);
        invalidate();

    }

    /**
     * 開啟縮放設(shè)置縮放值
     *
     * @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;
    }


    /**
     * 設(shè)置選中格子格式化
     *
     * @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;
    }
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赘被,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肖揣,更是在濱河造成了極大的恐慌民假,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件龙优,死亡現(xiàn)場離奇詭異羊异,居然都是意外死亡,警方通過查閱死者的電腦和手機彤断,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門野舶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宰衙,你說我怎么就攤上這事平道。” “怎么了供炼?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵一屋,是天一觀的道長。 經(jīng)常有香客問我袋哼,道長冀墨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任涛贯,我火速辦了婚禮诽嘉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己虫腋,他們只是感情好骄酗,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岔乔,像睡著了一般酥筝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雏门,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天嘿歌,我揣著相機與錄音,去河邊找鬼茁影。 笑死宙帝,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的募闲。 我是一名探鬼主播步脓,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼浩螺!你這毒婦竟也來了靴患?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤要出,失蹤者是張志新(化名)和其女友劉穎鸳君,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體患蹂,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡或颊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了传于。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囱挑。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沼溜,靈堂內(nèi)的尸體忽然破棺而出平挑,到底是詐尸還是另有隱情,我是刑警寧澤盛末,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布弹惦,位于F島的核電站,受9級特大地震影響悄但,放射性物質(zhì)發(fā)生泄漏棠隐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一檐嚣、第九天 我趴在偏房一處隱蔽的房頂上張望助泽。 院中可真熱鬧啰扛,春花似錦、人聲如沸嗡贺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诫睬。三九已至煞茫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摄凡,已是汗流浹背续徽。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留亲澡,地道東北人钦扭。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像床绪,于是被迫代替她去往敵國和親客情。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345