Android开发进阶_创建丰富多彩的View读后笔记

图1

1.自定义View

自定义view有几种类型,分别为:继承自View的完全自定义;继承自系统控件比如ImageView实现特定的效果;还有就是实现ViewGroup实现布局类(也可将系统控件进行组合,组成自定义组合控件)。这里比较重要的就是View的测量与布局、View的绘制、触摸事件的处理、动画等。

1.自定义ImageView

这里我们实现一个自定义的ImageView,先上整体源码,再一步步来分析它。

 public SimpleImageView(Context context) {
    this(context, null);
}

public SimpleImageView(Context context, AttributeSet attrs) {
    super(context, attrs);

    initPaint();

    initAttrs(attrs);
}

private void initPaint() {
    mPaint = new Paint();
    //设置无锯齿
    mPaint.setAntiAlias(true);
}

private void initAttrs(AttributeSet attrs) {
    TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SimpleImageView);
    //通过xml属性获取drawable文件
    mDrawable = typedArray.getDrawable(R.styleable.SimpleImageView_src);

    typedArray.recycle();

    measureDrawable();
}

private void measureDrawable() {
    if (mDrawable != null) {
        //获取图片本来的宽度和高度
        mDrawableWidth = mDrawable.getIntrinsicWidth();
        mDrawableHeight = mDrawable.getIntrinsicHeight();
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //设置控件的宽高和图片一样大
    setMeasuredDimension(mDrawableWidth, mDrawableHeight);
}

@Override
protected void onDraw(Canvas canvas) {
    if (mDrawable == null) {
        return;
    }
    //将图片绘制出来
    canvas.drawBitmap(((BitmapDrawable) mDrawable).getBitmap(), getLeft(), getTop(), mPaint);
}

先来看看初始化initAttrs的代码吧
图1这里主要是通过自定义属性的TypedArray,然后通过属性名来获取相应属性所对应的值,获取然后要对TypedArray进行释放,因为TypedArray是比较消耗资源的,而且容易造成内存泄露。最后通过measureDrawble来测量图片的大小。
接下来我们来看看measureDrawable方法都做了些什么吧图1这里主要是获取到了图片的原始宽高

看完了初始化部分的代码,跟着代码的执行顺序,我们现在要看看onMeasure都做了些什么.图1这里主要是通过setMeasureDimension来设置自已的宽高。这里的宽高就是图片原始的大小。
设置完大小后,因为是View,所以不能进行onlayout的操作,就直接来看看onDraw方法。图1这里主要的操作就是将获取到的drawable画出来。这样就完成了图片的显示了。

这里有个问题,就是图片显示的大小一定就是图片的原始大小图片,也就是说只有在设置wrap_content布局后才能正确的显示。如果设置match_parent或者指定具体的宽高都是没有效果的。

2.view的尺寸测量

在视图树渲染时View系统的绘制流程会从RootViewperformTraversals方法中开始,在其内部调用Viewmeasure方法,这个measure()接收两个参数分别为widthMeasureSpecheightMeasureSpec,这两个参数分别用来确定视图的宽、高的规格和大小。onMeasure是在父视图调用的,也就是父视图对自己的期望。
MeasureSpec由两部分组成,specSizespecModespecSize代表期望子视图的大小,specMode代表规格。

3.specMode规格的3种类型

1.EXACTLY
表示父视图希望子视图的大小是由specSize决定的,系统默认为按照这个大小来进行设置。开发人员当然也可以按照自己的意愿来设置成任意的大小。此类型对应的布局方式是match_parent或者指定具体的宽高。
2.AT_MOST
表示子视图最多可以设置specSize中指定的大小,开发人员应该尽可能小的去设置这个视图,并且保证不会超过spceSize。系统默认会按照这个规则来设置子视图的大小,开发人员也可以按照自己的意愿来设置任意大小。这个类型对应的布局方式是wrap_content.
3.UNSPECIFIED
表示开发人员可以将视图设置成任意大小,没有任何限制,这种模式比较少见,不太会用到。

4.MeasureSpec的由来

在视图树的根部ViewRootImpl里的measureHierarchy()函数里measureSpce被创建。 图1这里可以看到MeasureSpec是通过getRootMeasureSpec函数来创建的。这个函数的第一个参数表示窗口的宽度或高度,第二个参数是在创建viewGroup时被赋值的。它们都等于match_parent。然后我们来看看getRootMeasureSpec函数吧。 图1这里可以看到MeasureSpec是通过MeasureSpec.makeMeasureSpec来进行创建的,而这里的参数windowSize都是填充屏幕,所以根视图都是占满整个屏幕的。
创建完MeasureSpec后,就调用performMeasure函数来一层一层的对视图树中的视图来进行测量了。当前View调用自己的measure方法,而在measure方法里会调用子ViewonMeasure方法,子view通过在onMeasure获得父类传来的measureSpec后,获得父类对自己的期望,从中得到specSizespecMode,然后再通过setDimension给自己设置宽和高。

5.优化后的自定义ImageView

我们来重构一下我们上面自定义的ImageViewonMeasure函数。图1这里通过widthMeasureSpecheightMeasureSpec分别获得宽和高的spceSize大小和specMode规格。再通过setDismension来设置当前view的宽和高。
图1通过规格来得到相应的宽和高。这里主要是当规格为EXACTLY,需要使用父类指定的宽和高来进行设置。

2.Canvs和Paint

canvs是画布的意思,在Android中,整个view就是一个画布,开发人员通过画笔在这张画布上画出各种图形,元素,例如:各种形状,文字或图片等。
paint是用来在canvs上绘制的工具,可以通过设置paint的属性来绘制不同的效果。

2.1 save和restore方法

对于canvs这里有两个比较重要的方法,saverestoresave方法保存的是当前canvas的状态,就像玩游戏里的存档一样,记录了当前的一个点。而restore就是恢复到当前保留的状态。

这里我们使用saverestore方法来在之前的自定义ImageView上实现竖直显示的文字。这里我们只需要修改onDraw里面的代码。图1先保存canvs的状态,然后将canvs旋转90度,旋转后在来画文字,这个时候canvs是横着的,而文字同样也是横着的。最后将canvs恢复到以前的竖着的状态,此时文字自然也就变成了竖着的了。

3.Scroller的使用

Scroller是一个让view平滑滚动的辅助类,它通过startScroll来设置滚动的参数,即滚动起始点坐标(x,y)以及x,y轴上要滚动的距离。Scroller它封装了滚动时间、滚动目标(x,y)轴坐标点以及每一个时间内View应该滚动到的(x、y)轴坐标点。这样用户就可以在有效的滚动周期内通过scrollergetCurrXgetCurrY来获得当前时间点需要滚动的位置,然后通过scrollToscrollBy去滚动到相应的位置。
什么时候滚动停止?
我们覆写view里的computeScroll方法,这个方法是每次view绘制都是被调用的。在此方法里通过scrollercomputeScrollOffset来进行判断是否滚动完成。当返回true说明还没有完成,在还没有完成的情况下,我们通过调用scrollergetCurrX或者getCurrY来获取当前时刻需要滚动到目标的位置,然后通过scrollToscrollBy进行滚动。每次执行完后,还需要调用postInvalidateInvalidate导致View重绘,如此循环,直到computeScrollOffset返回false,也就是滚动完成为此。

3.1 ScrollerView例子

先上源码

public class ScrollerLayout extends FrameLayout {
private Scroller mScroller;

public ScrollerLayout(Context context) {
    this(context, null);
}

public ScrollerLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    mScroller = new Scroller(context);
}

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        //说明还未完成
        //滚动到此,View应该滚动到的x,y坐标上
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        //请求重新绘制,导致computeScroll方法又会被执行,直到computeScrollOffset返回为false
        //说明滚动完成
        postInvalidate();
    }
}

