这还只是张图片
本文由hadisi5216原创,这篇可不能匿名转载。
背景:我一哥们公司做智能设备的,该动画用在手机和家中网络连接时用,他让我看了下需求。刚看到这动画时感觉产品\UI设计的不错,想着试试。昨天开始做的,本来感觉很简单,但做起来貌似没那么简单;最后花了近一天时间终于搞定了。看看效果还行!
niceloading.gif
如果有想直接用的同道中人,看前半部分就行;如果想批评指正我的思考的看看后半部分
1.直接上代码(NiceLoadingView)
packagecom.hadisi.niceloading;importandroid.animation.ValueAnimator;
importandroid.content.Context;
importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.graphics.Canvas;
importandroid.graphics.Paint;
importandroid.graphics.Rect;
importandroid.util.AttributeSet;
importandroid.view.View;
importandroid.view.animation.LinearInterpolator;
/**
*Createdbyhadisi5216on2016/7/12.
*/
publicclassNiceLoadingViewextendsView{
privateContextmContext;
privatePaintmPaint;
privateintwidthSpecSize;
privateintheightSpecSize;
privateintradiusSmall=38;
privateintradiusbig=76;
privateintmoveX;
privateintXPoint;
privateintmState=-1;//0失败,1成功,-1默认
privatebooleanmflag;
privateValueAnimatoranimator;
publicNiceLoadingView(Contextcontext){
super(context);
}
publicNiceLoadingView(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
publicNiceLoadingView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
mContext=context;
}
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
}
@Override
protectedvoidonDraw(Canvascanvas){
super.onDraw(canvas);
mPaint=newPaint();
mPaint.setColor(0xFFFFBC53);
mPaint.setAntiAlias(true);
if(Math.abs(moveX)widthSpecSize*5/4)
{
XPoint=(moveX0)?XPoint=widthSpecSize*7/4
-Math.abs(moveX):widthSpecSize-widthSpecSize*7/4
+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)widthSpecSize&&Math.abs(moveX)widthSpecSize*3/2)
{
XPoint=(moveX0)?XPoint=widthSpecSize*3/2-Math.abs(moveX)
:widthSpecSize-widthSpecSize*3/2+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)widthSpecSize*3/4&&Math.abs(moveX)widthSpecSize*5/4)
{
XPoint=(moveX0)?XPoint=widthSpecSize*5/4-Math.abs(moveX):
widthSpecSize-widthSpecSize*5/4+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)widthSpecSize/2&&Math.abs(moveX)widthSpecSize){
XPoint=(moveX0)?XPoint=widthSpecSize-Math.abs(moveX):
widthSpecSize-widthSpecSize+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)widthSpecSize/4&&Math.abs(moveX)widthSpecSize*3/4){
XPoint=(moveX0)?XPoint=widthSpecSize*3/4-Math.abs(moveX):
widthSpecSize-widthSpecSize*3/4+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)0&&Math.abs(moveX)widthSpecSize/2){
XPoint=(moveX0)?XPoint=widthSpecSize/2-Math.abs(moveX):
widthSpecSize-widthSpecSize/2+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
//中间大圆
if(Math.abs(moveX)0&&Math.abs(moveX)widthSpecSize*5/4){
radiusbig=2*radiusSmall-radiusSmall*
(Math.abs(moveX))/(widthSpecSize*5/4);
radiusbig=(radiusbigradiusSmall)?radiusbig:radiusSmall;
canvas.drawCircle(widthSpecSize/2,heightSpecSize/2,radiusbig,mPaint);
}
if(Math.abs(moveX)12&&mState=0){
if(mState==0){
canvas.drawCircle(widthSpecSize/2,heightSpecSize/2,radiusbig,mPaint);
Bitmapbitmap=BitmapFactory.decodeResource(getContext().getResources(),
R.mipmap.connect_failed);
canvas.drawBitmap(bitmap,null,newRect(widthSpecSize/
2-radiusbig,heightSpecSize/2-radiusbig,widthSpecSize/
2+radiusbig,heightSpecSize/2+radiusbig),mPaint);
}
if(mState==1){
canvas.drawCircle(widthSpecSize/2,heightSpecSize/2,radiusbig,mPaint);
Bitmapbitmap=BitmapFactory.decodeResource(getContext().getResources(),
R.mipmap.connect_success);
canvas.drawBitmap(bitmap,null,newRect(widthSpecSize/
2-radiusbig,heightSpecSize/2-radiusbig,widthSpecSize/
2+radiusbig,heightSpecSize/2+radiusbig),mPaint);
}
}
}
publicvoidstart(){
if(animator!=null)
animator.cancel();
moveX=widthSpecSize*(-9/4);
mState=-1;
mflag=true;
post(newRunnable(){
@Override
publicvoidrun(){
animator=ValueAnimator.ofFloat(0f,1.0f);
animator.setDuration(3000);//没啥用
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(newLinearInterpolator());
animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
if(mState0){
moveX=(moveXwidthSpecSize*7/4)
?widthSpecSize*(-9/4):moveX+12;
if(Math.abs(moveX)12)
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}else{
if(moveX0)
moveX=(moveXwidthSpecSize*7/4)
?widthSpecSize*(-9/4):moveX+12;
elseif(moveX0&&mflag){
moveX+=12;
if(Math.abs(moveX)12)
mflag=false;
}
}
postInvalidate();
}
});
animator.start();
}
});
}
publicvoidsuccess(){
mState=1;
}
publicvoidfailed(){
mState=0;
}
}
项目已上传到github,戳着
2.怎么用?
布局文件中
com.hadisi.niceloading.NiceLoadingViewandroid:id="@+id/nice_loading"
android:layout_width="match_parent"
android:layout_height="100dp"/
你要用的地方
NiceLoadingViewniceLoading=(NiceLoadingView)findViewById(R.id.nice_loading);……
//开始连接时
niceLoading.start();
……
//连接成功时
niceLoading.success();
……
//连接失败时
niceLoading.failed();
3.我怎么实现的!
仔细看效果图可以得出:
1、有6个小圆依次从屏幕左侧移入屏幕中间,然后又依次从屏幕中间移出屏幕右侧。
2、中间有个大圆在随着小球的依次靠近慢慢变大,离开慢慢变小;注意在左侧第一个小圆到达中间时才出现大圆,在最后一个小圆准备向右侧移动时消失;大圆的半径在小圆半径和大圆半径之间。
3、不管何时得到成功和失败的状态,动画终止时都是在小圆依次从左边进入中间后。
4、动画完成后显示成功/失败图片和大圆。
1. 6个小圆的运动
我是这样想的:当第1个小圆移动到widthSpecSize/4(widthSpecSize 为控件的宽度)时第2个小圆开始移动、当第2个小圆移动到widthSpecSize/4时第3个小圆开始移动......当第5个小圆移动到widthSpecSize/4 时第6个小圆开始移动、第6个小圆移动到widthSpecSize/2 时继续移动、当第6个小圆移动到widthSpecSize 3/4时第5个小圆开始移动......当第2个小圆移动到widthSpecSize 3/4时第一个小圆开始移动、最后第1个小球移出屏幕右侧,到此为一个循环。
假设有一个位移变量moveX,moveX在不断增加,其变化范围为(a,b);可以看出按照我的想法,第1个小圆在范围的两边时开始移动、第6个小球在变化范围的中间部分开始移动。
我们可以继续假设变化范围为(-a,a),这样第1个小圆在范围的绝对值大时开始移动、第6个小球在变化范围的绝对值小时开始移动;其实这种重复的动作很容易想到绝对值控制
找张纸画下很容易得到moveX的变化范围在(-widthSpecSize 7/4 , widthSpecSize 7/4)之间。
自己画的图,有点丑
对照图很快可以得出下面代码
if(Math.abs(moveX)widthSpecSize*5/4){XPoint=(moveX0)?XPoint=widthSpecSize*7/4-Math.abs(moveX):
widthSpecSize-widthSpecSize*7/4+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)widthSpecSize&&Math.abs(moveX)widthSpecSize*3/2)
{
XPoint=(moveX0)?XPoint=widthSpecSize*3/2-Math.abs(moveX):
widthSpecSize-widthSpecSize*3/2+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)widthSpecSize*3/4&&Math.abs(moveX)widthSpecSize*5/4)
{
XPoint=(moveX0)?XPoint=widthSpecSize*5/4-Math.abs(moveX):
widthSpecSize-widthSpecSize*5/4+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)widthSpecSize/2&&Math.abs(moveX)widthSpecSize)
{
XPoint=(moveX0)?XPoint=widthSpecSize-Math.abs(moveX):
widthSpecSize-widthSpecSize+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)widthSpecSize/4&&Math.abs(moveX)widthSpecSize*3/4)
{
XPoint=(moveX0)?XPoint=widthSpecSize*3/4-Math.abs(moveX):
widthSpecSize-widthSpecSize*3/4+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
if(Math.abs(moveX)0&&Math.abs(moveX)widthSpecSize/2){
XPoint=(moveX0)?XPoint=widthSpecSize/2-Math.abs(moveX):
widthSpecSize-widthSpecSize/2+Math.abs(moveX);
canvas.drawCircle(XPoint,heightSpecSize/2,radiusSmall,mPaint);
}
中间变化的大圆在左侧第1个小圆到达中间时才出现大圆,在第1个小圆准备向右侧移动时消失,变化范围(-widthSpecSize 5/4 , widthSpecSize 5/4)之间。大圆的半径在小圆半径和大圆半径之间,我们用radiusbig = radiusbig - radiusSmall (Math.abs(moveX)) / (widthSpecSize 5/4)计算大圆半径,可以得到慢慢变大和变小的效果,然后控制在小于radiusSmall时用radiusSmall。
if(Math.abs(moveX)0&&Math.abs(moveX)widthSpecSize*5/4){radiusbig=radiusbig-radiusSmall*(Math.abs(moveX))/(widthSpecSize*5/4);
radiusbig=(radiusbigradiusSmall)?radiusbig:radiusSmall;
canvas.drawCircle(widthSpecSize/2,heightSpecSize/2,radiusbig,mPaint);
}
3. 动画终止的控制
正常当一个循环结束时我们需要重新给moveX赋值为widthSpecSize * (-7/4),当收到成功或失败状态时需要判断当前的状态,等到动画进行到结束状态(小圆依次从左边进入中间后)。见下面代码,mState为当前状态(0失败,1成功,-1默认)。
我重新赋值时将moveX设为 widthSpecSize * (-9/4),因为一个循环结束后有点停顿会感觉舒服点,这个无所谓,自己感觉而已
if(mState0){moveX=(moveXwidthSpecSize*7/4)
?widthSpecSize*(-9/4):moveX+12;
}else{
if(moveX0)
moveX=(moveXwidthSpecSize*7/4)
?widthSpecSize*(-9/4):moveX+12;
elseif(moveX0&&mflag){
moveX+=12;
if(Math.abs(moveX)12)
mflag=false;
}
}
4. 显示成功/失败图片
这个简单,在收到成功或失败状态,待动画完成时先画一个大圆,再画一个bitmap
if(Math.abs(moveX)12&&mState=0){if(mState==0){
canvas.drawCircle(widthSpecSize/2,
heightSpecSize/2,radiusbig,mPaint);
Bitmapbitmap=BitmapFactory.decodeResource(getContext().getResources(),R.mipmap.connect_failed);
canvas.drawBitmap(bitmap,null,newRect(widthSpecSize
/2-radiusbig,heightSpecSize/2-radiusbig,
widthSpecSize/2+radiusbig,heightSpecSize/2+radiusbig),mPaint);
}
if(mState==1){
canvas.drawCircle(widthSpecSize/2,heightSpecSize/2,radiusbig,mPaint);
Bitmapbitmap=BitmapFactory.decodeResource(getContext().getResources(),R.mipmap.connect_success);
canvas.drawBitmap(bitmap,null,newRect(widthSpecSize/2
-radiusbig,heightSpecSize/2-radiusbig,widthSpecSize
/2+radiusbig,heightSpecSize/2+radiusbig),mPaint);
}
}
5.优化
可以优化,将paint的颜色等属性、大小圆的半径、优化画小圆的逻辑,使小圆个数可变..........
其实核心的就是想法,随便怎么优化。反正我就弄到这了,油而不腻,我觉得挺好,不需要太多优化。吼吼....
南京牧狼文化传媒有限公司简介:
牧狼传媒,牧者之心,狼者之性,以牧之谦卑宽容之心待人,以狼之团结无畏之性做事!
公司注册资金100万,主营众筹全案服务、网站营销全案服务、网站建设、微信小程序开发、电商网店设计、H5页面设计、腾讯社交广告投放以及电商营销推广全案等相关业务,致力于为客户提供更有价值的服务,创造让用户满意的效果!
为百度官方及其大客户、苏宁易购、金山WPS秀堂、美的、创维家电、新东方在线、伊莱克斯、宝丽莱等国内国外知名品牌服务过,服务经验丰富!同时,公司也是南京电子商务协会会员单位、猪八戒网官方认证签约服务商、江苏八戒服务网联盟、南京浦口文化产业联合会会员单位,可以为您提供更好的服务!
主营项目:众筹全案服务、网站营销全案服务、网站建设、微信小程序开发、电商网店设计、H5页面设计、腾讯社交广告投放、竞价托管、网站优化、电商代运营等
合作客户:百度、苏宁易购、饿了么、美的、创维家电、新东方在线、宝丽莱、金山WPS秀堂、伊莱克斯
资质荣誉:百度商业服务市场2017年度最佳图片服务商、南京电子商务协会会员单位、猪八戒网官方认证签约服务商、江苏八戒服务网联盟、南京浦口文化产业联合会会员单位、八戒通TOP服务商、"易拍即合杯"H5创意大赛"三等奖"。