今日科技快讯

据CNBC报道,网络安全记者发布报告声称,Facebook在未加密的情况下存储了多达6亿个用户账户密码,并以明文形式存储,公司数万名员工可以访问。在Facebook的27亿用户中,6亿用户已经占了相当大的比例。该公司周四表示,计划开始通知受到影响的用户,以便他们更改密码。

作者简介

周一上午好,新的一周在春意盎然中继续加油吧!

本篇文章来自 至死仍是少年心 的投稿,和大家分享了如何在 Android中快速实现一个Excel表格,希望对大家有所帮助!

至死仍是少年心的博客地址:

https://www.jianshu.com/u/823f9ad8aa8f

前言

最近公司做的OA项目有会议室预订功能,设计图是这个样子的:

看到设计图的时候就想到了要做成左右、上下滑动的样子,因为以前朋友做过一个抢位置app也是这种效果,当时感觉很炫。不过他是用的是github上的一个开源项目,虽然那个效果比较好,但美中不足的是它横向滑动的时候比较卡顿,而且代码相对来说比较复杂,改造成完全适应自身项目所需要花费的时间就比较多,对于赶项目的程序员来说,没什么是花费时间少且完美运行的完成任务更有吸引力了,所以后面我就采用了原生布局嵌套的方式快速的实现了功能。

先不说其他的,摆上最终效果图:

这里先讲解下业务逻辑,头部的横轴是时间段(0:00-23:59),纵轴是会议室,内容区域展示的是被预订的会议室,上下滑动内容区域的时候会议室列表要联合滚动,左右滑动内容区域的时候头部的时间轴也要联合滚动,滚动会议室和时间轴的时候内容区域也要相应的滚动。由于预订的时间可能是 8:10-8:50或者是9:10-10:30这种的,所以这个地方我们还要考虑不是零起点、跨区域等情况,而不是单纯的填充背景色。

布局嵌套思维图

根据布局思维图可以很清晰的看到:

时间轴的外层是套了个横向的scrollview,里面是一个linearLayout布局,布局xml代码代码:

纵轴会议室简单,就是一个listview。

内容区域由于需要刷新,所以在最外层加了个swipeRefresh,然后在内部一次加上横向的scrollview和listview,布局xml代码如下:

