Android多媒体开发与应用(一):Android动画处理与自定义View

目录

 

第一章:动画基础

第一节:逐帧动画

一、逐帧动画定义:

二、定义帧动画的方法:

三、逐帧动画实例:

第二节:视图动画系统

一、透明度动画AlphaAnimation

二、缩放动画ScaleAnimation

三、位移动画TranslateAnimation

四、旋转动画RotateAnimation

五、集合动画AnimationSet

六、插值器Interpolator

第三节:属性动画系统

一、视图动画vs属性动画:

二、属性动画能改变的属性

三、ValueAnimator

四、ObjectAnimator

五、ViewPropertyAnimator

六、动画集

第二章:转场动画

第一节:揭露动画

第二节:多视图的转场动画

Scene(场景)

Transition(转换)

TransitionManager(转场)

第三节:Activity间的转场动画

第三章:自定义视图

第四章 SurfaceView

一、 SurfaceView与View 的区别

二、SurfaceView 的具体使用场景

三、使用SurfacceView


第一章:动画基础

动画的意义:

  • 视觉效果:优雅的动画能大幅度的提升用户的观感,给用户带来良好的视觉感受。
  • 引导用户:好的动画能够合理吸引用户的注意力,起到引导用户的作用,让他们更轻松的理解我们应用的行为。

动画的分类:

  • 逐帧动画:逐帧动画的基础是帧,也就是图片,这些图片一般由公司的美工来制作,没有原图就无法定义逐帧动画,应用的范围也就比较小。
  • 视图动画(补间动画Tween Animation)系统:视图动画系统操作的是视图对象,可以让它们产生透明度渐变、位移、旋转等效果,视图动画系统已经可以定义比较丰富的效果,但是它也有局限性,通过对视图动画的学习,我们会掌握动画的一些基本属性。
  • 属性动画系统:属性动画系统操作的对象不再局限于视图,而且它可以真实的改变对象的属性。在Android 3.0的(API级别11)引入。

第一节:逐帧动画

一、逐帧动画定义:

逐帧动画也叫图片动画,通过在一个固定区域一张一张的加载事先准备好的图片而产生动画效果。

二、定义帧动画的方法:

在Android平台使用AnimationDrawable对象来定义逐帧动画,它是一个Drawable的容器,和普通的Drawable对象一样,它可以设置为视图对象的背景。

最简单的定义帧动画的方法就是在XML文件中通过<animation-list>来定义AnimationDrawable对象。

三、逐帧动画实例:

1、在drawable目录中创建一个Drawable类型资源文件loading.xml。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true"><!--只播放一次-->
    <item
        android:drawable="@drawable/frame_1"
        android:duration="100"/>
    <item
        android:drawable="@drawable/frame_2"
        android:duration="100"/>
    <item
        android:drawable="@drawable/frame_3"
        android:duration="100"/>
</animation-list>

2、在布局文件中创建View对象,背景设置为loading.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity">

    <View
        android:id="@+id/view"
        android:background="@drawable/loading"
        android:layout_centerInParent="true"
        android:layout_width="300dp"
        android:layout_height="300dp"/>

    <LinearLayout
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/start_btn"
            android:text="Start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/stop_btn"
            android:text="Stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

</RelativeLayout>

3、在代码中创建AnimationDrawable对象,并初始化为View对象的背景。通过Animation的start方法播放动画,stop方法停止动画,setOneShot(true)方法可以设置只播放一次。

package com.administrator.animationdrawable;

import android.graphics.drawable.AnimationDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private View mView;
    private AnimationDrawable mAnimationDrawable;
    private Button mBtnStart;
    private Button mBtnStop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化控件
        initView();

        //添加监听器
        mBtnStart.setOnClickListener(this);
        mBtnStop.setOnClickListener(this);

    }

    private void initView() {
        mView = findViewById(R.id.view);
        mBtnStart = findViewById(R.id.start_btn);
        mBtnStop = findViewById(R.id.stop_btn);
        mAnimationDrawable = (AnimationDrawable) mView.getBackground();
        mAnimationDrawable.setOneShot(true);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.start_btn:
                mAnimationDrawable.start();
                break;
            case R.id.stop_btn:
                mAnimationDrawable.stop();
                break;
        }
    }
}

