作为一个android开发者,看到一个好的ui无疑是赏心悦目的。对于用户来,给予一个美观的ui也是非常重要的。此篇文章分析主要学习如何自定义view,同时也逐步探求一个好的loading的设计,以及animation的一种代码的设计。
android自定义view是一个android开发者进阶的一个重要的里程碑,其实这也是离不开Animation,Animator,canvas,path,paint等等这几个类和API,所以当遇到感觉困难的地方,android官网api一定相当有帮助。掌握了这几个类,基本上酷炫的view只要有些灵感,相信是难不倒了。
下面这一个AdhesiveLodingView工程是由一个小球循环绕圈,与周边小球形成粘性效果,放大后过重形成水珠跌落,水珠反弹形成文字的动画效果。涉及了如下几个类:
1.Path和Canvas、Paint
2.ValueAnimator、AnimationSet
AdhesiveLoadingView效果(项目地址)
结构分析
这个动画包括了三个过程:
1.小球旋转放大,其中还有震动效果
2.小球缩小衍生水滴,迅速跌落
3.文字弹出展现
在结构上是主要是通过controller对三个animator进行一个控制,并作为其中的信息传递媒介链接各个animator,将canvas分发给animator进行绘制。而view通过controller的初始化来达到展示动画的效果。其中,动画的效果是由AnimationSet进行顺序的控制。
代码分析(项目地址)
下面就通过代码的结构来分析一下整个的一个动画过程,其中分成三个部分,也就是三个animation的一个绘制的过程。
圆点
圆点是由抽象类Circle.java进行衍生的,正在进行运动的是WolfCircle.java,静止不动的六个小球是RabbitCircle.java。还有之后的水滴BeadCircle.
WolfCircle具有runTo()方法,这个是更改绘制角度,实现圆点运动
RabbitCircle具有state状态,这个是用来控制当状态改变,作出不同的绘制效果。
BeadCircle具有drop方法,这个是用来控制小球下跌的动作。
1. LoopCircleAnimator
这个动画负责圆点的旋转,利用度数来绘制六个圆点,同时通过度数来绘制运动的圆点,利用塞贝尔曲线来绘制其中粘性的效果,所以这里主要是利用度数来进行圆点之间的一个距离的判定。
这里主要的难点是,由于位置都是根据canvas的rotate进行旋转绘制,而塞贝尔曲线绘制的是在两个圆点之间,所以在旋转的时候如果位置计算不正取就会偏移。而这里通过了调整小偏移来弥补这个问题,后期进行处理。
绘制圆点通过Canvas的drawCircle方法进行绘制。
关于这个塞贝尔曲线以及黏着效果的实现,可以参考一下这个博客贝塞尔曲线应用及QQ气泡拖动原理实践。主要用到的方法也就是Path的quadTo,lineTo的方法。
以下是代码解析(主要的方法):
public LoopCircleAnimator(View view) {
mView = view;
initComponent();
initAnimator();
mPath = new Path();
}
/**
* 设置六个圆点以及运动的圆点
*/
private void initComponent() {
startX = Config.START_X;
startY = Config.START_Y;
centerX = Config.CENTER_X;
centerY = Config.CENTER_Y;
bigR = Config.BIG_CIRCLE_RADIUS;
// create 6 rabbits
int r = Math.min(mView.getWidth(), mView.getHeight()) / 20;
int degree = 0;
for (int i = 0; i < Config.RABBIT_NUM; i++) {
mRabbits.add(new RabbitCircle(startX, startY, r, degree));
degree += Config.DEGREE_GAP;
}
// create wolf
if (mWolf == null) {
mWolf = new WolfCircle(startX, startY, (int)(rate * r), 0);
}
}
/**
* 设置animator的参数
*/
private void initAnimator() {
this.setIntValues(0, 360);
this.setDuration(DURATION);
this.setInterpolator(new AccelerateDecelerateInterpolator());
this.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int degree = (int) animation.getAnimatedValue();
startActivities(degree);
mView.invalidate();
}
});
}
/**
* 开始运动,目的是旋转一圈,所以是从0-360度
* @param degree
*/
private