把三个主要布局画出来后就需要在页面中设置数据了:
这里有两个listview,所以需要两个适配器,而两个listview需要联动。当然,两个横向的scrollview也需要联动,两个滑动监听代码如下:

   /**     * HorizontalScrollView的滑动监听(水平方向同步控制)     */    private class HorizontalScrollListener implements MyHorizontalScrollView.OnHorizontalScrollListener {        @Override        public void onHorizontalScrolled(MyHorizontalScrollView view, int l, int t, int oldl, int oldt) {            if (view == mhscContent) {                mhscRow.scrollTo(l, t);            } else {                mhscContent.scrollTo(l, t);            }        }

    }

    /**     * 两个ListView的滑动监听(垂直方向同步控制)     */    private class VerticalScrollListener implements AbsListView.OnScrollListener {

        int scrollState;

        @Override        public void onScrollStateChanged(AbsListView view, int scrollState) {            this.scrollState = scrollState;            if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_TOUCH_SCROLL) {                View subView = view.getChildAt(0);                if (subView != null && view == lvContent) {                    int top = subView.getTop();                    int position = view.getFirstVisiblePosition();                    lvColumn.setSelectionFromTop(position, top);                } else if (subView != null && view == lvColumn) {                    int top = subView.getTop();                    int position = view.getFirstVisiblePosition();                    lvContent.setSelectionFromTop(position, top);                }            }

            // 滑动事件冲突的解决:如果ListView的首条item的position != 0,即此时不再顶上,则将下拉刷新禁用//            if (swipeRefreshEnable) {            if (view.getFirstVisiblePosition() != 0 && srlTableContent.isEnabled()) {                srlTableContent.setEnabled(false);            }

            if (view.getFirstVisiblePosition() == 0) {                srlTableContent.setEnabled(true);            }//            }        }

        @Override        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {            //判断滑动是否终止,以停止自动对齐,否则该方法会一直被调用,影响性能            if (scrollState == SCROLL_STATE_IDLE) {                return;            }            View subView = view.getChildAt(0);            if (subView != null && view == lvContent) {                int top = subView.getTop();                lvColumn.setSelectionFromTop(firstVisibleItem, top);            } else if (subView != null && view == lvColumn) {                int top = subView.getTop();                lvContent.setSelectionFromTop(firstVisibleItem, top);            }        }    }

这里就不摆出listview设置数据的代码了,应该都会。

设置横向时间轴

设置横向时间轴的代码如下:

  private void initRowLayou() {        topItmeWidth = AppDensityUtil.dip2px(this, 100);        topItmeHeight = AppDensityUtil.dip2px(this, 35);        List<String> rowDataList = getRowDataList();        for (int i = 0; i < rowDataList.size(); i++) {            RelativeLayout inflate = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.item_table_row_view, null);            final TextView tvRowTxt = (TextView) inflate.findViewById(R.id.tv_row_txt);            tvRowTxt.setText(rowDataList.get(i));            tvRowTxt.setTag(i);

            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(topItmeWidth, topItmeHeight);//            tvRowTxt.setWidth(topItmeWidth);//设置宽度//            tvRowTxt.setHeight(topItmeHeight);//设置宽度            tvRowTxt.getPaint().setFakeBoldText(true);            tvRowTxt.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    ToastUtil.showMessage(tvRowTxt.getTag() + "");                }            });            llRowContent.addView(inflate, layoutParams);        }    }

意思很简单,根据集合遍历,然后动态new出布局。

一开始在上面也说了预订的时间可能是 8:10-8:50或者是9:10-10:30这种的,所以这个地方我们还要考虑不是零起点、跨区域等情况。因为原生的progressbar必须从头开始绘制,不满足要求,所以我们需要自定义控件才能满足要求,那就来个简单的自定义控件,代码如下:

/** * 名称:范围进度条 没做动画效果 * 创建人:邹安富 * 创建时间:2018/3/2 * 详细说明: */public class MyProgress extends View {    private Paint backPaint;    private Paint fillPaint;

    private int backColor;    private int fillColor;

    private float layout_width;    private float layout_height;    private int maxProgress;    private int startProgress;    private int endProgress;

    public MyProgress(Context context) {        super(context);    }

    public MyProgress(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init(context, attrs);    }

    public MyProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context, attrs);    }

    public void setStartProgress(int startProgress) {        this.startProgress = startProgress;    }

    public void setEndProgress(int endProgress) {        this.endProgress = endProgress;    }

    public void setMaxProgress(int maxProgress) {        this.maxProgress = maxProgress;    }

    public void setStardEndProgress(int start, int endProgress) {        this.startProgress = start;        this.endProgress = endProgress;        invalidate();    }

    public void clearProgress() {        this.startProgress = 0;        this.endProgress = 0;        invalidate();    }

    private void init(Context context, AttributeSet attrs) {        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyProgress);

        layout_height = a.getDimension(R.styleable.MyProgress_mpLayoutHeight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics()));        layout_width = a.getDimension(R.styleable.MyProgress_mpLayoutWidth, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, context.getResources().getDisplayMetrics()));        maxProgress = a.getInteger(R.styleable.MyProgress_mpMaxValues, 100);        startProgress = a.getInteger(R.styleable.MyProgress_mpStartValues, 0);        endProgress = a.getInteger(R.styleable.MyProgress_mpEndValues, 0);        backColor = a.getColor(R.styleable.MyProgress_mpBackGroundColor, getResources().getColor(R.color.white));        fillColor = a.getColor(R.styleable.MyProgress_mpFillGroundColor, getResources().getColor(R.color.red));

        backPaint = new Paint();        backPaint.setAntiAlias(true);        backPaint.setColor(backColor);        backPaint.setDither(true);//防抖动        backPaint.setStyle(Paint.Style.FILL);

        fillPaint = new Paint();        fillPaint.setAntiAlias(true);        fillPaint.setColor(fillColor);        fillPaint.setDither(true);        fillPaint.setStyle(Paint.Style.FILL);    }

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int measureView_width = measureView(widthMode, widthSize, layout_width);        int measureView_height = measureView(heightMode, heightSize, layout_height);        setMeasuredDimension(measureView_width, measureView_height);    }

    private int measureView(int mode, int size, float defaultSize) {        switch (mode) {            case MeasureSpec.UNSPECIFIED:            case MeasureSpec.AT_MOST:                if (size > defaultSize) {                    size = (int) Math.ceil(defaultSize);                }                return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);            case MeasureSpec.EXACTLY:                return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);        }        return -1;    }

    private static final String TAG = "MyProgress";

    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);

        canvas.drawRect(0, 0, layout_width, layout_height, backPaint);

        double startReta = (double) startProgress / maxProgress;        double endReta = (double) endProgress / maxProgress;

        float fillStartWidth = (float) (startReta * layout_width);        float fillEndWidth = (float) (endReta * layout_width);

        Log.i(TAG, "onDraw: startProgress==" + startProgress);        Log.i(TAG, "onDraw: endProgress==" + endProgress);        Log.i(TAG, "onDraw: maxProgress==" + maxProgress);

        Log.i(TAG, "onDraw: endReta==" + endReta);        Log.i(TAG, "onDraw: fillStartWidth==" + fillStartWidth);        Log.i(TAG, "onDraw: fillEndWidth==" + fillEndWidth);

        canvas.drawRect(fillStartWidth, 0, fillEndWidth, layout_height, fillPaint);

    }