第二节:视图动画系统

视图动画系统操作的是Android中的视图对象,简单说通过视图动画可以让Button、TextView、ImageView等动起来。

视图动画系统相关的类都定义在android.view.animation包中,其中的Animation类,它有一些子类AlphaAnimation、ScaleAnimation、TranslateAnimation、RotateAnimation、AnimationSet。

视图对象系统能够对视图对象展示补间动画(补间动画:提供起点、终点和时长,自动完成中间过程)。

详情参考:https://blog.csdn.net/pzm1993/article/details/77167049

一、透明度动画AlphaAnimation

可以在资源文件中定义透明度动画,也可以在java文件中定义。在资源文件中通过<alpha>标签定义动画,每个标签都对应一个AlphaAnimation对象。

1、在res中创建anim目录,在anim目录中创建一个透明度动画资源文件alpha.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"><!--持续时间-->

    <alpha
        android:fromAlpha="1.0"<!--开始时透明度-->
        android:toAlpha="0.1"/><!--结束时透明度-->
</set>

2、在java文件中获取动画对象,并在目标对象上应用。

//获取透明度对象
Animation alphaAnimation = AnimationUtils.loadAnimation(this, R.anim.alpha);
//为对象设置透明度动画,如Button、TextView等
view.startAnimation(alphaAnimation);

提示:xml标签的属性可以快速提取:Refator->Extract->Style...

二、缩放动画ScaleAnimation

可以在X轴或Y轴移动动画,同透明度动画一样,在资源文件中每个<scale>标签对应一个ScaleAnimation对象。

<scale>标签常用属性

  • android:fromXScale    起始时X方向上相对自身的缩放比例,浮点值,比如1.0代表自身无变化,0.5代表起始时缩小一倍,2.0代表放大一倍;
  • android:toXScale        结束时X方向上相对自身的缩放比例,浮点值;
  • android:fromYScale    起始时Y方向上相对自身的缩放比例,浮点值,
  • android:toYScale        结束时Y方向上相对自身的缩放比例,浮点值;
  • android:pivotX            X轴的基准点,可以是数值、百分数、百分数p 三种样式,比如 20、20%、20%p,当值为数值时,表示在当前视图的左边,20表示视图基准点向右50px做为X轴基准点;当值为20%时,表示在当前基准点加上自身宽度的20%做为起始点;如果是20%p,表示当前基准点加上父控件宽度的20%做为起始点。
  • android:pivotY           Y轴的基准点,取值及意义跟android:pivotX一样。
  • android:fillAfter          结束时保持状态,取值true、false,一般用在上一级标签中
  • android:fillBefore       结束时保持状态,取值true、false,一般用在上一级标签中
  • android:duration        动画持续时间

xml代码:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"><!--动画结束后保持状态-->

    <scale
        android:fromXScale="1.0"
        android:toXScale="2.0"
        android:fromYScale="1.0"
        android:toYScale="1.0"
        android:pivotX="40%p"
        android:pivotY="0"/>
</set>

三、位移动画TranslateAnimation

平移动画,实现视图垂直/水平方向位移变化,指定开始的位置,和结束的位置即可。

xml代码:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime">
    <translate
        android:fromXDelta="1"
        android:fromYDelta="0"
        android:toXDelta="80%p"
        android:toYDelta="0" />
</set>

四、旋转动画RotateAnimation

旋转动画,与缩放动画较为相似,也需要一个轴点来实现旋转。

