转载请注明出处:王亟亟的大牛之路

最近一直在写混合开发的东西,是时候温故下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的更多相关文章

  1. (排班表二)后台动态绘制Grid表格

    后台动态绘制值班表(Grid表格 列名不固定) 要求:表头除了值班人姓名,还要显示日期,及每天的星期值,用斜杠‘/’分隔.即:几号/星期几 最终实现的效果:根据查询的年月显示每个值班人查询月份每天的值 ...

  2. 使用SQL语句使数据从坚向排列转化成横向排列(排班表)

    知识重点: 1.extract(day from schedule01::timestamp)=13 Extract 属于 SQL 的 DML(即数据库管理语言)函数,同样,InterBase 也支持 ...

  3. (排班表一)使用SQL语句使数据从坚向排列转化成横向排列

    知识重点: 1.extract(day from schedule01::timestamp)=13 Extract 属于 SQL 的 DML(即数据库管理语言)函数,同样,InterBase 也支持 ...

  4. (排班表三)导出列名不固定的Grid表格到Excel

    将班表信息导出Excel表格保存到本地 要求:文档名称为[XXXX]年X月值班表 文档显示的效果: 实现代码: //导出Excel值班表 private void btn_export_1_Click ...

  5. c++实现医院检验科排班程序

    c++实现医院检验科排班程序 1.背景: 医院急诊检验科24h×7×365值班.工作人员固定.採取轮班制度.确保24h都有人值班. 本文就通过C++实现编敲代码自己主动排班,并能够转为Excel打印. ...

  6. 详解 OneAlert 排班可以帮你做什么

    排班的存在,实质是通过有序安排,降低企业/团队人力成本,提升工作效率. 阅读导航(预计2min)   1. 详解排班功能 轮班机制 工作时间 双视图展示 灵活调整 2. 利用排班如何助力运维团队 排班 ...

  7. Google Optimization Tools实现员工排班计划Scheduling【Python版】

    上一篇介绍了<使用.Net Core与Google Optimization Tools实现员工排班计划Scheduling>,这次将Google官方文档python实现的版本的完整源码献 ...

  8. 使用.NET Core与Google Optimization Tools实现员工排班计划Scheduling

    上一篇说完<Google Optimization Tools介绍>,让大家初步了解了Google Optimization Tools是一款约束求解(CP)的高效套件.那么我们用.NET ...

  9. Javascript:日期排班功能实现

     背景: 近期,公司的产品经常会遇到日期排班类似的功能: 需求的排班日期长短不一:有些是两周,有些是四周:要求选中的时候有一个active的状态区分,另外要提供钩子获取选中日期的形如:[2018-04 ...

随机推荐

  1. 微信公众号获取用户openId How to use cURL to get jSON data and decode the data?

    w http://stackoverflow.com/questions/16700960/how-to-use-curl-to-get-json-data-and-decode-the-data

  2. bootloader,kernel,initrc

    http://www.ibm.com/developerworks/cn/linux/l-k26initrd/index.html http://www.68idc.cn/help/server/li ...

  3. Storm-源码分析- timer (backtype.storm.timer)

    mk-timer timer是基于PriorityQueue实现的(和PriorityBlockingQueue区别, 在于没有阻塞机制, 不是线程安全的), 优先级队列是堆数据结构的典型应用 默认情 ...

  4. spring data jpa 遇到的问题

    org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.O ...

  5. 转!!Linux 里的 2>&1 究竟是什么

    原博文地址:https://blog.csdn.net/shunzi1046/article/details/76110963 我们在Linux下经常会碰到nohup command>/dev/ ...

  6. Python并行编程(三):线程同步之Lock

    1.基础概念 当两个或以上对共享内存操作的并发线程中,如果有一个改变数据,又没有同步机制的条件下,就会产生竞争条件,可能会导致执行无效代码.bug等异常行为. 竞争条件最简单的解决方法是使用锁.锁的操 ...

  7. Python垃圾回收机制详解转自--Kevin Lu

    一.垃圾回收机制 Python中的垃圾回收是以引用计数为主,分代收集为辅.引用计数的缺陷是循环引用的问题. 在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存. #e ...

  8. 具体解释linux下的串口通讯开发

    串行口是计算机一种经常使用的接口,具有连接线少.通讯简单,得到广泛的使用.经常使用的串口是RS-232-C接口(又称EIA RS-232-C)它是在1970年由美国电子工业协会(EIA)联合贝尔系统. ...

  9. idea配置scala和spark

    1 下载idea  路径https://www.jetbrains.com/idea/download/#section=windows 2安装spark  spark-2.1.0-bin-hadoo ...

  10. 如何使用别人的代码 (特指在MFC里面 或者推广为C++里面)

    别人写了一堆代码,给了你源代码.在C++里面 应该是  头文件(.h)和源文件(.cpp).  那么我们如何使用他们呢?? 第一步:将其包含进来 如下图  ,不论是头文件还是源文件都如此 第二步:告诉 ...