这个自定义控件很简单,就是传入起始和结束的位置。然后和最大值比较,得到比率。最后根据比率算出应该绘制的长度和区域。这个view写好后还需要封装一下,RowItmeViewLayout就是对改view的封装,封装的具体内容有: 动态增加字内容、清除所有的进度展示、根据传入预订时间范围设置进度条,代码如下:

/** * 名称: * 创建人:邹安富 * 创建时间:2018/3/5 * 详细说明: */public class RowItmeViewLayout extends LinearLayout {    private static final String TAG = "RowItmeViewLayout";    private List<MyProgress> myProgressList = new ArrayList<>();

    public RowItmeViewLayout(Context context) {        super(context);    }

    public RowItmeViewLayout(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        initView(context, attrs);    }

    public RowItmeViewLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initView(context, attrs);    }

    private void initView(Context context, AttributeSet attrs) {        this.setOrientation(HORIZONTAL);        //整天没有预订        for (int i = 0; i < 24; i++) {            View inflate = LayoutInflater.from(context).inflate(R.layout.include_item_yd_row, null);            MyProgress myProgress = (MyProgress) inflate.findViewById(R.id.mp_progress);            myProgress.setMaxProgress(60);            myProgress.setStardEndProgress(0, 0);            this.addView(inflate);//            myProgress.setOnClickListener(new View.OnClickListener() {//                @Override//                public void onClick(View v) {//                    ToastUtil.showMessage("没有预订");//                }//            });            myProgressList.add(myProgress);        }    }

    /**     * 清楚所有的进度展示     */    public void clearAllProgress() {        for (int i = 0; i < myProgressList.size(); i++) {            myProgressList.get(i).clearProgress();        }    }

    /**     * 设置进度条     */    public void setProgress(String progressStr) {//        String str = "09:10-10:20";        try {            String[] split = progressStr.split("-");            String startHour = split[0].split(":")[0];            String startMinute = split[0].split(":")[1];            String endHour = split[1].split(":")[0];            String endMinute = split[1].split(":")[1];

            if (TextUtils.equals(startHour, endHour)) {                //相等                String index;                if (startHour.startsWith("0") && !startHour.endsWith("0")) {                    index = startHour.replace("0", "");                } else {                    index = startHour;                }

                MyProgress myProgressStart = myProgressList.get(Integer.parseInt(index));                myProgressStart.setStardEndProgress(Integer.parseInt(startMinute), Integer.parseInt(endMinute));

            } else {                //不相等                int indexStart;                int indexEnd;                if (startHour.startsWith("0") && !startHour.endsWith("0")) {                    indexStart = Integer.parseInt(startHour.replace("0", ""));                } else {                    indexStart = Integer.parseInt(startHour);                }

                if (endHour.startsWith("0")) {                    indexEnd = Integer.parseInt(endHour.replace("0", ""));                } else {                    indexEnd = Integer.parseInt(endHour);                }

                for (int i = indexStart; i <= indexEnd; i++) {                    MyProgress myProgressStart = myProgressList.get(i);                    myProgressStart.setStardEndProgress(0, 60);                    if (i == indexStart) {                        myProgressStart.setStardEndProgress(Integer.parseInt(startMinute), 60);                    }                    if (i == indexEnd) {                        myProgressStart.setStardEndProgress(0, Integer.parseInt(endMinute));                    }                }            }        } catch (Exception e) {            Log.e(TAG, "setProgress: e==" + e.toString());        }    }

}