/**
 * 向外提供一个y轴滚动的方法
 *
 * @param y
 */
public void scrollTo(int y) {

    //第1,2参数分别为滚动的起始点水平,垂直的滚动偏移量
    //第3,4参数分别为在水平,垂直方向上滚动的距离,这里只沿Y轴滚动
    mScroller.startScroll(getScrollX(), getScrollY(), 0, y);

    invalidate();
}
}

上面执行的顺序是,先完成构造方法的执行,初始化scroller实例,然后提供了一个供调用的方法,当函数被调用后,首先通过startScroll封装了初始点及要滚动的目标点。然后通过invalidate导致view的重绘,以使computeScroll被执行,从而通过computeScrollOffset来计算是否滚动完成,如果未完成,则通过getCurrY获取当前时刻需要滚动的目标点,最后通过invalidateview的绘制循环起来,直到computeScrollOffset返回false为止。

4动画

动画分为frame动画(帧动画)、补间动画、属性动画以及5.0后又增加的vectorDrawable动画。

4.1 Frame动画(帧动画)

帧动画是一系列图片按照一定的顺序进行展示的过程,和放电影的机制相似,它的原理是在一定的时间段内切换多张有细微差异的图片从而达到动画播放的结果。
帧动画可以使用xml来实现,也可以完全使用代码实现。使用xml实现时,需要放在res目录下的drawable目录下,当使用xml时,文件名可以通过资源id来引用,如果使用完全编码实现,需要使用AnimationDrawable对象。如果使用xml来编码时,根元素需要使用animation-list,它可以包含1或者n个<item>元素.android:onshot如果设置为true说明只播放一次,如果为false则循环播放。<item>表示一帧动画,有两个属性,一个android:drawable表示当前帧要显示的图片,另一个android:duration表示当前帧播放的时间,单位为毫秒。
xml格式:
图1定义好之后,我们还需要把动画设置给某个view。例如,这里设置给imageView作为背景。图1但是,此时动画仍然还是不会播放,想要播放需要得到动画后调用start方法让其播放。图1
使用代码编码完成:图1