<rotate>标签常用属性

  • android:pivotX            旋转轴点的X坐标(其值可以为:数值、百分数、百分数p),与scale标签相同
  • android:pivotY            旋转轴点的Y坐标(其值可以为:数值、百分数、百分数p),与scale标签相同
  • android:fromDegrees 旋转开始的角度,其值可以为正负
  • android:toDegrees     旋转结束的角度,其值可以为正负

xml代码:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000">

    <rotate
        android:fromDegrees="0"
        android:toDegrees="90"
        android:pivotY="50%"
        android:pivotX="50%"/>
</set>

五、集合动画AnimationSet

集合动画,将多种视图动画效果组合到一起。在xml中使用set标签设置集合动画,每个set标签对应一个AnimationSet对象。

属性介绍:

  • android:startOffset 动画执行的启动时间,单位为毫秒。系统默认当执行start方法后立刻执行动画,当使用该方法设置后,将延迟一定的时间再启动动画。

xml代码:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000">
    <rotate
        android:startOffset="200"
        android:fromDegrees="0"
        android:toDegrees="720"
        android:pivotX="50%"
        android:pivotY="50%"/>
    <translate
        android:fromXDelta="0"
        android:toXDelta="200%"
        android:fromYDelta="0"
        android:toYDelta="0"/>
</set>

java代码:

                Animation set=AnimationUtils.loadAnimation(this,R.anim.set);
                view.startAnimation(set);

六、插值器Interpolator

Interpolator负责控制动画变化的速率,使得基本的动画效果能够以匀速、加速、减速、抛物线速率等各种速率变化。

常用插值器

  • LinearInterpolator    @android:anim/linear_interpolator    匀速运动
  • AccelerateInterpolator    @android:anim/accelerate_interpolator    加速运动
  • DecelerateInterpolator    @android:anim/decelerate_interpolator    减速运动
  • AccelerateDecelerateInterpolator    @android:anim/accelerate_decelerate_interpolator    先加速后减速
  • BounceInterpolator()    @android:anim/bounce_interpolator    回弹效果,到终点然后回弹几次再停下
  • AnticipateInterpolator()    @android:anim/anticipate_interpolator    先后退一点然后加速
  • OvershootInterpolator()    @android:anim/anticipate_overshoot_interpolator 加速超过终点一点,再后退回来
  • AnticipateOvershootInterpolator()    @android:anim/anticipate_overshoot_interpolator 上面两个效果组合

详情参考:https://blog.csdn.net/pzm1993/article/details/77926373

java代码:

View viewAccelerate=findViewById(R.id.view_accelerate);
                View viewLinear=findViewById(R.id.view_linear);
                
                Animation accelerateAnimation=AnimationUtils.loadAnimation(this,R.anim.translate);
                Animation linearAnimation=AnimationUtils.loadAnimation(this,R.anim.translate);
                accelerateAnimation.setInterpolator(new AccelerateInterpolator());//设置加速插值器
                linearAnimation.setInterpolator(new LinearInterpolator());//设置线性插值器
                
                viewAccelerate.setAnimation(accelerateAnimation);
                viewLinear.setAnimation(linearAnimation);

 

第三节:属性动画系统

一、视图动画vs属性动画:

  • 从操作对象来看,视图动画只能操作视图对象,如Button、TextView、LinearLayout等,无法操作其他对象,而属性对象则可以操作任意对象。
  • 从属性上来看,视图动画并没有真正改变视图的属性,如通过Scale拉伸控件长度,外观虽然改变了,但是控件的width属性并没有改变,而属动画是能够真正改变属性的。
  • 属性动画更强大,视图动画能实现的效果都可以通过属性动画来实现。

二、属性动画能改变的属性

  • 时长
  • 时间插值器
  • 重复次数以及重复模式
  • 动画集
  • 延迟

三、ValueAnimator

属性动画相关的类都被定义在android.animation包中,其中的Animator抽象类封装了动画的基本属性。Animator的子类ValueAnimator能够控制value的变化,即值的变化。属性动画的本质就是改变属性的值,ValueAnimator就负责驱动这个变化的过程。

