学习自定义view最基础的知识后就需要做一个小demo来检验自己的成果,比如实现以下加载动画。
这是缩放10倍的效果由于截图软件的原因可能有一些卡顿 但是正常的话是一个小球跳动的过程。当然这也是为了促进对自定义view的了解和初步学习。
有两种查看方式,一种是不考虑动画看小球的状态:小球有两种状态 一种是正常的圆形,一种是椭圆形。椭圆形是越来越扁。然后是一个阴影慢慢变长。第二种查看方式是按顺序看 开始时小球是圆形,然后下降,下降到一个值后变成椭圆。阴影依然也是慢慢变长。
这里绘制圆形应该没有问题。x坐标是getWidth/2,y坐标在变化。此时会用到动画。而且是一种属性动画,这样大脑中要知道属性动画的实现方式一种是ObjectAnimator,一种是ValueAnimator。这两种方式需要牢记。特别是ValueAnimator顾名思义是值动画基本就是获取值。通过设置y的起点和终点。小球可以动起来 比如:
对应当log:
代码稍作修改后
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(color);
mDensity = getResources().getDisplayMetrics().density;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mStartX = getWidth()/2;
mEndY = getHeight()/2;
mStartY = mEndY*5/6;
playAnimator();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mStartX,mCurrentY,mDensity*radius,mPaint);
}
private void playAnimator() {
ValueAnimator animator = ValueAnimator.ofInt(mStartY,mEndY);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCurrentY= (int) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(-1);
animator.setDuration(1000);
animator.start();
}
其动画效果是这样的:
细心的可以发现 playAnimator()方法移动到了onSizeChanged()而不是init()方法中,原因很简单在初始化的时候会getWidth和getHeight方法值都为0,view的绘制没有走到onMeasure()所以mCurrentY也是0;当然playAnimator()可以放到onDraw()方法中。那么在onMeasure()方法中是否可以呢?其实你只要固定宽高是肯定可以的。因为在走到自己这里的onMeasure()方法时已经测量好了宽高,但是在wrap_content是否可以我没有做实验 但是我认为如果是测量前应该是不可以,测量后才可以,但是没有实验不能乱说,待以后测试就知道了。
重点是属性动画 。说到这里差不多基本能实现这简单的view了。也就是第二阶段变成椭圆绘制椭圆就是drawOval()接受两个参数 重要的是这个rectf矩形参数。在ondraw()方法中稍作修改:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mEndY - mCurrentY>10){
canvas.drawCircle(mStartX,mCurrentY,mDensity*radius,mPaint);
}else{
RectF rectF = new RectF(mStartX-radius*mDensity-2,
mCurrentY-radius*mDensity+5,
mStartX+radius*mDensity+2,
mCurrentY+radius*mDensity);
canvas.drawOval(rectF,mPaint);
}
}
RectF的几个参数可以自由定义,由于是练习就不是很标准,这里需要知道的是矩形的长越长宽越短则椭圆越扁,要么是水平很扁要么是垂直越扁。四个参数很好解释:mStartX-radius*mDensity-2 中mStartX是getWidth/2-半径-2就是圆的左边向左偏移2px;而mStartX+radius*mDensity+2则是圆的右边向右偏移的2px而mCurrentY-radius*mDensity+5中则是当前圆的圆心y坐标-半径则是圆的上边+5 理解坐标系都只是向下是正。那么圆的半径-5基本就是一个矩形。
最难莫过于阴影。因为阴影的矩形是变化的而且我们知道它是从中间向两边拉长的此时它的top和bottom是不变的可以这样考虑,当然也可以考虑它是变化的。简单一点就不用考虑这个变化因为太小了不一定能看出来。或许以后了解多了之后可以进行优化。接下来考虑的是left和right。当然可以考虑吧动画left从mStartX到 mStartX-半径,right也是一样。这样难度就是时机的把握。要在一定程度比如mEndY-mStartY等于一个值然后开始动画开始并且到小球一轮动画截至。阴影动画也停止。当然还有一种方式叫做ratio也就是比率。ratio是0-1的变化代码为:
int dy = mEndY -mStartY;
int dy1 = mCurrentY - mStartY;
float ratio = (float) (dy1*1.0/dy);
具体代码可以:
int dy = mEndY -mStartY;
int dy1 = mCurrentY - mStartY;
float ratio = (float) (dy1*1.0/dy);
if (ratio<0.3) return;
int ovalRadius = (int) (radius * ratio*mDensity);
RectF rectF = new RectF(mStartX-ovalRadius,mEndY+10,mStartX+ovalRadius,mEndY+15);
mPaint.setColor(Color.parseColor("#3F3B2D"));
canvas.drawOval(rectF,mPaint);