4.2补间动画(tween动画)

补间动画就是通过对某个控件进行位移、翻转、渐变、缩放的一种转换过程,补间动画也分为xml或者代码实现两种方式。
xml实现
图1其中set表示动画集合,如果只是做单独的动画则不需要此元素,直接使用内部元素即可。
        <alpha>表示渐变动画;与之对应的Java对象为alphaAnimation.android:fromAlpha表示起始alpha值,取值范围为0.1-1.0。分别代表从透明到不透明。android:toAlpha表示结尾的alpha值。
        <scale>表示缩放动画;与之对应的Java对象为ScaleAnimationandroid:fromXScale表示起始的x轴上相对自身的缩放比例。比如1.0代表自身无变化,2.0表示比自身大一倍。android:toXScale表示结尾的缩放比例。android:pivotX表示缩放的中轴点X坐标,如果想位于图片x轴的中间就可以使用0.5或者50%。
        <translate>表示位移动画;对应Java的对象为TranslateAnimation.android:fromXDelta表示起始x轴的位置;android:toDelta表示结尾x轴的位置。这里的四个参数都可以使用浮点数、num%、num%p这三种来表示。浮点数表示相对于自身位置的浮点数;num%表示相对自身位置的百分比,比如toXDelta设置为100%,表示移动距离为自己的1倍。num%p表示相对父类位置的百分比。
        rotate表示旋转动画;对应Java的对象为RotateAnimationandroid:fromDegrees表示旋转的起始角度,浮点值,单位:度。android:pivotY的值也有三种表示方式,浮点数、num%、num%p。浮点数表示旋转中心距离自身左边缘或者上边缘的距离;num%表示旋转中心距离自身左边缘或上边缘的百分比;num%p表示旋转中心距离距离父类左边缘或上边缘的百分比。

5属性动画

        属性动画是Android3.0后推出来的动画包,属性动画不在是针对View来进行设计的,也不在只是alphascaletranslaterotate这几种简单的动画操作,同时也不在仅仅是一种视觉上的操作。属性动画是通过不断的修改对象的属性的值来实现的。属性动画可以对任何对象的属性来做一个不断改变的动画操作。我们只需要告诉系统动画要操作的属性、动画时长、需要执行哪种类型的动画以及动画初始值和结束值,剩下的操作就交给系统来完成了。

5.1 valueAnimation

valueAnimation是属性动画的核心类,valueAnimation内部使用一种循环机制来计算值与值之间的动画过渡,我们只需要设置属性的取值范围,动画的运行时长提供给valueAnimation,它就会自动帮我们计算出属性值在各个动画运行时间段的取值。这些值会按照一定的计算方式让动画实现平滑移动。除此之外,valueAnimation还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。我们通过一个例子来看看valueAnimation的实现吧。图1其实属性动画就是模拟一段动画属性值不断变化的操作,我们通过这个变化,在不同的时间点获取到相应的变化的属性值来进行属性的设置,以此来完成动画。

5.2 ObjectAnimation

