翻翻git之---可用作课程表/排班表的自定义table库ScrollTableView
转载请注明出处:王亟亟的大牛之路
最近一直在写混合开发的东西,是时候温故下native的了。
一年多之前领导提了一个双性滚动+快点击的”TableView”那时候自己整了2 3天没整出来,本来想今天”圆梦”,但是发现轮子已经有了,但是少了一些小功能和足够多的解释,那就把这个轮子fork下来自己改!(我们不生产代码,我们只是代码的搬运工)
源码地址:https://github.com/ddwhan0123/ScrollTableView
按照习惯,安利下:https://github.com/ddwhan0123/Useful-Open-Source-Android (今天把search view也划分了出去)
效果图:
可以滚动,可以点击,基本满足了之前的要求
包结构:东西也不是很多,copy也不复杂
本文会详细的”拆”这个库
如何引用:
<com.loopeer.android.librarys.scrolltable.ScrollTableView
android:id="@+id/scroll_table_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
正常控件用就行了,没什么特别的姿势,这边讲下有哪些自定义标签
都在ScrollTable/src/main/res/values/attrs.xml里
dataMargin 数据间距 类型:dimension
itemHeight 每个块的高度 类型:dimension
itemWidth 每个块的宽度 类型:dimension
topPlaceHeight 头部的高度 类型:dimension
itemIndicatorCircleRadius 小圆圈的半径 类型:dimension
itemIndicatorLineWidth 分割线的宽度 类型:dimension
textTitleSize 标题大小 类型:dimension
textLeftTitleColor 左边标题颜色 类型:dimension
textTopTitleColor 上边标题颜色 类型:dimension
等等等。。。(一排可以自己看,不难理解)
组成
很明显,这是一个试图组,这边拆一下,给大家解释下
ScrollTable/src/main/res/layout/view_container.xml里
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.loopeer.android.librarys.scrolltable.view.IHorizontalScrollView
android:id="@+id/scroll_header_horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/table_top_title_height"
android:layout_marginLeft="@dimen/table_left_title_width"
>
<com.loopeer.android.librarys.scrolltable.view.TopTitleView
android:id="@+id/header_horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
</com.loopeer.android.librarys.scrolltable.view.IHorizontalScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<com.loopeer.android.librarys.scrolltable.view.VerticalScrollView
android:id="@+id/scroll_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.loopeer.android.librarys.scrolltable.view.LeftTitleView
android:id="@+id/header_vertical"
android:layout_width="@dimen/table_left_title_width"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"/>
<com.loopeer.android.librarys.scrolltable.view.IHorizontalScrollView
android:id="@+id/scroll_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.loopeer.android.librarys.scrolltable.view.CustomTableView
android:layout_marginTop="@dimen/table_default_margin_top"
android:id="@+id/content_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.loopeer.android.librarys.scrolltable.view.IHorizontalScrollView>
</LinearLayout>
</com.loopeer.android.librarys.scrolltable.view.VerticalScrollView>
</LinearLayout>
</LinearLayout>
一个水平滚动试图里包着一个TopTitle的view
一个垂直滚动的视图包着一个LeftTitle的view
中间是一个自己绘画的CustomView
横向滚动试图IHorizontalScrollView
public class IHorizontalScrollView extends HorizontalScrollView implements IScroller {
private IScroller scroller;
public IHorizontalScrollView(Context context) {
this(context, null);
}
public IHorizontalScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (scroller != null) {
scroller.onScrollXY(l, t);
}
}
public void setIScroller(IScroller scroller) {
this.scroller = scroller;
}
@Override
public void onScrollXY(int offsetX, int offsetY) {
scrollTo(offsetX, offsetY);
}
}
继承于HorizontalScrollView ,用于给titleview添加同步滚动的操作,让他们看上去是一体的
VerticalScrollView也是类似效果只不过一个横向一个纵向而已
横向的TopTitleView
因为有很多初始化的“没营养代码”,所以我只贴重要步骤
TopTitleView extends View
它是一个自定义的View
尺寸:
宽度=间距+字宽
高度=定义的高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
mItemHeight = sizeHeight;
setMeasuredDimension(column * mItemWidth + (column - 1) * mItemMargin, mItemHeight);
}
绘画:
循环便利数据源,因为仅为单行多列,所以一个循环搞定,画字,算间距即可
private void drawItem(Canvas canvas) {
for (int columnIndex = 0; columnIndex < column; columnIndex++) {
String content = titles.get(columnIndex);
Paint.FontMetrics fontMetrics = mPaintTextNormal.getFontMetrics();
float fontHeight = fontMetrics.bottom - fontMetrics.top;
float textWidth = mPaintTextNormal.measureText(content);
float y = mItemPlaceHeight - (mItemPlaceHeight - fontHeight) / 2 - fontMetrics.bottom;
float x = (mItemMargin + mItemWidth) * columnIndex + mItemWidth / 2 - textWidth / 2;
canvas.drawText(content, x, y, mPaintTextNormal);
}
}
LeftTitleView 左侧标题
也是一个自定义view只不过比top多了点,多了圈
也是一样,只挑重要代码解释
private void drawItem(Canvas canvas) {
for (int rowIndex = 0; rowIndex < row; rowIndex++) {
//获取文字内容
String content = titles.get(rowIndex);
//计算文字尺寸
Paint.FontMetrics fontMetrics = mPaintTextNormal.getFontMetrics();
float fontHeight = fontMetrics.bottom - fontMetrics.top;
float textWidth = mPaintTextNormal.measureText(content);
float y = rowIndex * (mItemHeight + mItemMargin) + mItemHeight - (mItemHeight - fontHeight) / 2 - fontMetrics.bottom - mItemHeight / 2 + getResources().getDimension(R.dimen.table_default_margin_top) - mItemMargin / 2 + mItemMargin;
float x = mItemWidth - textWidth - 2 * mItemIndicatorCircleRadius - mItemIndicatorLineWidth - 12;
//画字
canvas.drawText(content, x, y, mPaintTextNormal);
//画圈
canvas.drawCircle(mItemWidth - mItemIndicatorLineWidth - mItemIndicatorCircleRadius,
rowIndex * (mItemHeight + mItemMargin) + getResources().getDimension(R.dimen.table_default_margin_top) - mItemMargin / 2 + mItemMargin,
mItemIndicatorCircleRadius, mPaintItemIndicatorCircle);
//规划矩阵
canvas.drawRect(mItemWidth - mItemIndicatorLineWidth,
rowIndex * (mItemHeight + mItemMargin) + getResources().getDimension(R.dimen.table_default_margin_top) - mItemMargin / 2 - mItemMargin / 2 + mItemMargin,
mItemWidth,
rowIndex * (mItemHeight + mItemMargin) + getResources().getDimension(R.dimen.table_default_margin_top) - mItemMargin / 2 + mItemMargin / 2 + mItemMargin,
mPaintItemIndicatorLine);
}
}
这是一个增量的绘画过程,每个循环都增量上一轮的参数值
CustomView
//大小计算
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//(间距总和+宽度总和),(高度总和+间距总和)
setMeasuredDimension(column * mItemWidth + (column - 1) * mItemMargin, row * mItemHeight + row * mItemMargin);
}
//实际画框的操作,循环画框,也就是说在初始化时所有的ui已经画出来了只是没滑到的时候不显示而已
private void drawItem(Canvas canvas) {
for (int rowIndex = 0; rowIndex < row; rowIndex++) {
for (int columnIndex = 0; columnIndex < column; columnIndex++) {
adJustSelectPaintColor(columnIndex, rowIndex);
float left = mItemWidth * columnIndex + mItemMargin * columnIndex;
float right = left + mItemWidth;
float top = mItemHeight * rowIndex + mItemMargin * (rowIndex + 1);
float bottom = top + mItemHeight;
canvas.drawRect(left, top, right, bottom, mPaintItemBg);
String content = getShowData(rowIndex, columnIndex);
Paint.FontMetrics fontMetrics = mPaintTextNormal.getFontMetrics();
float fontHeight = fontMetrics.bottom - fontMetrics.top;
float textWidth = mPaintTextNormal.measureText(content);
float y = top + mItemHeight - (mItemHeight - fontHeight) / 2 - fontMetrics.bottom;
float x = left + mItemWidth / 2 - textWidth / 2;
//画字
canvas.drawText(content, x, y, mPaintTextNormal);
}
}
}
双循环画字画方块画间距。
写个接口解决点击行为
public interface OnPositionClickListener {
void onPositionClick(Position position);
}
public boolean onTouchEvent(MotionEvent event) {
Position position = getPositionFromLocation(event.getX(), event.getY());
if (position == null) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
if (mPositionChangeListener != null) {
mPositionChangeListener.onPositionClick(position);
}
break;
}
return true;
}
在onTouchEvent记录坐标点
内部的子View都讲完了,剩下就是”载体”ScrollTableView
public class ScrollTableView extends LinearLayout implements CustomTableView.OnPositionClickListener {
//各种对象
private IHorizontalScrollView scrollHeaderHorizontal;
private IHorizontalScrollView scrollHorizontal;
private LeftTitleView headerVertical;
private TopTitleView headerHorizontal;
private CustomTableView contentView;
//被点击的坐标集合
private ArrayList<Position> selectPositions;
private ArrayList<String> topTitles;
private ArrayList<String> leftTitles;
//数据源
private ArrayList<ArrayList<String>> datas;
public ScrollTableView(Context context) {
this(context, null);
}
public ScrollTableView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrollTableView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.view_container, this);
setUpView();
setUpData();
selectPositions = new ArrayList<>();
if (attrs == null) return;
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ScrollTableView, defStyleAttr, 0);
if (a == null) return;
headerVertical.setUpAttrs(context, attrs, defStyleAttr);
headerHorizontal.setUpAttrs(context, attrs, defStyleAttr);
contentView.setUpAttrs(context, attrs, defStyleAttr);
a.recycle();
}
//同步了2个横向 2个纵向滚动的行为
private void setUpView() {
scrollHeaderHorizontal = (IHorizontalScrollView) findViewById(R.id.scroll_header_horizontal);
scrollHorizontal = (IHorizontalScrollView) findViewById(R.id.scroll_horizontal);
headerVertical = (LeftTitleView) findViewById(R.id.header_vertical);
headerHorizontal = (TopTitleView) findViewById(R.id.header_horizontal);
contentView = (CustomTableView) findViewById(R.id.content_view);
//传递滚动行为
scrollHorizontal.setIScroller(scrollHeaderHorizontal);
scrollHeaderHorizontal.setIScroller(scrollHorizontal);
}
public LeftTitleView getLeftTitleView() {
return headerVertical;
}
public TopTitleView getTopTitleView() {
return headerHorizontal;
}
public CustomTableView getContentView() {
return contentView;
}
private void setUpData() {
leftTitles = new ArrayList<>();
topTitles = new ArrayList<>();
datas = new ArrayList<>();
contentView.setRowAndColumn(leftTitles.size(), topTitles.size());
contentView.setOnPositionChangeListener(this);
}
//填充数据
public void setDatas(ArrayList<String> topTitlesData, ArrayList<String> leftTitlesData, ArrayList<ArrayList<String>> itemData) {
topTitles.clear();
leftTitles.clear();
datas.clear();
topTitles.addAll(topTitlesData);
leftTitles.addAll(leftTitlesData);
datas.addAll(itemData);
updateView();
}
//刷新数据
private void updateView() {
headerVertical.updateTitles(leftTitles);
headerHorizontal.updateTitles(topTitles);
contentView.setRowAndColumn(leftTitles.size(), topTitles.size());
contentView.setDatas(datas);
}
//记录坐标点
@Override
public void onPositionClick(Position position) {
if (selectPositions.contains(position)) {
selectPositions.remove(position);
} else {
selectPositions.add(position);
}
contentView.setSelectPositions(selectPositions);
}
//获取被点击的集合
public ArrayList<Position> getSelectPositions() {
return selectPositions;
}
}
整体不是很难,但是要考虑好各个空间的关系,对于View Group的入门学习还是不错的,解释的过程中省略了一些传递参数和逻辑代码,不明白的建议看源码!!
实在看不明白可以微信我,可以给你简单解释下
翻翻git之---可用作课程表/排班表的自定义table库ScrollTableView的更多相关文章
- (排班表二)后台动态绘制Grid表格
后台动态绘制值班表(Grid表格 列名不固定) 要求:表头除了值班人姓名,还要显示日期,及每天的星期值,用斜杠‘/’分隔.即:几号/星期几 最终实现的效果:根据查询的年月显示每个值班人查询月份每天的值 ...
- 使用SQL语句使数据从坚向排列转化成横向排列(排班表)
知识重点: 1.extract(day from schedule01::timestamp)=13 Extract 属于 SQL 的 DML(即数据库管理语言)函数,同样,InterBase 也支持 ...
- (排班表一)使用SQL语句使数据从坚向排列转化成横向排列
知识重点: 1.extract(day from schedule01::timestamp)=13 Extract 属于 SQL 的 DML(即数据库管理语言)函数,同样,InterBase 也支持 ...
- (排班表三)导出列名不固定的Grid表格到Excel
将班表信息导出Excel表格保存到本地 要求:文档名称为[XXXX]年X月值班表 文档显示的效果: 实现代码: //导出Excel值班表 private void btn_export_1_Click ...
- c++实现医院检验科排班程序
c++实现医院检验科排班程序 1.背景: 医院急诊检验科24h×7×365值班.工作人员固定.採取轮班制度.确保24h都有人值班. 本文就通过C++实现编敲代码自己主动排班,并能够转为Excel打印. ...
- 详解 OneAlert 排班可以帮你做什么
排班的存在,实质是通过有序安排,降低企业/团队人力成本,提升工作效率. 阅读导航(预计2min) 1. 详解排班功能 轮班机制 工作时间 双视图展示 灵活调整 2. 利用排班如何助力运维团队 排班 ...
- Google Optimization Tools实现员工排班计划Scheduling【Python版】
上一篇介绍了<使用.Net Core与Google Optimization Tools实现员工排班计划Scheduling>,这次将Google官方文档python实现的版本的完整源码献 ...
- 使用.NET Core与Google Optimization Tools实现员工排班计划Scheduling
上一篇说完<Google Optimization Tools介绍>,让大家初步了解了Google Optimization Tools是一款约束求解(CP)的高效套件.那么我们用.NET ...
- Javascript:日期排班功能实现
背景: 近期,公司的产品经常会遇到日期排班类似的功能: 需求的排班日期长短不一:有些是两周,有些是四周:要求选中的时候有一个active的状态区分,另外要提供钩子获取选中日期的形如:[2018-04 ...
随机推荐
- C语言实现链表中结构体嵌套
1.首先,定义两个结构体,一个用于定义链表,一个用于定义数据 // 定义数据相关的结构体 typedef struct Student{ int stu_id; ]; }Stu; // 定义链表相关的 ...
- java 常用资源
java高手真经:http://pan.baidu.com/share/link?uk=2100475681&shareid=2381645927#path=%252F%255Bwww.jav ...
- Yii框架2.0的安装过程
Yii框架是个不错的php开发框架,大型项目上都可以使用.和大多框架一样他也是开源,而且采用了mvc结构的. Yii1.*,直接下载然后用脚步可以创建自己的项目了,最近看了下Yii2.0版本的,他推荐 ...
- C++应该被看成是个语言集合——四种语言(C语言,OO语言,泛型语言,STL)
至少有三种语言: 一,C++ is C 二,C++ is an OO language 三,C++ is a genetic programming language 有的童鞋觉得难,可能是没有看清楚 ...
- 【python】常用函数
使用list生成dict(可指定单条长度和数据类型,splen为4即为list中每4行组成dict中一条) def list2dict(srclist,splen,datatype):# dataty ...
- HBase 二次开发 java api和demo
1. 试用thrift python/java以及hbase client api.结论例如以下: 1.1 thrift的安装和公布繁琐.可能会遇到未知的错误,且hbase.thrift的版本 ...
- 使用maven为web工程引入jstl包时报错了
原pom文件: <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</a ...
- django生产环境部署
测试环境:linux centos7下 1.安装uwsgi python3下安装: pip3 install uwsgi python2下安装: pip install uwsgi 如果是系统自带的p ...
- FileZilla使用
FileZilla是一个免费开源的FTP软件,分为客户端版本和服务器版本,具备所有的FTP软件功能.可控性.有条理的界面和管理多站点的简化方式使得Filezilla客户端版成为一个方便高效的FTP客户 ...
- PAT 1048 Find Coins[比较]
1048 Find Coins (25 分) Eva loves to collect coins from all over the universe, including some other p ...