把RowItmeViewLayout封装好以后就可以在适配器item布局中使用啦,xml布局代码是这样的:

适配器中布局使用

看下在适配器中布局使用:

/** * 名称:主内容适配器 * 创建人:邹安富 * 创建时间:2018/3/1 * 详细说明: */public class TableContentAdapter extends LvSuperBaseAdapter<TableContentBean> {    private static final String TAG = "TableContentAdapter";

    public TableContentAdapter(Context context, List<TableContentBean> mDatas) {        super(context, mDatas);    }

    @Override    public int getLayoutItemId() {        return R.layout.item_table_content_view;    }

    @Override    public LvBaseViewHolder getViewHolder(View itemView) {        return new ViewHolder(itemView);    }

    @Override    public void setDatas(LvBaseViewHolder holder, TableContentBean itemBean, int position) {        final ViewHolder viewHolder = (ViewHolder) holder;        int childCount = viewHolder.llTableContent.getChildCount();        Log.e(TAG, "setDatas: childCount===" + childCount);        List<TableContentBean.HourData> hourData = itemBean.getHourData();

        viewHolder.rowlContent.clearAllProgress();

        if (itemBean.getYdStatc() == 1) {            for (int i = 0; i < hourData.size(); i++) {                String ydTimeSlot = hourData.get(i).getYdTimeSlot();                if (!TextUtils.isEmpty(ydTimeSlot)) {                    viewHolder.rowlContent.setProgress(ydTimeSlot);                }            }        } else {            viewHolder.rowlContent.clearAllProgress();        }    }

    static class ViewHolder extends LvBaseViewHolder {        @BindView(R.id.rowl_content)        RowItmeViewLayout rowlContent;        @BindView(R.id.ll_table_content)        CheckableLinearLayout llTableContent;

        ViewHolder(View view) {            super(view);            ButterKnife.bind(this, view);        }    }}

总结

注意事项:

1.  MyProgress要封装起来,不然在listview上下滑动的时候会巨卡。

2.  主内容区域往左滑动到最后面有多余的空白条,在被嵌套的listview中需要加上: android:scrollbars="none"这个消除空白条。

3.  嵌套的listview没有用recyclerview替换,理论上是没问题的,可能处理滑动和高度的地方需要注意下,有兴趣的童鞋可以试试。

思路参考地址:

https://www.cnblogs.com/begin1949/p/5910785.html

其它方式的实现:

https://github.com/Kelin-Hong/ScrollablePanel

文章中Github地址:

https://github.com/zouanfu/AndroidExcel

推荐阅读:

Kotlin的特性应用示例,原来还可以这么玩

分享一个我开发的MVVM架构的开源小项目

Gradle妙用,统一化自动依赖管理

欢迎关注我的公众号,学习技术或投稿

长按上图,识别图中二维码即可关注

