这篇文章记录一下Android自定义UI的知识,一个小小的跳动的loading,特别感谢Nil的指点…废话不多,先来效果
SoHOT链接和star地址:SoHOT源码地址,在文章最后最后有github开源地址,别错过
如果您还没有去给SoHOT一颗star而直接看这个项目,那简直是有点损失,希望您点上面的链接,去star和下载体验一下SoHoT,捡起那个大西瓜再来捡这个小芝麻项目,你懂的!!
凑乎看吧,录制的效果不如真机上平滑…凑乎看吧.
我们一步步分解这个loading来看,我们顶住一个小球来分析,
这一个小球走的路线有没有是一个正弦函数的路线.
如果三个小球全部一起运动的话就是重叠的,那我们就在刷新view的时候分别给每个小球来个间隔不就行了.这样就有这种错乱的赶脚了…
好的首先我们先定义我们的自定义属性,
为了更好的扩展,我们需要尽可能抽取出来三个小球的颜色,
小球的大小等,
然后就是书写我们的自定义view,
我们在构造函数中,初始化我们需要的各种属性和参数
然后我们在onSizeChanged中把小球的初始x 和y值 计算出来,让小球在我们设定的移动范围垂直距离中间开始执行正弦轨迹,
计算好小球的初始值位置坐标后,就是初始化我们的动画类,
这里用ValueAnimaiton来完成的正弦的参数变化,
这里我们拿到一个ValueAnimator 传入他的变化值范围,在他的变化监听中根据正弦的函数,根据x值得到y值,这样小球每时每刻的坐标就计算出来了,然后调用invalidate方法,通知ondraw方法,来绘制
这样小球的曲线路径就展现出来了.
然后就是看我们的ondraw中无非就是根据x y 的值分别绘制此时小球的位置,给人感官上的感觉就是动画执行起来了.
是的就是这个道理!
我把这个控件的完整代码放到最后,有兴趣的可以运行下,工程就不上传了..
public class ColorBallView extends View {
private static final int STRETCHING_X = 150;
private static final int STRETCHING_Y = 80;
private static final float BALL_MAX = 1 / 10F;
private static final float DE_VIEW_SIZE = 120F;
private static final long ANIMATION_TIME = 1200;
private static final long INTERVAL_TIME = 400;
private final int DE_BALL_SIZE = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
private Paint mPaint;
private int mFirstBallColor;
private int mSecondBallColor;
private int mThirdBallColor;
private int mBallRadius;
private int mWidth;
private int mHeight;
private int cx, cy, cx1, cy1, cx2, cy2;
private int offsetY, offsetY1, offsetY2;
private boolean isDrawSecond, isDrawThird;
public ColorBallView(Context context) {
this(context, null);
}
public ColorBallView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ColorBallView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ColorBallView, defStyleAttr, 0);
int indexCount = typedArray.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.ColorBallView_ballSize:
mBallRadius = typedArray.getDimensionPixelSize(attr, DE_BALL_SIZE);
break;
case R.styleable.ColorBallView_firstBallColor:
mFirstBallColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ColorBallView_secondColor:
mSecondBallColor = typedArray.getColor(attr, Color.GREEN);
break;
case R.styleable.ColorBallView_thirdBallColor:
mThirdBallColor = typedArray.getColor(attr, Color.YELLOW);
break;
}
}
typedArray.recycle();
init();
startAllBallAnimation();
}
private void startAllBallAnimation() {
Observable.interval(INTERVAL_TIME, INTERVAL_TIME, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long aLong) {
int i = aLong.intValue();
if (i == 0) {
initAnimation();
} else if (i == 1) {
initAnimation1();
} else if (i == 2) {
initAnimation2();
this.unsubscribe();
onCompleted();
}
}
});
}
private void init() {
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize;
int heightSize;
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DE_VIEW_SIZE, getResources().getDisplayMetrics());
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DE_VIEW_SIZE, getResources().getDisplayMetrics());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mBallRadius = (int) ((int) (Math.min(Math.min(w, h) * BALL_MAX, mBallRadius)) * .5F);
cx = mBallRadius;
cy = mHeight / 2;
cx1 = cx;
cx2 = cx;
cy1 = cy;
cy2 = cy;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBall(canvas);
}
private void drawBall(Canvas canvas) {
mPaint.setColor(mFirstBallColor);
canvas.drawCircle(cx, cy + offsetY, mBallRadius, mPaint);
if (isDrawSecond) {
mPaint.setColor(mSecondBallColor);
canvas.drawCircle(cx1, cy1 + offsetY1, mBallRadius, mPaint);
}
if (isDrawThird) {
mPaint.setColor(mThirdBallColor);
canvas.drawCircle(cx2, cy2 + offsetY2, mBallRadius, mPaint);
}
}
private void initAnimation() {
ValueAnimator valueAnimator = getValueAni();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float aFloat = Float.valueOf(animation.getAnimatedValue().toString());
cx = (int) (aFloat * STRETCHING_X) + mBallRadius;
offsetY = (int) ((float) Math.sin(2 * Math.PI * aFloat) * STRETCHING_Y);
invalidate();
}
});
valueAnimator.start();
}
private void initAnimation1() {
ValueAnimator valueAnimator = getValueAni();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float aFloat = Float.valueOf(animation.getAnimatedValue().toString());
cx1 = (int) (aFloat * STRETCHING_X) + mBallRadius;
offsetY1 = (int) ((float) Math.sin(2 * Math.PI * aFloat) * STRETCHING_Y);
isDrawSecond = true;
invalidate();
}
});
valueAnimator.start();
}
private void initAnimation2() {
ValueAnimator valueAnimator = getValueAni();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float aFloat = Float.valueOf(animation.getAnimatedValue().toString());
cx2 = (int) (aFloat * STRETCHING_X) + mBallRadius;
offsetY2 = (int) ((float) Math.sin(2 * Math.PI * aFloat) * STRETCHING_Y);
isDrawThird = true;
invalidate();
}
});
valueAnimator.start();
}
private ValueAnimator getValueAni() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1.1f);
valueAnimator.setDuration(ANIMATION_TIME);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
return valueAnimator;
}
}
再来点福利,基于最新百度地图API开发的项目已开源,求star基于百度地图API开源项目