代码:

                //ValueAnimator valueAnimator=ValueAnimator.ofInt(0,100);
                ValueAnimator valueAnimator=ValueAnimator.ofFloat(0,100);
                valueAnimator.setDuration(100);
                valueAnimator.setInterpolator(new LinearInterpolator());
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
                        float animatedFraction = valueAnimator.getAnimatedFraction();//值得变化完成度
                        //int animatedValue = (int) valueAnimator.getAnimatedValue();//获取当前插值
                        float animatedValue = (float) valueAnimator.getAnimatedValue();
                        //Log.d(TAG, "onAnimationUpdate: "+String.format("%.3f,%d",animatedFraction,animatedValue));
                        Log.d(TAG, "onAnimationUpdate: "+String.format("%.3f,%.3f",animatedFraction,animatedValue));
                    }
                });
                valueAnimator.start();

四、ObjectAnimator

ObjectAnimator允许我们指定target来说明要改变哪个对象的属性的值,它是ValueAnimator的子类,可以在java代码和资源文件中定义。

在资源文件中使用<objectAnimator>标签来定义,每个ObjectAnimator标签对应一个ObjectAnimator对象。

在视图动画中使用AnimationUtil来从资源文件中加载动画,而在属性动画中使用AnimatorInflater工具类来从资源文件中加载属性动画。

1、在资源文件中实现动画:

xml代码:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:propertyName="alpha"
        android:duration="1000"
        android:valueType="floatType"
        android:valueFrom="1.0"
        android:valueTo="0.1"/>
</set>

java代码:

                Animator animator = AnimatorInflater.loadAnimator(this, R.animator.alpha);
                animator.setTarget(view);
                animator.start();

2、在java文件中实现动画:

                ObjectAnimator animator=ObjectAnimator.ofFloat(view,"alpha",1.0f,0f,1.0f);
                animator.setDuration(2000);
                animator.start();

也可以连写:

ObjectAnimator.ofFloat(view,"scaleX",1.0f,3.0f).start();

五、ViewPropertyAnimator

虽然说属性动画可以让任何对象产生动画效果,而不止局限于视图对象,但是在大多数情况下我们还是要让视图动起来,Android提供了一个让视图产生属性动画的类叫做ViewPropertyAnimator。

通过视图的animator方法来获取ViewPropertyAnimator对象,在调用对应的方法就能够不断地控制视图的属性,让它产生动画的效果。

java实现代码:

view.animate().translationX(500f).setDuration(2000).start();

六、动画集

1、通过AnimatorSet实现:

                Animator rotateAnimator = ObjectAnimator.ofFloat(view, "rotation", 0,720);
                rotateAnimator.setDuration(1000);

                Animator moveAnimator=ObjectAnimator.ofFloat(view,"x",0,500);
                moveAnimator.setDuration(1000);
                moveAnimator.setStartDelay(500);
                AnimatorSet set=new AnimatorSet();
                set.playTogether(rotateAnimator,moveAnimator);//同时播放
                //set.playSequentially(rotateAnimator,moveAnimator);//顺序播放
                set.start();

2、通过ViewPropertyAnimator实现:

                view.animate().rotation(720).setDuration(1000).start();
                view.animate().translationX(500).setDuration(1000).setStartDelay(500).start();

第二章:转场动画

转场:我们可以把手机的屏幕想象成一个舞台,在这个固定的舞台上,不同的UI元素进入和离开。如果我们舞台的某个特定状态叫做一个场景,那不同的场景切换的时候就叫做转场。

第一节:揭露动画

使用揭露动画非常简单,Android Sdk 中已经帮我们提供了一个工具类 ViewAnimationUtils 来创建揭露动画。ViewAnimationUtils 里面只有一个静态方法 createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius), 返回一个 Animator 动画对象。