valueAnimation虽然强大,但它并没有直接对某个对象的某个属性值进行修改,只是模拟了属性值的变化。我们只能通过设置监听值的变化再来手动的设置对某个对象的某个属性进行修改来达到动画的效果。这显然多转了一个弯,用起来就没有那么的直接、方便。ObjectAnimation就是在继承valueAnimation的基础上,进行了一层封装,让我们可以直接在创建ObjectAnimation对象的时候,通过ofInt或者ofFolat等静态工厂函数来直接进行对某个对象属性的值的修改来完成动画,这样更加的方便、直接。比如对ImageView做一个从0.1到1.0的渐变动画。图1这里的ofFloat静态工厂方法接收至少四个参数,第一个参数为要操作的对象;第二个参数为对象的属性名,这里的属性名是要对象能够通过setXXX的方式来进行设置的属性名,而且此属性的setXXX方法只接收一个参数的才可以。剩下的参数是可变参数,也就是长度是可变的,最少需要设置两个参数,也就是动画的起始参数,和结尾参数。如果设置多个参数,则会在这些参数中进行平滑的动画过渡。

5.3 AnimatorSet(动画合集)

有时候我们要将多个动画合在一起执行,这时就需要AnimatorSet来组合这些动画。此合集可以属性动画进行组合。AnimatorSet提供了一个play的方法,此方法接收一个Animator对象,然后返回一个Animator.Builder实例。此实例包括5个核心方法:
1.after(Animator anim)
在anim动画执行完后,再执行前面调用after方法的动画。
2.after(long delay)
将调用after的动画延迟多少时间后再执行。
3.befroe(Animator anim)
在anim动画执行之前执行调用before的动画。
4.with(Animator anim)
将调用with的动画和anim动画一起执行。
5.playTogather(Animator… anims)
将多个动画一起执行。
我们来看看下面的例子:图1这里首先执行after参数里的动画anim3,然后anim1和anim2一起执行。

5.4 TypeEvaluator和TimeInterpolator

       动画的实现是通过不断的更改属性的某个值。而在某一时间点这个值是通过什么来确定的呢?答案是TypeEvaluator类型估值器。
图1这里的TypeEvaluator接口里只有一个evaluate方法,参数1(fraction)为动画已执行时间与总时间的比值;参数2(startValue)表示动画开始属性值;参数3(endValue)表示动画结束的属性值。
       TypeEvaluator的原理就是通过动画已执行时间与总时间的比值来计算某个时间点属性的取值。通常TypeEvaluator的计算公式为:图1也就是动画开始时的属性值+总时间/动画已执行时间 动画结束属性值与动画开始属性值的差值来得到当前时间点属性的取值。下面是我们实现的一个默认的TypeEvaluator类型估值器:图1
代码使用我们自定义的TypeEvaluator为:图1
如果使用这样子的方式来实现,动画在每个时间段所取到的时间差值基本上是一样的,也就是动画可能是匀速运动的。但是我们有时间有不同的一个需求,需要动画成非线性运动,这个时间就需要使用我们的TimeInterpolator时间插值器来完成了。
TimeInterpolator通过改变fraction动画已执行时间与总时间的比值来完成动画的不同方式的运动。 图1时间插值器会在TypeEvaluator之前执行,TimeInterpolator主要做的事情只有一件,就是修改TypeEvaluator中方法evaluate的参数fraction的值。系统提供了几种已实现的时间插值器。
1.AccelerateDecelerateInterpolator
加速减速插值器:开始和结束的地方速率改变比较慢,在中间的时候加速。图1其实现为:图1
2.AccelerateInterpolator
加速插值器:开始的地方速度比较慢,然后开始加速图1其实现为:图1
3.AnticipateInterpolator
开始的时候向后,然后向前甩图1其实现为:图1
4.AnticipateOvershootInterpolator
开始的时候向后然后向前甩一定值后返回最后的值图1其实现为:图1
4.BounceInterpolator
动画结束的时候弹起图1其实现为:图1
5.CycleInterpolator
循环播放特定的次数,速率改变沿着正弦曲线图1其实现为: 图1
6.DecelerateInterpolator
在开始的地方快然后走来越慢图1 其实现为:图1
此插值器创建的时候可以传一个factor图1如传2f图1
7.LinearInterpolator
以常量速率运行。图1其实现为:图1也就是没有改变其时间值。
*8. OvershootInterpolator

向前甩一定值后回到原点位置图1其实现为:图1OvershootInterpolator可以传入一个值,来进行修改。图1下面为传入0.8f的显示:图1

当然我们也可以实现TimeInterpolatorgetInterpolation方法来自定义插值器。

好了,到这里,第二个章的内容就分享上这里的。