美文网首页
2年之后重新写的页面

2年之后重新写的页面

作者: Sxgg | 来源:发表于2017-11-02 16:52 被阅读0次

    当年刚到公司的时候,就遇上了这个项目启动,从零开始做的项目。
    �第一个任务就是做一个展示房间列表的页面。具体的样子就是下图所长的样子

    ui图
    要求能够�展示所有房间14天内的预定信息,并且能对选中�部分日期的房间做预定,入住等操作。当时做的还是比较慢的,基本的想法就是用横向滑动的ScrollView里面嵌套一个GridView这些现有的控件�来做,可想而知,效果并不好。首先AbsListView嵌套在ScrollView里面的话,�就完全没法复用其中的contentViewviewHolder,有多少就一次性的生成多少对象,当数据量一大的时候造成性能上的低下,页面渲染的卡顿,并且当要更新选中的�格子的状态和UI,调用notifyDataSetChange()的时候,会对整个GridView进行了刷新,造成很大的系统开销,反应迟缓,因为GridView并没有像ReccyclerView一样,有只对某一个对象的更新而更新UI的功能。其次,ScrollViewGridVIew都只能进行一个方向的滑动,比如一旦竖直方向的滑动开始了之后,再进行水平方向的滑动是不会有响应的,用户的体验比较差。

    �第一版差不多就是这样做的,数据小的时候还勉强能用,�当有100个以上的房间的时候,加载的时候就特别慢,�网络请求拿到数据之后,还要过差不多3-4秒的时间,整个GridView才会显示出来,体验很差。由于是项目刚开始,对产品其他功能的需求实现比较迫切,所以对这个房态页面的功能仅仅是要求达到暂时能用,并没有立即�着手进行优化。后来,我记得�差不多半年之后,对页面做过一次优化,基本的结构并没有怎么改变,�UI上做了一些改善,还有�就是�自定了外面的ScrollView,能够进行同时两个方向的滑动了,这对体验上来说是一个提升,但是性能上并没有什么改善,数据一大,还是那么的卡,老样子。

    项目迭代了两年,�好几次都想要优化这个页面,但是由于功能快速迭代,一直没有时间和精力去好好的想方案。

    今年感觉积累的差不多了,稍微有点空的时候,就琢磨着捣鼓捣鼓这个页面,给它来一次全面的升级,抛弃掉以前的包袱,重新来写一遍。

    分析一下页面卡,主要是要渲染的东西太多了,或者一次生成的对象太多了,像GridView这种控件,每一个item都是一个对象,还有一个viewHolder,一旦出现没法复用contentViewviewHolder的话,数据量一多,一下子生成的对象多的会造成虚拟机内存抖动,在短时间内产生大量的对象,严重占用Young Generation(分代垃圾回收的年轻代)的内存区域,当剩余空间不够的时候,会触发GC从而导致刚产生的对象又很快被回收。调用控件的notifyDataSetChange()方法的时候,就会引起不断的GC,从而导致UI线程被频繁的阻塞,导致UI卡顿。初步分析可能是这个原因。

    memory.jpg

    当我�进入页面的时候内存使用率直接从20多mb跃到了40多mb,可怕的翻了一番,然后看一下memory usage的报表

    memoryusage1.jpg memoryusage2.jpg

    �objects中的view从105个增长到了14363�个�(当前页面有180个房间14天的情况�),每个item的视图都会有几个控件对象的生成,结果就是造成了现在一下子多了这么多的对象在内存中。打开模拟器的Gpu呈现模式分析,看到进去页面的时候柱状图反应的当前界面�渲染时间非常的高。

    gpu.jpg

    蓝色代表了绘制的时间,橙色代表处理的时间,红色代表执行的时间�几条最高的柱子中,�蓝色占了这么多的部分,说明很多时间都花在了gpu的绘制上。 说了这么多,�我觉得首先要控制对象的生成,一下子那么多对象,怎么都会卡,解决了对象的数量问题,什么过度绘制,cpu和gpu消耗大量资源导致系统卡顿的问题自然而然能够解决了。

    第一步确定方案,这次我打算不适用原生的那些类似ListView的组件了,直接自己在View上面绘制,我需要的内容,这样只有一个View对象,不会出现之前那种大量对象占用内存的情况了。

    对象直接�继承于View,首先需要确定这个�View的大小,确定View的大小�得在onMeasure()方法里面调用setMeasuredDimension()方法,参数传入�你需要的长度和宽度,宽我们直接可以确定,因为一行需要十四个格子,高度的话,需要根据数据的大小来设置,设置完了长宽,�就要在上面画格子,格子是一行14个,每列的数量也是根据数据来设置的,�所以我们可以在这个自定义View的构造函数,或者设置数据的时候来设置这个值,然后在onDraw()方法中,用canvas.drawLine()方法来画线,只要确定,线的起点,终点就可以。

     protected void dispatchDraw(Canvas canvas) {
            super.dispatchDraw(canvas);
            if (mData != null) {
                for (int i = 0; i < mData.length; i++) {
                    canvas.drawLine(0, unitHeight * i, unitWidth * 14, unitHeight * i, painttext);
                }
                for (int i = 0; i < mData[0].length; i++) {
                    canvas.drawLine(unitWidth * i, 0, unitWidth * i, unitHeight * mData.length, painttext);
                }
            }
        }
    

    画完线之后,要让View能够滑动起来,查看没有在当前页面显示的数据。滑动就交给onTouchEvent()方法,来追踪手势。

      switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    int index = event.getActionIndex();
                    downX = (int) event.getX();
                    downY = (int) event.getY();
                    event.getX(index);
                    if (!mScroller.isFinished()) {
                        mScroller.abortAnimation();
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    int x = (int) event.getX();
                    int y = (int) event.getY();
                    int moveX = downX - x;
                    downX = x;
                    int moveY = downY - y;
                    downY = y;
    
                    if (moveX < 0) {
                        if (getScrollX() + moveX < 0) {
                            moveX = 0;
                        }
                    } else {
                        if (getScrollX() + moveX > getMeasuredWidth() - mDm.widthPixels) {
                            moveX = 0;
                        }
                    }
                    if (moveY < 0) {
                        if (getScrollY() + moveY < 0) {
                            moveY = 0;
                        }
                    } else {
                        if (getScrollY() + moveY > getMeasuredHeight() - mDm.heightPixels) {
                            moveY = 0;
                        }
                    }
                    scrollBy(moveX, moveY);
                    break;
    

    ACTION_DOWN中确定手指触摸的位置,在ACTION_MOVE中来指定View滑动的距离。这样View虽然可以滑动了,但是�滑动是很生硬的,�只能用手指拖着View走,所以要加入fling这个操作,在手指滑动然后松开的时候View能够继续按照之前滑动的方向滑翔一段距离。Scroller的概念就不在这里述说了,Scroller有一个fling()的方法,根据这个方法的描述

    Start scrolling based on a fling gesture. The distance travelled will depend on the initial velocity of the fling.

    基于一个手势来进行滑动,滑动的距离将取决于开始时候的速度。然后看下这个方法的参数,startXstartY比较简单,就是开始滑动的位置。第三个和第四个参数需要了解一下,是两个根据x轴方向和y轴方向以像素/秒为单位测量的抛射速度,简单点说就是手指在屏幕上滑动的速度,我们要获取的是手指离开屏幕的那一瞬间的速度,用这个速度来控制View的惯性滑动距离。�在这里我们要用到android的VelocityTracker速度追踪器这个类,该类是用来计算在滑动控件时,手指在水平方向和竖直方向的速度。基本用法如下:

    VelocityTracker mVelocityTracker = VelocityTracker.obtain();//初始化  
    mVelocityTracker.addMovement(event);//将事件MotionEvent交给它处理  
    mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);//获取当前的速度
    
    int xVelocity = (int) mVelocityTracker.getXVelocity ();
            int yVelocity = (int) mVelocityTracker.getYVelocity ();//获取X轴方向和Y轴方向的速度
    

    computeCurrentVelocity()方法第一个参数的意思是指定单位时间内移动的像素,如果设为1就是1毫秒秒内移动了多少个像素,1000的话就是一秒内。maxVelocity是指最大的移动速度,移动速度的取值在0到设置的范围之内。使用完该对象后,需要对该对象释放,以节约内存,mVelocityTracker.recycle()。获取到了速度之后就可以用Scroller来做根据惯性滑动了。如下图所示。 模拟器使用起来还是没有真机流畅。

    1.gif

    格子和滑动都做好了,接下来就要将数据填充进去了。像使用RecyclerView一样,也是要有一个对象数组的,数据的改变映射到视图上面。从服务器获取的数据是一个房间的订单列表,里面包括了订单ID,订单使用的�房间的ID,开始日期和结束日期,根据这些字段,要把订单的信息填充到房间日历里面去。

    首先是确定哪些格子要被填充,被填充格子的横坐标用开始日期和结束日期来确定,因为横坐标就是14天的时间段,Y轴显示的是具体的�房间,可以通过订单的房间号去确定纵坐标。确定了横坐标和纵坐标之后,就能在那个位置画出订单的信息来了,canvas.drawRect()画一个矩形,来表示某一个房间的某一段时间被这个订单占用了,这样订单的展示就做完了。接下来是能够响应点击事件,触发空闲的房间被选中,从而能够预定某个房间的某个时间段。

    当手指点下去的时候就会触发MotionEvent.ACTION_DOWN事件,这个时候通过event.getX()event.getY()就能获取相对于当前View的坐标,�然后除以格子的宽高得到的整数,就能获得当前点击是那个位置的格子,�调用invalidate()来刷新整个View,我在这里是用一个二维数组来记录格子的点击状态的,�刷新View的时候,遍历这个二维数组,把选中状态的格子画上一个被选中的标记,如果之前已经被选中了,那就把选中取消掉,如果那个格子有订单,也是记录在二维数组中,就不响应这个点击事件。响应的是点击之后跳转到相应的订单详情页面。点击事件也做好了,接下来是横坐标的�日期列表,和纵坐标的房间列表的展示,和响应格子图的滑动,它们俩也跟着滑动。

    响应滑动其实很简单,复写当前ViewonScrollChanged()方法:

    @Override
        protected void onScrollChanged(int l, int t, int oldl, int oldt) {
            super.onScrollChanged(l, t, oldl, oldt);
            if (viewOne!= null) {
                viewOne.scrollTo(l, t);
            }
            if (viewTwo != null) {
                viewTwo.scrollTo(l, t);
            }
        }
    

    这样,纵坐标和横坐标都能跟着中间的格子来滑动了。具体代码在这里 代码还在不断修改中。

    相关文章

      网友评论

          本文标题:2年之后重新写的页面

          本文链接:https://www.haomeiwen.com/subject/hlcapxtx.html