Android自定义view之属性动画初见

来源:互联网 发布:java邮箱的正则表达式 编辑:程序博客网 时间:2024/05/21 10:12

序言:初到新公司,暂时工作没有那么忙,每天都在看公司的代码,在看代码以及效果的同时发现一个很大的问题,就是打开新的Activity的时候都会有一段progressDialog显示,刚开始我以为是他们自己自定义的view,后来才发现原来是帧动画实现的,LZ比较有强迫症,大量的图片汇集在一起生成一个帧动画,怎么想都觉得有点划不来,而且大量的图片处理不当的话会造成系统卡顿和OOM,加之这两天在学习属性动画的一些知识,所以就想着能不能换成属性动画来实现,所以就拿这个栗子来练练手,往下看:

项目需求

上图为项目中的需求,两个小球横向来回移动(不是3D旋转的那种),所以看起来比较容易实现,讲一下思路:

注:首先这个效果我知道的有两种方法可以实现(其实讲到底也可以说是一种,自定义view+线程),一是自定义view+线程,二是自定义view+属性动画;
实现思路:首先有两个球,初始化的时候可以把它们都放在中心位置,然后改变两个圆心的位置(这里知道一个圆心位置的改变就可以了,因为两边是对称的)进行重绘界面。
自定义view+线程:利用线程来控制小球的来回平移,每次计算小球圆心的变化,记录圆心的值
自定义view+属性动画:利用属性动画来控制小球的来回平移,每次都改变小球圆心位置这一属性

下面来看看实现方法:

