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);}
首先先创建两个点,startPoint和endPoint,这里讲清楚一下,这两个点并不是红蓝两个球的圆心,而是一个球的起始位置,我们知道了一个球的平移轨迹,另外一个也就知道了,在画圆的时候花两个圆就好了,这个方法中还有一个比较重要的就是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
- Android自定义view之属性动画初见
- Android动画合集之属性动画-初见
- Android 自定义View 之 自定义View属性
- Android 之 自定义View属性
- Android自定义View之属性
- 从零开始学Android自定义View之动画系列——属性动画(1)
- 从零开始学Android自定义View之动画系列——属性动画(2)
- 从零开始学Android自定义View之动画系列——属性动画(3)
- Android基础入门教程——8.4.3 Android动画合集之属性动画-初见
- Android之自定义view及自定义属性
- Android 自定义View动画篇之基础
- Android 自定义View动画篇之进阶
- android自定义view之加载动画ColorBall
- android自定义view之加载动画ColorBall
- 属性动画:如何自定义View
- 自定义view加属性动画
- 自定义view画圆加属性动画
- 自定义view+属性动画实现
- NSTimer详解
- 写给小白的android基础面试笔试题(一)
- 个人新年感悟
- 写给小白的android基础面试笔试题(二)
- 读书笔记——周鸿伟自述
- Android自定义view之属性动画初见
- 热更新hot update大纲
- 2
- 写给小白的android基础面试笔试题(三)
- hdu 2037 今年暑假不AC(简单贪心)
- 如何使用RecyclerView打造首页轮播图
- 康托和逆康托展开
- 聊天机器人对话数据
- 使用Android Studio进行JNI开发 - Mac篇