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

最近一直在写混合开发的东西,是时候温故下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的入门学习还是不错的,解释的过程中省略了一些传递参数和逻辑代码,不明白的建议看源码!!

实在看不明白可以微信我,可以给你简单解释下

最新文章

  1. Thinkphp的单字母函数整理
  2. iOS百度地图路径规划和POI检索详细总结-b
  3. JS 函数调用
  4. java初学的几个问题
  5. (转)Hibernate 的应用(Hibernate 的结构)?
  6. Proxy 代理模式
  7. 怎样调通微信支付及微信发货通知接口(Js API)
  8. ListView下拉刷新及上拉更多两种状态
  9. Asp.Net Web API 2(CRUD操作)第二课
  10. PAT (Advanced Level) 1113. Integer Set Partition (25)
  11. 服务器数据库搭建流程(CentOs+mysql)
  12. Jxl创建Excel文件和解析Excel文件
  13. 【一天一道LeetCode】#15 3Sum
  14. 在tensorflow中使用batch normalization
  15. uCOS-III等RTOS与IoT OS
  16. AOJ 2249 Road Construction (dijkstra)
  17. [python] 初学python,级联菜单输出
  18. short、int、long、float、double区别
  19. &lt;转&gt;MySQL临时表的简单用法
  20. jvm 知识点

热门文章

  1. Storm-源码分析-Topology Submit-Nimbus
  2. 解决:“Workbench has not been created yet” error in eclipse plugin programming”,OSGI启动控制台报错问题
  3. 1.1 - python基础语法 - 总结练习题
  4. log4cpp简单示例
  5. Ignatius and the Princess IV---hdu1029(动态规划或者sort)
  6. VMware 虚拟机 Ubuntu 不能全屏问题
  7. vim的快捷键
  8. Visual Studio 起始页面关闭新闻等
  9. 如何停止requestAnimationFrame方法启动的动画
  10. 别真以为JavaScript中func.call/apply/bind是万能的!