让你直呼666的仿Excel表格效果相关推荐

  1. Spring MVC和Spring Boot有什么区别? 这样答,面试官直呼666

    Spring MVC和Spring Boot有什么区别? 这样答,面试官直呼666 作为初级程序员,这样的问题在面试中,也被问到过,随着越来越了解,发现以前自己答的真水. 一般的回答 ​ 先来说说我以 ...

  2. Python仅用3行代码就能输出花式字符串图集,同事直呼666!

    Python用3行代码输出花式字符串图集,同事直呼666! 高逼格的日志 springboot 相信Java程序员看到上面的图,一定不会陌生.没错,springboot的启动日志.不知道其他人怎么想, ...

  3. html 仿excel,智表-浏览器端仿EXCEL表格jQuery插件

    智表(ZCELL)是一款浏览器端仿EXCEL表格jQuery插件.智表可以为你提供EXCEL般的智能体验,并带有灵活的单元格选中与实时计算功能,强大的复制与粘贴功能,标准化数据加载与获取,以及灵活的外 ...

  4. Qt高仿Excel表格组件-支持冻结列、冻结行、内容自适应和合并单元格

    目录 一.概述 二.效果展示 三.实现思路 1.冻结行.冻结列 2.行高自适应 3.蚂蚁线 四.测试代码 1.添加表格数据 2.设置冻结行.列 3.行高.列宽 4.单元格背景色 5.单元格文字 6.其 ...

  5. HTML Table 固定列宽,实现excel表格效果

    (1)获取行号 <table> <tr onmouseover ="showIndex(this)"><td>1</td></ ...

  6. 前端仿Excel表格

    可持久化存储表格信息和样式 <!DOCTYPE html> <html lang="en"><head><meta charset=&qu ...

  7. 这样规范写代码,同事直呼“666”

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://i7q.cn/5iDTto 一.MyBatis ...

  8. 这样设计订单系统,同事直呼 666!

    来源:人人都是产品经理 | http://dwz.win/Wkh 本文主要讲述了在传统电商企业中,订单系统应承载的角色,就订单系统所包含的主要功能模块梳理了设计思路,并对订单系统未来的发展做了一些思考 ...

  9. Spring 事务失效的 8 大场景,面试官直呼666...

    前几天发了一篇文章里面有一个关于事务失效的问题: 用 Spring 的 @Transactional 注解控制事务有哪些不生效的场景? 其中有个热心粉丝留言分享了下,我觉得总结得有点经验,给置顶了: ...

最新文章

  1. POJ2153 (C++ map)
  2. TCP/IP协议的一个具体实现Socket
  3. 2019.08.04 新建随笔
  4. 合并表格,并实现对datatable的group by 功能
  5. final类是否可以被代理_设计模式——代理模式
  6. 使用X Manager远程CentOS 7服务器(XDMCP)
  7. Go unsafe Pointer
  8. key_t IPC键和ftok函数详解和剖析
  9. 从入门到放弃之大数据Hive
  10. java web redis_java web网页版redis客户端工具
  11. Android仿苹果版QQ下拉刷新实现(二) ——贝塞尔曲线开发鼻涕下拉粘连效果
  12. tornado SQLAlchemy
  13. dwf怎么合成一个_cad多张图纸拆分(如何将一个有多个图的CAD文件,按图分成几个文件)...
  14. 5G的调制方式,到底是怎么实现的?
  15. 牛顿与莱布尔茨的微积分战争
  16. vss 迁入后,服务器上面的文件没有变化,VSS迁移
  17. 条件运算符的嵌套使用
  18. 内存错误分析工具----asan(AddressSanitizer)的介绍和使用
  19. Caused by: org.dom4j.DocumentException异常信息记录
  20. 面试之满帮与达达京东到家(未完全解析)

热门文章

  1. 查找我的iphone怎么用详细教程
  2. 嵌入式Linux系统libevent异步事件库的移植
  3. java教育机构管理计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
  4. Opencv 三对角线矩阵(Tridiagonal Matrix)解法之(Thomas Algorithm)
  5. .bat 批处理文件实现微信多开
  6. 使用windows身份验证登录sqlserver问题记录
  7. 【高数】高数第一章节——极限无穷连续与间断
  8. 中科新媒体:实体零售企业如何在数字化浪潮下创新营销
  9. python的语句有哪些_python基本语句有哪些
  10. 弘辽科技:商家不要错过的淘宝直播妙招!