ViewAnimationUtils.createCircularReveal() 方法能够为裁剪区域添加动画以揭露或隐藏视图,该方法有五个参数:

第一个参数是执行揭露动画的 View 视图

第二个参数是相对于视图 View 的坐标系,动画圆的中心的x坐标

第三个参数是相对于视图 View 的坐标系,动画圆的中心的y坐标

第四个参数是动画圆的起始半径,

第五个参数动画圆的结束半径。


实例:

进入效果的实现:

private void revealEnter() {

        int w=mViewAnimation.getWidth();
        int h=mViewAnimation.getHeight();
        int cx=w;
        int cy=h;
        float r= (float) Math.hypot(w,h);

        Animator animator= ViewAnimationUtils.createCircularReveal(mViewAnimation,cx,cy,0,r);
        animator.setDuration(2000);
        mViewAnimation.setVisibility(View.VISIBLE);
        animator.start();
    }

离开效果的实现:

private void revealExit() {

        int w=mViewAnimation.getWidth();
        int h=mViewAnimation.getHeight();
        int cx=w;
        int cy=h;
        float r= (float) Math.hypot(w,h);

        Animator animator= ViewAnimationUtils.createCircularReveal(mViewAnimation,cx,cy,r,0);
        animator.setDuration(2000);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mViewAnimation.setVisibility(View.INVISIBLE);
            }
        });
        animator.start();
    }

第二节:多视图的转场动画

之前学的视图动画系统和属性动画系统都是针对单一的视图,而在转场动画中一个场景中有多个视图,为了实现多视图的动画效果android提供了转场动画框架android.transition,涉及到三个类Scene、Transition、TransitionManager。

Scene(场景)

getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)从布局中加载场景
  • sceneRoot转场发生的地方
  • layoutId资源名称,组成场景的布局资源
  • context上下文环境

Transition(转换)

android预定好了一些专场效果

  • Fade淡入淡出效果,透明度的变化
  • ChangeBounds变换边界,变换位置、长度、宽度。
  • AutoTransition 默认效果。
TransitionInflater.from(getBaseContext()).inflateTransition(R.transition.transition)在布局中加载Transition效果。

与Animation、Animator类似Transition可以在布局中定义在java代码中定义(推荐布局中定义)。

TransitionManager(转场)

转场的实现

TransitionManager.go(Scene scene, Transition transition)

scene要跳转到的场景

transition变换类型(可选),如果不设置系统会使用默认的效果。

实例:

        ViewGroup sceneRoot=findViewById(R.id.scene_root);
        mOverviewScene = Scene.getSceneForLayout(sceneRoot, R.layout.scene_overview,getBaseContext());
        mInfoScene = Scene.getSceneForLayout(sceneRoot, R.layout.scene_info,getBaseContext());

        TransitionManager.go(mOverviewScene);
        switch (view.getId()){
            case R.id.btnInfo:
                Transition transition= TransitionInflater.from(getBaseContext()).inflateTransition(R.transition.transition);
                TransitionManager.go(mInfoScene,transition);
                break;
            case R.id.btnClose:
                TransitionManager.go(mOverviewScene);
                break;
        }

res/transition/transition.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeImageTransform
        android:duration="3000">
        <targets android:targetId="@id/image"/>
    </changeImageTransform>

    <fade android:duration="3000" android:startDelay="1000"/>
</transitionSet>

补充知识:Android Studio 创建矢量图标的方法

res/drawable->右键->new->vector assect->点击Clip Art 选择图标

详情参考:Android使用矢量图(SVG, VectorDrawable)实践篇https://www.jianshu.com/p/0555b8c1d26a

第三节:Activity间的转场动画

Activity的转场动画很早就有,但是太过于单调,样式也不好看,于是Google在Android5.0之后,又推出的新的转场动画,效果还是非常炫的,今天我们一起来看一下。 
Android5.0之后Activity的出入场动画总体上来说可以分为两种,一种就是分解、滑动进入、淡入淡出,另外一种就是共享元素动画。

Activity的转场效果是定义在ActivityOptions中的,把ActivityOptions传递给Activity才会播放转场动画。

1、分解、滑动进入、淡入淡出转场动画:

FirstActivity:

        //Transition transition = new Slide(Gravity.LEFT);//滑动效果
        //Transition transition = new Explode();//分解效果
        Transition transition = new Fade();//淡出淡出
        transition.excludeTarget(android.R.id.statusBarBackground, true);//禁用状态栏动画效果

        getWindow().setEnterTransition(transition);//进场动画
        getWindow().setExitTransition(transition);//离场动画
        getWindow().setReenterTransition(transition);//再次进场
        ActivityOptions options=ActivityOptions.makeSceneTransitionAnimation(this);//
        startActivity(intent,options.toBundle());

SecondActivity:

        //Transition transition = new Slide(Gravity.RIGHT);//滑动效果
        //Transition transition=new Explode();//分解效果
        Transition transition=new Fade();//淡出淡出
        transition.setDuration(1000);
        transition.excludeTarget(android.R.id.statusBarBackground, true);//禁用状态栏动画效果
        getWindow().setEnterTransition(transition);
        getWindow().setExitTransition(transition);

2、共享元素转场动画。要求两个Activity有“共享元素”,共享元素要求类型相同,并且transitionName属性值相同,这样Activity跳转时,将播放共享元素的补间动画。

xml1

    <LinearLayout
        android:id="@+id/id_ll"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_alignParentBottom="true">
        <ImageView
            android:id="@+id/pic1_iv"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:transitionName="img"
            android:src="@drawable/pic1"/>
        <ImageView
            android:id="@+id/pic2_iv"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:transitionName="img1"
            android:src="@drawable/pic2"/>
    </LinearLayout>

xml2:

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <ImageView
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:transitionName="img"
            android:src="@drawable/pic1"/>
        <ImageView
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:transitionName="img1"
            android:src="@drawable/pic2"/>
    </LinearLayout>

代码:

                View pic1 = findViewById(R.id.pic1_iv);
                Pair<View,String> share1=new Pair<>(pic1,"img");
                View pic2 = findViewById(R.id.pic2_iv);
                Pair<View,String> share2=new Pair<>(pic2,"img1");
                ActivityOptions options=ActivityOptions.makeSceneTransitionAnimation(this,share1,share2);
                startActivity(new Intent(ImagesActivity.this,Images2Activity.class),options.toBundle());

 

案例源码

参考:https://blog.csdn.net/w630886916/article/details/78319502

参考:https://www.jianshu.com/p/1007f300f17a

参考:https://www.cnblogs.com/lenve/p/5865897.html

第三章:自定义视图

为什么要选择自定义控件?

  1. 特定的显示风格,如圆形进度条。
  2. 处理特有的用户交互,拖拽、排序等。
  3. 优化布局,例如ListView的item需要显示图片与文字的组合内容。
  4. 封装,封装多个不同类型的控件,方便复用。

如何自定义控件?

  1. 自定义属性的声明与获取
  2. 测量onMeasure
  3. 绘制onDraw
  4. 状态的存储与恢复

一、自定义属性的声明与获取

  1. 分析需要的自定义属性
  2. 在res/values/attrs.xml中定义声明
  3. 在layout xml文件中使用
  4. 在View的构造方法中获取

二、测量onMeasure

1、EXACTLY , AT_MOST , UNSPECIFIED

  • EXACTLY:用户为控件声明了一个具体的宽和高,在onMeasure中直接使用用户设置的值就可以
  • AT_MOST:用户将控件宽和高设置为"wrap_content",在onMeasure中可以获得父控件的宽和高,设置宽和高的时候不能大于父控件的宽和高
  • UNSPECIFIED:有滚动条的自定义View,如ListView、ScrollView等,在onMeasure中设置宽和高没有限制

