美文网首页自定义控件
Android基础(5)—自定义View

Android基础(5)—自定义View

作者: 危君子频道 | 来源:发表于2020-06-12 23:28 被阅读0次

    自定义View

    基本认知:

    虽然Android已经自带来很多强大的UI控件,但是依旧不能满足所有开发人员的需求。通常开发人员需要实现设计师精心设计的视觉效果,这样情况下可能现有的控件就不能满足需求或者说使用现有的控件实现起来成本太高。这时候,我们只能通过自定义View实现。因此,自定义View成为了Android开发人员必须掌握的最重要技能之一。自定义View的实现方式有几种,重写View无疑是最自由的,同时难度也是最大的。核心步骤分别为尺寸测量与绘制,对应的方法为onMeasure()、onDraw()。

    自定义View/ViewGroup的步骤:

    1.自定义属性;

    2.选择和设置构造方法;

    3.重写onMeasure()方法;

    4.重写onDraw()方法;

    5.重写onLayout()方法;

    6.重写其他事件的方法(滑动监听等)。

    步骤详解:

    自定义属性:

    定义自定义属性:

    通常将自定义属性定义在/values/attr.xml文件中(attr.xml文件需要自己创建)。

    <?xml version="1.0" encoding="utf-8"?
    <resources
        <attr name="rightPadding" format="dimension" /
    
        <declare-styleable name="CustomMenu"
            <attr name="rightPadding" /
        </declare-styleable
    </resources
    

    可以看到,我们先是定义了一个属性rightPadding,然后又在CustomMenu中引用了这个属性。下面说明一下:

    1、首先,在declare-stylable标签中直接定义属性而不需要引用外部定义好的属性,但是为了属性的重用,可以选择上面的这种方法:先定义,后引用;

    2、declare-stylable标签只是为了给自定义属性分类。一个项目中可能又多个自定义控件,但只能有一个attr.xml文件,因此需要对不同自定义控件中的自定义属性进行分类,所以declare-stylable标签中的name属性往往定义成自定义控件的名称;

    3、在declare-stylable标签中的引用,就是去掉了外部定义的format属性,如果没有去掉format,则会报错;如果外部定义中没有format而在内部引用中有format,也一样会报错。

    常见的format类型:

    string:字符串类型;
    integer:整数类型;
    float:浮点型;
    dimension:尺寸,后面必须跟dp、dip、px、sp等单位;
    Boolean:布尔值;
    reference:引用类型,传入的是某一资源的ID,必须以“@”符号开头;
    color:颜色,必须是“#”符号开头;
    fraction:百分比,必须是“%”符号结尾;
    enum:枚举类型
    

    下面对format类型说明几点:

    1、format中可以写多种类型,中间使用“|”符号分割开,表示这几种类型都可以传入这个属性

    2、enum类型的定义示例如下代码所示:

    <resources
        <attr name="orientation"
            <enum name="horizontal" value="0" /
            <enum name="vertical" value="1" /
        </attr
    
        <declare-styleable name="CustomView"
            <attr name="orientation" /
        </declare-styleable
    </resources
    

    使用时通过getInt()方法获取到value并判断,根据不同的value进行不同的操作即可。

    使用自定义属性:

    在XML布局文件中使用自定义的属性时,需要先定义一个namespace。Android中默认的namespace是android,因此通常可以使用“android:xxx”的格式去设置一个控件的某个属性,android这个namespace的定义是在XML文件的头标签中定义的,通常是这样的:

    xmlns:android="http://schemas.android.com/apk/res/android"
    

    自定义的属性不在这个命名空间下,因此需要添加一个命名空间。自定义属性的命名空间如下:

    xmlns:app="http://schemas.android.com/apk/res-auto"
    

    可以看出来,除了将命名空间的名称从android改成app之外,就是将最后的“res/android”改成了“res-auto”。

    注意:自定义namespace的名称可以自己定义,不一定非得是app。

    获取自定义属性:

    在自定义View / ViewGroup中,可以通过TypedArray获取到自定义的属性。示例代码如下:

    public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context
        .getTheme()
        .obtainStyledAttributes(attrs, R.styleable.CustomMenu, defStyleAttr, 0);
        
        int indexCount = a.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.CustomMenu_rightPadding:
                    mMenuRightPadding = a.getDimensionPixelSize(attr, 0);
                    break;
            }
        }
        a.recycle();
    }
    

    说明:

    1、获取自定义属性的代码通常是在三个参数的构造方法中编写的;

    2、在获取TypedArray对象时就为其绑定了该自定义View的自定义属性集(CustomMenu),通过getIndexCount()方法获取到自定义属性的数量,通过getIndex()方法获取到某一个属性,最后通过switch语句判断属性并进行相应的操作;

    3、在TypedArray使用结束后,需要调用recycle()方法回收它。

    构造方法:

    当定义一个新的类继承了View/ViewGroup时,系统都会提示重写它的构造方法。View/ViewGroup中有四个构造方法可以重写,它们分别有一、二、三、四个参数。四个参数的构造方法我们通常用不到。

    一个参数的构造方法:

    构造方法的代码: public CustomMenu(Context context) { …… } 
    

    这个构造方法只有一个参数Context上下文。当我们在JAVA代码中直接通过new关键字创建,在创建这个控件时,就会调用这个方法。

    两个参数的构造方法:

    构造方法的代码: public CustomMenu(Context context, AttributeSet attrs) { …… } 
    

    这个构造方法有两个参数:Context上下文和AttributeSet属性集。当需要在自定义控件中获取属性时,就默认调用这个构造方法。AttributeSet对象就是这个控件中定义的所有属性。

    我们通过AttributeSet对象的getAttributeCount()方法获取属性的个数,通过getAttributeName()方法获取到某条属性的名称,通过getAttributeValue()方法获取到某条属性的值。

    注意:不管有没有使用自定义属性,都会默认调用这个构造方法。

    三个参数的构造方法:

    构造方法的代码: public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { …… } 
    

    这个构造方法中有三个参数:Context上下文、AttributeSet属性集和defStyleAttr自定义属性的引用。这个构造方法不会默认调用,必须要手动调用,这个构造方法和两个参数的构造方法的唯一区别就是这个构造方法给我们默认传入了一个默认属性集。

    defStyleAttr指向的是自定义属性的<declare-styleable标签中定义的自定义属性集,我们在创建TypedArray对象时需要用到defStyleAttr。

    三个构造方法的整合:

    一般情况下,会将这三个构造方法串联起来,即层层调用,让最终的业务处理都集中在三个参数的构造方法。让一参的构造方法引用两参的构造方法,两参的构造方法引用三参的构造方法,就可以保证无论使用什么方式创建这个控件,最终都会到三个参数的构造方法中处理,减少了重复代码。示例代码如下:

    public CustomMenu(Context context) {
        this(context, null);
    }
    
    public CustomMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 业务代码
    }
    

    onMeasure():

    onMeasure()方法中主要负责测量,决定控件本身或其子控件所占的宽高。可以通过onMeasure()方法提供的参数widthMeasureSpec和heightMeasureSpec来分别获取控件宽度和高度的测量模式测量值(测量 = 测量模式 + 测量值)。

    widthMeasureSpec和heightMeasureSpec虽然只是int类型的值,但它们是通过MeasureSpec类进行了编码处理的,其中封装了测量模式和测量值,因此可以分别通过MeasureSpec.getMode(xMeasureSpec)和MeasureSpec. getSize(xMeasureSpec)来获取到控件或其子View的测量模式和测量值。

    onMeasure()方法中常用的方法:

    1、getChildCount()
    //获取子View的数量;
    2、getChildAt(i)
    //获取第i个子控件;
    3、subView.getLayoutParams().width/height
    //设置或获取子控件的宽或高;
    4、measureChild(child, widthMeasureSpec, heightMeasureSpec)
    //测量子View的宽高;
    5、child.getMeasuredHeight/width()
    //执行完measureChild()方法后就可以通过这种方式获取子View的宽高值;
    6、getPaddingLeft/Right/Top/Bottom()
    //获取控件的四周内边距;
    7、setMeasuredDimension(width, height)
    //重新设置控件的宽高。如果写了这句代码,就需要删除“super.onMeasure(widthMeasureSpec, heightMeasureSpec);”这行代码。
    

    onDraw():

    onDraw()方法负责绘制,每次触摸了自定义View/ViewGroup时都会触发onDraw()方法。

    Paint类:

    Paint画笔对象,这个类中包含了如何绘制几何图形、文字和位图的样式和颜色信息,指定了如何绘制文本和图形。画笔对象右很多设置方法,大体上可以分为两类:一类与图形绘制有关,一类与文本绘制有关。

    1、图像绘制:

    1)  setArgb(int a, int r, int g, int b):设置绘制的颜色,a表示透明度,r、g、b表示颜色值;
    2)  setAlpha(int a):设置绘制的图形的透明度;
    3)  setColor(int color):设置绘制的颜色;
    4)  setAntiAlias(boolean a):设置是否使用抗锯齿功能,抗锯齿功能会消耗较大资源,绘制图形的速度会减慢;
    5)  setDither(boolean b):设置是否使用图像抖动处理,会使图像颜色更加平滑饱满,更加清晰;
    6)  setFileterBitmap(Boolean b):设置是否在动画中滤掉Bitmap的优化,可以加快显示速度;
    7)  setMaskFilter(MaskFilter mf):设置MaskFilter来实现滤镜的效果;
    8)  setColorFilter(ColorFilter cf):设置颜色过滤器,可以在绘制颜色时实现不同颜色的变换效果;
    9)  setPathEffect(PathEffect pe):设置绘制的路径的效果;
    10) setShader(Shader s):设置Shader绘制各种渐变效果;
    11) setShadowLayer(float r, int x, int y, int c):在图形下面设置阴影层,r为阴影角度,x和y为阴影在x轴和y轴上的距离,c为阴影的颜色;
    12) setStyle(Paint.Style s):设置画笔的样式:FILL实心;STROKE空心;FILL_OR_STROKE同时实心与空心;
    13) setStrokeCap(Paint.Cap c):当设置画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式;
    14) setStrokeJoin(Paint.Join j):设置绘制时各图形的结合方式;
    15) setStrokeWidth(float w):当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度;
    16) setXfermode(Xfermode m):设置图形重叠时的处理方式;
    

    2、文本绘制:

    1)  setTextAlign(Path.Align a):设置绘制的文本的对齐方式;
    2)  setTextScaleX(float s):设置文本在X轴的缩放比例,可以实现文字的拉伸效果;
    3)  setTextSize(float s):设置字号;
    4)  setTextSkewX(float s):设置斜体文字,s是文字倾斜度;
    5)  setTypeFace(TypeFace tf):设置字体风格,包括粗体、斜体等;
    6)  setUnderlineText(boolean b):设置绘制的文本是否带有下划线效果;
    7)  setStrikeThruText(boolean b):设置绘制的文本是否带有删除线效果;
    8)  setFakeBoldText(boolean b):模拟实现粗体文字,如果设置在小字体上效果会非常差;
    9)  setSubpixelText(boolean b):如果设置为true则有助于文本在LCD屏幕上显示效果;
    

    Canvas类:

    Canvas即画布,其上可以使用Paint画笔对象绘制很多东西。

    Canvas对象中可以绘制:

    1)  drawArc():绘制圆弧;
    2)  drawBitmap():绘制Bitmap图像;
    3)  drawCircle():绘制圆圈;
    4)  drawLine():绘制线条;
    5)  drawOval():绘制椭圆;
    6)  drawPath():绘制Path路径;
    7)  drawPicture():绘制Picture图片;
    8)  drawRect():绘制矩形;
    9)  drawRoundRect():绘制圆角矩形;
    10) drawText():绘制文本;
    11) drawVertices():绘制顶点。
    

    Canvas对象的其他方法:

    1)  canvas.save():把当前绘制的图像保存起来,让后续的操作相当于是在一个新图层上绘制;
    2)  canvas.restore():把当前画布调整到上一个save()之前的状态;
    3)  canvas.translate(dx, dy):把当前画布的原点移到(dx, dy)点,后续操作都以(dx, dy)点作为参照;
    4)  canvas.scale(x, y):将当前画布在水平方向上缩放x倍,竖直方向上缩放y倍;
    5)  canvas.rotate(angle):将当前画布顺时针旋转angle度。
    

    onLayout():

    onLayout()方法负责布局,大多数情况是在自定义ViewGroup中才会重写,主要用来确定子View在这个布局空间中的摆放位置。onLayout(boolean changed, int l, int t, int r, int b)方法有5个参数,其中changed表示这个控件是否有了新的尺寸或位置;l、t、r、b分别表示这个View相对于父布局的左/上/右/下方的位置。

    以下是onLayout()方法中常用的方法:

    1)  getChildCount():获取子View的数量;
    2)  getChildAt(i):获取第i个子View
    3)  getWidth/Height():获取onMeasure()中返回的宽度和高度的测量值;
    4)  child.getLayoutParams():获取到子View的LayoutParams对象;
    5)  child.getMeasuredWidth/Height():获取onMeasure()方法中测量的子View的宽度和高度值;
    6)  getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距;
    7)  child.layout(l, t, r, b):设置子View布局的上下左右边的坐标。
    

    其他方法:

    generateLayoutParams():

    generateLayoutParams()方法用在自定义ViewGroup中,用来指明子控件之间的关系,即与当前的ViewGroup对应的LayoutParams。我们只需要在方法中返回一个我们想要使用的LayoutParams类型的对象即可。在generateLayoutParams()方法中需要传入一个AttributeSet对象作为参数,这个对象是这个ViewGroup的属性集,系统根据这个ViewGroup的属性集来定义子View的布局规则,供子View使用。

    onTouchEvent():

    onTouchEvent()方法用来监测用户手指操作。通过方法中MotionEvent参数对象的getAction()方法来实时获取用户的手势,有UP、DOWN和MOVE三个枚举值,分别表示用于手指抬起、按下和滑动的动作。每当用户有操作时,就会回掉onTouchEvent()方法

    onScrollChanged():

    如果自定义View / ViewGroup是继承自ScrollView / HorizontalScrollView等可以滚动的控件,就可以通过重写onScrollChanged()方法来监听控件的滚动事件。这个方法中有四个参数:l和t分别表示当前滑动到的点在水平和竖直方向上的坐标;oldl和oldt分别表示上次滑动到的点在水平和竖直方向上的坐标。我们可以通过这四个值对滑动进行处理,如添加属性动画等。

    invalidate():

    nvalidate()方法的作用是请求View树进行重绘,即draw()方法,如果视图的大小发生了变化,还会调用layout()方法。

    一般会引起invalidate()操作的函数如下:

    1)  直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身;
    2)  调用setSelection()方法,请求重新draw(),但只会绘制调用者本身;
    3)  调用setVisibility()方法,会间接调用invalidate()方法,继而绘制该View;
    4)  调用setEnabled()方法,请求重新draw(),但不会重新绘制任何视图,包括调用者本身。
    

    postInvalidate():

    功能与invalidate()方法相同,只是postInvalidate()方法是异步请求重绘视图。

    requestLayout():

    requestLayout()方法只是对View树进行重新布局layout过程(包括measure()过程和layout()过程),不会调用draw()过程,即不会重新绘制任何视图,包括该调用者本身。

    requestFocus():

    请求View树的draw()过程,但只会绘制需要重绘的视图,即哪个View或ViewGroup调用了这个方法,就重绘哪个视图。

    小结:

    函数调用顺序.png

    1、onMeasure()会在初始化之后调用一到多次来测量控件或其中的子控件的宽高;

    2、onLayout()会在onMeasure()方法之后被调用一次,将控件或其子控件进行布局;

    3、onDraw()会在onLayout()方法之后调用一次,也会在用户手指触摸屏幕时被调用多次,来绘制控件。

    实现的三种方式:

    1.继承控件:继承现有控件,对其控件的功能进行拓展

    2.组合控件:将现有控件进行组合,实现功能更加强大的控件

    3.自绘控件:重写View,实现全新的控件

    自定义控件代码:

    public class CounterView extends View implements View.OnClickListener {
    
        private Paint mPaint;
        private Rect mRect;//获取文字的宽高
        private int mCounter;
    
        public CounterView(Context context) {
            super(context);
        }
    
        public CounterView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            //初始化画笔、Rect
            mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
            mRect=new Rect();
            setOnClickListener(this);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            mPaint.setColor(Color.CYAN);
            //绘制一个填充色为青蓝色的圆形
            canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
    
            mPaint.setColor(Color.WHITE);
            mPaint.setTextSize(150);
            String text=String.valueOf(mCounter);
            //获取文字的宽高
            mPaint.getTextBounds(text,0,text.length(),mRect);
            float textWidth=mRect.width();
            float textHeight=mRect.height();
            //绘制字符串
            canvas.drawText(text,getWidth()/2-textWidth/2,getHeight()/2+textHeight/2,mPaint);
        }
    
    
        @Override
        public void onClick(View v) {
            mCounter++;
            invalidate();  //重绘
        }
    }
    

    在MainActivity中加载activity_main.xml布局,布局中使用刚才绘制的自定义View

    <?xml version="1.0" encoding="utf-8"?
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity"
    
    
        <com.example.jimyoungwei.mytestapp_1_day_1.CounterView
            android:id="@+id/counter_view"
            android:layout_width="130dp"
            android:layout_height="130dp"
            android:layout_gravity="center"
            android:layout_margin="20dp" /
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="20dp"
            android:gravity="bottom"
            android:text="点击矩形按钮可以重新绘制View" /
    
    </LinearLayout
    

    效果图:

    效果图.png

    动画

    基本认知:

    Android系统提供了很多丰富的API去实现UI的2D与3D动画,最主要的划分可以分为如下几类:

    View Animation(视图/补间动画): 视图动画在古老的Android版本系统中就已经提供了,只能被用来设置View的动画。

    Frame Animation(帧动画): 其实可以划分到视图动画的类别,专门用来一个一个的显示Drawable的resources,就像放幻灯片一样。

    Property Animation(属性动画): 属性动画只对Android 3.0(API 11)以上版本的Android系统才有效,这种动画可以设置给任何Object,包括那些还没有渲染到屏幕上的对象。这种动画是可扩展的,可以让你自定义任何类型和属性的动画。

    补间动画:

    属性详解:

    在补间动画中主要可以实现视图的位移(translate)、缩放(scale)、旋转(rotate)、淡入淡出(alpha)四种效果。其各的主要属性如下:

    Animation属性详解

    xml属性 java方法 解释
    android:detachWallpaper setDetachWallpaper(boolean) 是否在壁纸上运行
    android:duration setDuration(long) 动画持续时间,毫秒为单位
    android:fillAfter setFillAfter(boolean) 控件动画结束时是否保持动画最后的状态
    android:fillBefore setFillBefore(boolean) 控件动画结束时是否还原到开始动画前的状态
    android:interpolator setInterpolator(Interpolator) 设定插值器(指定的动画效果,譬如回弹等)
    android:repeatCount setRepeatCount(int) 重复次数
    android:repeatMode setRepeatMode(int) 重复类型有两个值,reverse表示倒序回放,restart表示从头播放
    android:startOffset setStartOffset(long) 调用start函数之后等待开始运行的时间,单位为毫秒
    android:zAdjustment setZAdjustment(int) 表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal

    Alpha属性详解

    xml属性 java方法 解释
    android:fromAlpha AlphaAnimation(float fromAlpha, …) 动画开始的透明度(0.0到1.0,0.0是全透明,1.0是不透明)
    android:toAlpha AlphaAnimation(…, float toAlpha) 动画结束的透明度,同上

    Rotate属性详解

    xml属性 java方法 解释
    android:fromDegrees RotateAnimation(float fromDegrees, …) 旋转开始角度,正代表顺时针度数,负代表逆时针度数
    android:toDegrees RotateAnimation(…, float toDegrees, …) 旋转结束角度,正代表顺时针度数,负代表逆时针度数
    android:pivotX RotateAnimation(…, float pivotX, …) 缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
    android:pivotY RotateAnimation(…, float pivotY) 缩放起点Y坐标,同上规律

    Translate属性详解

    xml属性 java方法 解释
    android:fromXDelta TranslateAnimation(float fromXDelta, …) 起始点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
    android:fromYDelta TranslateAnimation(…, float fromYDelta, …) 起始点Y轴从标,同上规律
    android:toXDelta TranslateAnimation(…, float toXDelta, …) 结束点X轴坐标,同上规律
    android:toYDelta TranslateAnimation(…, float toYDelta) 结束点Y轴坐标,同上规律

    Scale属性详解

    xml属性 java方法 解释
    android:fromXScale ScaleAnimation(float fromX, …) 初始X轴缩放比例,1.0表示无变化
    android:toXScale ScaleAnimation(…, float toX, …) 结束X轴缩放比例
    android:fromYScale ScaleAnimation(…, float fromY, …) 初始Y轴缩放比例
    android:toYScale ScaleAnimation(…, float toY, …) 结束Y轴缩放比例
    android:pivotX ScaleAnimation(…, float pivotX, …) 缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
    android:pivotY ScaleAnimation(…, float pivotY) 缩放起点Y坐标,同上规律

    xml文件中定义

    xml定义动画

    <?xml version="1.0" encoding="utf-8"?
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        <scale
            android:fromXScale="1.0"
            android:toXScale="1.4"
            android:fromYScale="1.0"
            android:toYScale="0.6"
            android:pivotX="50%"
            android:pivotY="50%"
            android:fillAfter="false"
            android:duration="700" /
    </set
    

    在java使用动画资源

    Animation animation = AnimationUtils.loadAnimation(context,R.anim.view_anin);
    image.startAnimation(animation);
    

    java文件中定义

    RotateAnimation rotateAnimation = new RotateAnimation(0,180);
    rotateAnimation.setDuration(400);
    image.startAnimation(rotateAnimation);
    

    帧动画:

    它通过加载图片资源,将多张图片一帧一帧显示形成动画效果。在Android中提供了两种方式为AnimationDrawable添加帧:XML定义的资源文件和Java代码创建。

    AnimationDrawable的常用方法

    void start():开始播放逐帧动画。

    void stop():停止播放逐帧动画。

    void addFrame(Drawable frame,int duration):为AnimationDrawable添加一帧,并设置持续时间。

    int getDuration(int i):得到指定index的帧的持续时间。

    Drawable getFrame(int index):得到指定index的帧Drawable。

    int getNumberOfFrames():得到当前AnimationDrawable的所有帧数量。

    boolean isOneShot():当前AnimationDrawable是否执行一次,返回true执行一次,false循环播放。

    boolean isRunning():当前AnimationDrawable是否正在播放。

    void setOneShot(boolean oneShot):设置AnimationDrawable是否执行一次,true执行一次,false循环播放。

    在xml文件中定义资源文件

    1.xml文件中定义

    <?xml version="1.0" encoding="utf-8"?
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false"
        <item android:drawable="@drawable/loading_1" android:duration="100"/
        <item android:drawable="@drawable/loading_2" android:duration="100"/
        <item android:drawable="@drawable/loading_3" android:duration="100"/
        <item android:drawable="@drawable/loading_4" android:duration="100"/
    </animation-list
    

    2.在java中调用

    // 通过逐帧动画的资源文件获得AnimationDrawable示例
            AnimationDrawable animationDrawable = (AnimationDrawable) getResources().getDrawable(R.drawable.animation_loading);
            image.setImageDrawable(animationDrawable);
            animationDrawable.start();
    

    使用java定义资源文件

    AnimationDrawable drawable = new AnimationDrawable();
    drawable.addFrame(getResources().getDrawable(R.drawable.loading_1),100);
    drawable.addFrame(getResources().getDrawable(R.drawable.loading_2),100);
    drawable.addFrame(getResources().getDrawable(R.drawable.loading_3),100);
    drawable.addFrame(getResources().getDrawable(R.drawable.loading_4),100);
    drawable.setOneShot(true);
    image.setImageDrawable(drawable);
    drawable.start();
    

    属性动画:

    特点:

    Duration:可以指定动画的持续时间。默认长度为300毫秒;

    Time interpolation:可以指定该属性的值如何作为动画的当前的运行时间的函数来计算;

    Repeat count and behavior:指定动画的播放次数,还可定义是否反向播放动画;

    Animator sets:动画集合,即可以同时对一个对象应用多个动画,这些动画可以同时播放也可以对不同动画设置不同的延迟;

    Frame refresh delay:多少时间刷新一次动画,即每隔多少时间计算一次属性值,默认10ms。最终取决于系统整体多么繁忙,系统如何能够快速的服务基础计时器。

    工作原理:

    属性动画原理.png

    ValueAnimator:是动画的执行类,跟踪动画的时间,并且它是动画的属性的当前值。

    TimeInterpolator:ValueAnimator封装了动画的TimeInterpolator时间插值器,定义动画的插入。

    TypeEvaluator:用于设置动画属性的值。

    要启动动画,需要创建一个ValueAnimator,并且指定目标对象属性的开始、结束值和持续时间。在调用start后,整个动画过程中, ValueAnimator会根据已经完成的动画时间计算得到一个0到1之间的分数,代表该动画的已完成动画百分比。0表示0%,1表示100%。

    当ValueAnimator计算完已完成动画分数后,它会调用当前设置的TimeInterpolator,去计算得到一个interpolated(插值)分数,在计算过程中,已完成动画百分比会被加入到新的插值计算中。

    当插值分数计算完成后,ValueAnimator会根据插值分数调用合适的 TypeEvaluator去计算运动中的属性值。

    具体使用:

    ValueAnimator

    ValueAnimator是属性动画的核心类。使用的时候需要添加监听AnimatorUpdateListener监听,在监听方法中对需要执行动画的对象设置当前的属性值。

    ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
    animation.setDuration(1000);
    animation.start();
    animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator updatedAnimation) {
            float animatedValue = (float)updatedAnimation.getAnimatedValue();
            textView.setTranslationX(animatedValue);
        }
    });
    

    ObjectAnimator

    ObjectAnimator是ValueAnimator的一个子类

    ObjectAnimator.ofFloat(textview,"rotationX",0.0f,180f)
                  .setDuration(100)
                  .start();
    

    AnimatorSet组合动画

    after(Animator anim): 将现有动画插入到传入的动画之后执行

    after(long delay): 将现有动画延迟指定毫秒后执行

    before(Animator anim): 将现有动画插入到传入的动画之前执行

    with(Animator anim): 将现有动画和传入的动画同时执行

    ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
    ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
    ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
    AnimatorSet animSet = new AnimatorSet();
    animSet.play(rotate).with(fadeInOut).after(moveIn);
    
    animSet.setDuration(5000);
    animSet.start();
    

    使用xml声明动画

    通过自定义的xml进行声明动画,可以进行动画的重用,更轻松地编辑动画序列。为了区分使用来自那些使用传统的新属性动画的API动画文件(视图动画)框架,从Android 3.1开始,属性动画的xml文件应保存在res/animator/目录下。

    1.xml文件:

    <?xml version="1.0" encoding="utf-8"?  
    <set xmlns:android="http://schemas.android.com/apk/res/android"  
        android:ordering="together"   
      
        <objectAnimator  
            android:duration="1000"  
            android:propertyName="scaleX"  
            android:valueFrom="1"  
            android:valueTo="0.5"   
        </objectAnimator  
        <objectAnimator  
            android:duration="1000"  
            android:propertyName="scaleY"  
            android:valueFrom="1"  
            android:valueTo="0.5"   
        </objectAnimator  
    </set
    

    2.在java中调用:

    // 加载动画  
    Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scale);  
    mMv.setPivotX(0);  
    mMv.setPivotY(0);  
    //显示的调用invalidate  
    mMv.invalidate();  
    anim.setTarget(mMv);  
    anim.start();
    

    Animation监听器:

    Animator提供了一个addListener()的方法,这个方法接收一个AnimatorListener,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了。

    anim.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    //动画启动时调用
                }
                @Override
                public void onAnimationEnd(Animator animation) {
                    //当动画结束时调用
                }
                @Override
                public void onAnimationCancel(Animator animation) {
                    //当动画被取消调用,被取消的动画还会调用onAnimationEnd方法
                }
                @Override
                public void onAnimationRepeat(Animator animation) {
                    //当动画重演调用
                }
            });
    

    插值器(Interpolator)与估值器(TypeEvaluator):

    插值器:

    TranslateAnimation rotateAnimation = new TranslateAnimation(0,200,0,200);
    rotateAnimation.setDuration(400);
    //创建对于的插值器
    OvershootInterpolator interpolator = new OvershootInterpolator();
    //设置插值器
    rotateAnimation.setInterpolator(interpolator);
    image.startAnimation(rotateAnimation);
    

    估值器:

    系统内置了3个估值器,分别是:

    IntEvaluator:整数属性值。

    FloatEvaluator:浮点型属性值。

    ArgbEvaluator:十六进制color属性值。

    ObjectAnimator objectAnimator = ObjectAnimator.ofObject(image,"alpha",new IntEvaluator(),1,0,1);
    objectAnimator.setDuration(500);
    objectAnimator.start();
    

    上一篇:Android基础(4)—View的绘制及事件分发机制
    下一篇:Android基础(6)—滑动组件之ListView和RecyclerView

    精彩内容不够看?更多精彩内容,请到微信搜索 “危君子频道” 订阅号,每天更新,欢迎大家关注订阅!

    微信公众号

    相关文章

      网友评论

        本文标题:Android基础(5)—自定义View

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