线程控制:@Overridepublic void run() {    while (isStart) {   //线程是否开启        try {            if (isDisjoint) {   //判断两个小球是否处于相离状态                //判断左边的小球有没有"走"到最左边(人为给定)                if (blueX <= getWidth() / 2 && blueX >= getWidth() / 2 - 40 && redX >= getWidth() / 2 && redX <= getWidth() / 2 + 40) {                    blueX -= 3;                    redX += 3;                    postInvalidate();                } else {                    blueX = getWidth() / 2 - 40;                    redX = getWidth() / 2 + 40;                    isDisjoint = false;                }            } else {                //判断右边的小球有没有"走"到最右边(人为给定)                if (blueX <= getWidth() / 2 && blueX >= getWidth() / 2 - 40 && redX >= getWidth() / 2 && redX <= getWidth() / 2 + 40) {                    blueX += 3;                    redX -= 3;                    postInvalidate();                } else {                    isDisjoint = true;                    blueX = getWidth() / 2;                    redX = getWidth() / 2;                }            }            Thread.sleep(15);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

我觉得这样做还是比较容易理解的,当左边小球的圆心到达最左边时(当右边小球的圆心到达最右边时)设置往回走,这样循环往复,就可以维持一个来回移动的状态,这种就是使用线程来完成的一个动画的效果,整个源码如下:

public class CustomDialogView extends View implements Runnable {    private Paint mPaint;    private boolean isStart;    private float blueX, redX;  //蓝红色小球的圆点x值,默认的y值为getHeight()/2    private boolean isDisjoint = true;    private boolean isFirst = true;    public CustomDialogView(Context context) {        this(context, null);    }    public CustomDialogView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public CustomDialogView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setColor(Color.BLUE);        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);        mPaint.setAntiAlias(true);        if (isFirst) {            isFirst = false;            blueX = getWidth() / 2;            redX = getWidth() / 2;        }        canvas.drawCircle(blueX, getHeight() / 2, 20, mPaint);        mPaint.setColor(Color.RED);        canvas.drawCircle(redX, getHeight() / 2, 20, mPaint);    }    //控制线程的开始    public boolean isStart() {        return isStart;    }    public void setStart(boolean start) {        isStart = start;    }    @Override    public void run() {        while (isStart) {    //线程是否开启            try {                if (isDisjoint) {   //判断两个小球是否处于相离状态                    //判断左边的小球有没有"走"到最左边(人为给定)                    if (blueX <= getWidth() / 2 && blueX >= getWidth() / 2 - 40 && redX >= getWidth() / 2 && redX <= getWidth() / 2 + 40) {                        blueX -= 3;                        redX += 3;                        postInvalidate();                    } else {                        blueX = getWidth() / 2 - 40;                        redX = getWidth() / 2 + 40;                        isDisjoint = false;                    }                } else {                    //判断右边的小球有没有"走"到最右边(人为给定)                    if (blueX <= getWidth() / 2 && blueX >= getWidth() / 2 - 40 && redX >= getWidth() / 2 && redX <= getWidth() / 2 + 40) {                        blueX += 3;                        redX -= 3;                        postInvalidate();                    } else {                        isDisjoint = true;                        blueX = getWidth() / 2;                        redX = getWidth() / 2;                    }                }                Thread.sleep(15);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

下面来看看使用属性动画来做的这样一个效果。首先呢,我们要知道什么是属性动画,顾名思义,就是通过修改某个属性而达到某种效果(某些效果)。这里呢,讲一个本次实验中用到的一个类TypeEvaluator,这个类可以帮我们完成一个功能,就是告诉系统如何从初始值过渡到结束值,我们要自定义一个这样的TypeEvaluator,然后重写它里面的evaluate()方法:

public class CustomPointEvaluator implements TypeEvaluator {    /**     *     * @param fraction 系数     * @param startValue 起始值     * @param endValue 终点值     * @return     */    @Override    public Object evaluate(float fraction, Object startValue, Object endValue) {        CustomPoint startPoint = (CustomPoint) startValue;        CustomPoint endPoint = (CustomPoint) endValue;        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());        CustomPoint point = new CustomPoint(x, y);        return point;    }}

可以看到,这里我们是对CustomPoint对象操作的,所以最终返回的对象也是一个CustomPoint对象,其实evaluate()方法中的逻辑还是很好理解的,将startValue和endValue强转成CustomPoint对象,这里的CustomPoint表示的是一个点的坐标,也就是两个球的圆心的坐标,然后根据fraction系数,计算出当前动画的x和y的值,下面给出CustomPoint的代码:

public class CustomPoint {    private float x;    private float y;    public CustomPoint(float x, float y) {        this.x = x;        this.y = y;    }    public float getX() {        return x;    }    public void setX(float x) {        this.x = x;    }    public float getY() {        return y;    }    public void setY(float y) {        this.y = y;    }}

完成自定义TypeEvaluator之后,我们就可以来尝试一下如何通过对CustomPoint对象进行动画操作:

//开始动画private void startAnimation() {    CustomPoint startPoint = new CustomPoint(getWidth() / 2, getHeight() / 2);    CustomPoint endPoint = new CustomPoint(getWidth() / 2 - 2 * RADIUS, getHeight() / 2);    anim = ValueAnimator.ofObject(new CustomPointEvaluator(), startPoint, endPoint, startPoint);    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {        @Override        public void onAnimationUpdate(ValueAnimator animation) {            currentPointBlue = (CustomPoint) animation.getAnimatedValue();            invalidate();        }    });    anim.setDuration(600);    anim.setRepeatCount(Animation.INFINITE);}

首先先创建两个点,startPointendPoint,这里讲清楚一下,这两个点并不是红蓝两个球的圆心,而是一个球的起始位置,我们知道了一个球的平移轨迹,另外一个也就知道了,在画圆的时候花两个圆就好了,这个方法中还有一个比较重要的就是ValueAnimator.AnimatorUpdateListener监听事件,事件中的onAnimationUpdate方法是在动画中每一帧更新的时候调用,监听这个接口可以使用动画播放过程中由ValueAnimator计算出来的值。为了使用这个值,使用传递给事件的ValueAnimator的对象的getAnimatedValue()接口来获取当前的动画值。如果你使用 ValueAnimator,必须实现这个方法。可以看到,我在这个方法中获得了一个CustomPoint对象,这个对象的属性值是在动画播放的过程中改变的,所以我们调用重新绘制方法来重绘界面,这样也能达到以上的目的,我们来看看完整的代码:

public class CustomPropertyAnimationView extends View {    private static final float RADIUS = 25;  //小球半径    private CustomPoint currentPointBlue;  //蓝色的小球    private CustomPoint currentPointRed;   //红色的小球    private Paint mPaint;    //蓝色小球的画笔    private static final int BOUNDARY = 70;   //白色的边界长度    private ValueAnimator anim;   //动画    public CustomPropertyAnimationView(Context context) {        this(context, null);    }    public CustomPropertyAnimationView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public CustomPropertyAnimationView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initView();    }    private void initView() {        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setAntiAlias(true);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (currentPointBlue == null) {            currentPointBlue = new CustomPoint(getWidth() / 2, getHeight() / 2);            currentPointRed = new CustomPoint(getWidth() / 2, getHeight() / 2);            drawCircle(canvas);   //画圆            if (isFirst) {            startAnimation();   //开始动画            isFirst = false;        }        } else {            drawCircle(canvas);        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int mode = MeasureSpec.getMode(widthMeasureSpec);        int size = MeasureSpec.getSize(widthMeasureSpec);        int width;        int height;        if (mode == MeasureSpec.EXACTLY) {   //表示确定的值或者MATCH_PARENT            width = size;        } else {   //表示WARP_CONTENT            width = (int) (2 * RADIUS + 2 * BOUNDARY + getPaddingLeft() + getPaddingRight());        }        mode = MeasureSpec.getMode(heightMeasureSpec);        size = MeasureSpec.getSize(heightMeasureSpec);        if (mode == MeasureSpec.EXACTLY) {            height = size;        } else {   //表示WARP_CONTENT            height = (int) (4 * RADIUS + getPaddingTop() + getPaddingBottom());        }        setMeasuredDimension(width, height);    }    //外部调用的地方可以控制动画的开始、暂停与停止    public void startCustomAnim() {        if (!anim.isStarted() || anim.isPaused()) {            anim.start();        }    }    public void stopCustomAnim() {        if (anim.isStarted()) {            anim.end();        }    }    public void pauseCustomAnim() {        if (!anim.isPaused()) {            anim.pause();        }    }    //画圆    private void drawCircle(Canvas canvas) {        float blueX = currentPointBlue.getX();        float redX = Math.abs(currentPointBlue.getX() - getWidth() / 2) + getWidth() / 2;        currentPointRed.setX(redX + getWidth() / 2);        mPaint.setColor(Color.BLUE);        canvas.drawCircle(blueX, getHeight() / 2, RADIUS, mPaint);        mPaint.setColor(Color.RED);        canvas.drawCircle(redX, getHeight() / 2, RADIUS, mPaint);    }    //开始动画    private void startAnimation() {        CustomPoint startPoint = new CustomPoint(getWidth() / 2, getHeight() / 2);        CustomPoint endPoint = new CustomPoint(getWidth() / 2 - 2 * RADIUS, getHeight() / 2);        anim = ValueAnimator.ofObject(new CustomPointEvaluator(), startPoint, endPoint, startPoint);        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                currentPointBlue = (CustomPoint) animation.getAnimatedValue();                invalidate();            }        });        anim.setDuration(600);        anim.setRepeatCount(Animation.INFINITE);    }}使用方法:CustomPropertyAnimationView acpaAnim;两个按钮的点击事件case R.id.acpa_btn_start:    acpaAnim.startCustomAnim();        break;case R.id.acpa_btn_pause:    acpaAnim.pauseCustomAnim();        break;

有一些属性你可以自己设定,从xml中获取,这里我为了方便演示,就直接用了确切的值。最后我们来比较一下这两种方法,这两种方法都是以线程为基础的,动画内部也是有线程的,只不过它内部会维护,性能可能会比较好一点,如果你也有好的方法,请私戳我一起交流。基础的属性动画篇就讲到这里,后面我还会继续深入学习Android属性动画。

这是我建的一个android小白的群,各位有兴趣的小白欢迎加群共同学习,也欢迎各位大神进群指导,共勉。群号:541144061

0 0
原创粉丝点击