2、MeasureSpec:在onMeasure中通过MeasureSpec类可以获得父控件传入的模式和宽高的大小
3、setMeasuredDimension:设置自定义View的大小
4、requestLayout():当控件大小发生变化时,调用requestLayout()强制重新测量

onMeasure的通用代码:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int width = 0;
        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            int needWidth = measureWidth() + getPaddingLeft() + getPaddingRight();
            if (widthMode == MeasureSpec.AT_MOST)
            {
                width = Math.min(needWidth, widthSize);
            } else
            {
                width = needWidth;
            }
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int height = 0;

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            int needHeight = measureHeight() + getPaddingTop() + getPaddingBottom();
            if (heightMode == MeasureSpec.AT_MOST)
            {
                height = Math.min(needHeight, heightSize);
            } else //MeasureSpec.UNSPECIFIED
            {
                height = needHeight;
            }
        }
        width=Math.min(width,height);
        setMeasuredDimension(width, width);

    }

三、绘制onDraw

四、状态的存储与恢复

通用代码:

private static final String INSTANCE = "instance";
    private static final String KEY_PROGRESS = "key_progress";
    @Nullable
    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putInt(KEY_PROGRESS, mProgress);
        bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle)
        {
            Bundle bundle = (Bundle) state;
            Parcelable parcelable = bundle.getParcelable(INSTANCE);
            super.onRestoreInstanceState(parcelable);
            mProgress = bundle.getInt(KEY_PROGRESS);
            return;
        }
        super.onRestoreInstanceState(state);
    }

参考:

Android - 自定义View】之自定义View浅析

https://www.cnblogs.com/itgungnir/p/6217447.html

Android 自定义 view(四)—— onMeasure 方法理解

https://www.cnblogs.com/yishujun/p/5560838.html

第四章 SurfaceView

一、 SurfaceView与View 的区别

  • 不适用onDraw
  • 非UI线程绘制
  • 独立的Surface

二、SurfaceView 的具体使用场景

  • 播放视频
  • 炫酷的动画效果
  • 小游戏

三、使用SurfacceView

通用模板,画一个半径不断变化的空心圆

public class SurfaceViewTemplate extends SurfaceView implements Runnable{
    private Thread mThread;
    private boolean mIsRunning;

    private Paint mPaint;

    private int mMaxRadius;
    private int mMinRadius;
    private int mRadius;
    private int mFlag;

    public SurfaceViewTemplate(Context context, AttributeSet attrs) {
        super(context, attrs);
        SurfaceHolder holder = getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                mIsRunning=true;
                mThread=new Thread(SurfaceViewTemplate.this);
                mThread.start();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                mIsRunning=false;
            }
        });

        setFocusable(true);
        setFocusableInTouchMode(true);
        setKeepScreenOn(true);

        initPaint();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMaxRadius=Math.min(w,h)/2-20;
        mRadius=mMinRadius=30;
    }

    private void initPaint() {
        mPaint=new Paint();
        mPaint.setDither(true);
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.GREEN);
        mPaint.setStrokeWidth(6);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    public void run() {
        while(mIsRunning){
            long start=System.currentTimeMillis();
            drawself();
            long end=System.currentTimeMillis();

            if(end-start<50){
                try {
                    Thread.sleep(50-(end-start));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void drawself() {
        Canvas canvas=null;
        try {
            canvas=getHolder().lockCanvas();
            if(canvas!=null){
                //todo something
                drawCircle(canvas);
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            if(canvas!=null){
                getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }

    private void drawCircle(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        canvas.drawCircle(getWidth()/2,getHeight()/2,mRadius,mPaint);
        if(mRadius<=mMinRadius){
            mFlag=1;
        }else if(mRadius>=mMaxRadius){
            mFlag=-1;
        }
        mRadius+=mFlag*2;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值