月度归档:2015年07月

实习学习记录(一)

偷懒一下,打算写一个小系列记录实习期间学习到的知识点总结一下。

View绘制的流程

View和Activity一样其实也有自己的生命周期(这里的生命周期不是指像Activity一样有onPause,onResume这种一样,这里指代的是View的一辈子所要干的事的一种抽象概念)。不过View的生命周期远比Activity的生命周期简单。View的生命周期里主要做了三件事:Attachment/Detachment,遍历(英文是Traversal,View的树形结构遍历,里面包括了onMeasure,onLayout,onDraw等方法的递归遍历调用),保存恢复数据 。

关于Attachment:就像Fragment要依附于Activity一样它又一个onAttach的方法一样。View也有一个叫onAttachedToWindow的方法。在这个方法里面,我们一般进行资源分配,注册监听器。如果你看ListView的源代码你可以发现,google在这里对Adapter注册了监听器用于监听数据变化。对应的View还有onDetachedWindow,在这里一般把分配的资源释放掉,还有把监听器弄掉。

关于遍历:

View在你看到前系统会对它走如下的流程:

Animate->Measure->Layout->Draw

Measure主要是要确定自己到底要占多少位置,多少大小。比如你要知道match_parent和wrap_content的时候你到底需要多大。Layout就是需要确定 child view的显示位置。Draw就是对view进行绘制。

Measure里新手第一次见到的时候会被好多新东西吓到。我来一个个解说,你先是会发现有一个MeasureSpec的数据结构,这个是对View的宽高,还有测量模式的一个结构封装。可以用MeasureSpec.getSize,MeasureSpec.getMode来进行调用。这里的Mode有三种模式:

  • Exactly:比如你确切的给view一个大小如200dp,还有match_parent,LinerLayout里的weight这种都属于Exactly确切模式
  • AT_MOST:这里是Wrap_content的应用。
  • UNSPECIFIED:不确定模式,一般是在ListView和ScrollView这种内容长度不确定的东西。

这里其实我们再自己自定义控件的时候我们没必要考虑每一种模式的计算情况,因为毕竟是自己使用,你可以假定我们永远只用match_parent啊或者是永远wrap_content,所以你主需要写一种模式的测量方法即可。(当然除非你想确保你的控件的普遍适用性,你可以考虑每一种模式)

onLayout

这里是利用前面测量好的数据(getMeasureWidth和getMeasureHeight)来确认view里的child view的放置位置。这里要注意有意思的一点是,View.getWidth和View.getHeight只有在onLayout完之后才可以使用,不然在之前调用返回的都是0.(这种返回0的情况我想很多人在写自己自定义的ViewGroup还有在自己自定义的Adapter的时候都会遇到)那是不是在view显示过一次之前我们都无法获得view的宽高了呢?并不是呢,我们这里还有一个利器叫onPreDrawListener,这个监听器的触发时间将在onLayout之后和onDraw之前。

Draw:这里面用得最多得就是invalidate。和requestLayout类似,调用它以后会触发View树的遍历,就是对它的parent一直进行递归调用直到到了view root。这里注意invalidate是我在UI线程里调用的函数,如果我在非UI线程的时候需要调用postInvalidate。

状态保存:一般是通过Override,onSaveInstanceState和onRestoreInstanceState实现。

clipRect的使用:如果每次调用invalidate的时候都把整个view重绘,将是很耗性能的事情。我们可以通过规定每次需要重绘的区域(一个矩形Rect)让GPU知道每次我只需要对View的这部分显示进行更新就好。如下面这个例子,在这个自定义View包括了文字还有一个按钮,按钮包括一个进度条显示的功能。若果每次只需要更新的是按钮的进度,而重绘整个view就显得有点浪费。可以我们可以选择每次只更新那个按钮的位置。通过设置canvas.clipRect(Rect clipArea)即可。clip

StaticLayout用于在canvas里绘制文字

这个相当于隐藏的API,在canvas里我们绘文字的时候,drawText方法字符串有多长它就画多长,它不会自动换行。用StaticLayout可以解决这个问题。通过建立一个StaticLayout区域,在里面drawText是会自动换行的。TextView就是基于这个实现的。想实现drawText文字换行还有一招,只是有点麻烦思路如下:获取显示区域大小如DisplayWidth,然后测量用画笔画所需字符串将得到的宽度TextWidth(这里是通过调用Paint.getTextBounds(),或Paint。getTextWidths获得,区别是前者是返回int后者是float。后者更精准)如果TextWidth > DisplayWidth我们就对要显示的字符串进行substring然后在下一行的位置进行显示(下一行的位置也是我们自己计算的,用前面得到的TextBounds来获得getHeight)比较麻烦。代码我就不放了,实现效果大概如下:

QQ图片20150726112820

bezier

曲线的魔力——初识贝塞尔曲线

为什么我们需要贝塞尔曲线?

设想一下平时曲线对计算机来说是画出来的呢。无非就是要用一个函数y = f(x)来绘制曲线。但是我们能拿到手就用的函数曲线无非是sin,cos这种函数了,若我们想要更复杂的曲线,获取它的方程将会是很难的。

curve

如何产生这个曲线的方程呢?

这个时候,贝塞尔曲线的作用就来了。

什么是贝塞尔曲线?

贝塞尔曲线(英语:Bézier curve)是电脑图形学中相当重要的参数曲线。是用几个很少的控制点就可以产生平滑复杂的曲线的方法。我们经常在开发中用贝塞尔曲线来绘制曲线,或者控制动画的时间变化(擦除函数)。

下面展示一下贝塞尔曲线算法的绘制过程:

先定义三个点

贝塞尔1

 

在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例。

贝塞尔2

 

根据上一步得到的比例,从第二条线段上找出对应的点 E,使得 AD:AB = BE:BC

贝塞尔3

 

连接D,E得

贝塞尔4

 

从新的线段 DE 上再次找出相同比例的点 F,使得 DF:DE = AD:AB = BE:BC。

贝塞尔6如果把这个过程反复的做下去将形成一个曲线!

贝塞尔5

 

wiki上的动图效果如下:(好像要点开才看得到)

Bézier_2_big

 

当然控制点还可以有更多来操作出更复杂的曲线。

在Android里的应用

Android里的Path提供了画贝塞尔曲线的API,下面我来做一个简单介绍。

Path包含贝塞尔曲线的API主要包括,quadTo,cubicTo,rCubicTo,rQuadTo。

其中quadTo是有两个控制点的贝赛尔曲线,cubicTo有三个控制点。

例子:

cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)

/**
 * Created by Jiaqi Ning on 5/7/2015.
 */
public class PathView extends View {
    Paint paint;

    public PathView(Context context) {
        super(context);

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(0xff41be9d);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    //draw the curve
        Path path = new Path();
        path.moveTo(50,50);
        path.cubicTo(50,50,50,300,250,300);
        canvas.drawPath(path,paint);
// draw the control line
        path.reset();
        paint.setColor(Color.GRAY);
        paint.setStrokeWidth(2);
        path.moveTo(50,50);
        path.lineTo(50,300);
        path.lineTo(250,300);
        canvas.drawPath(path,paint);

    }
}

curve_screenshot更强大的用法,考完试再写吧 Orz…

参考: