最近在开发中遇到要在客户端展示表格的需求,要实现以下几点功能:
- 支持解析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则可以继续滚动
如果是向下滚动(手指向上滚动),则要比较offset
和range
大小,已经滚动的距离小于在屏幕外的大小则可以继续滚动, 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;
}
}