最近换了一个项目组,需要从新开发一个APP,美工给推荐了几个Loading效果,都不是很满意,总觉得有违和感,于是自己从网上找了一个还算满意的效果,自己用代码实现了一遍,顺便重温了一下属性动画,100多行代码轻轻松松搞定。
网上原图效果如下:
Loading的载体其实就是一个自定义的dialog,下面总结一下实现过程:
1,首先,要有一个自定义的view,用来实现一个能屏幕适配的圆点。这里只继承view,不继承surfaceview的原因是不需要频繁的重绘我们的圆点,所以也就不存在阻塞UI线程的问题。
/**
* 自定义圆点
* @author lizheng
*
*/
public class MyPointView extends View{
/**
* 点的半径
*/
public int pointW = 20;
public void setPointW(int pointW) {
this.pointW = pointW;
}
public MyPointView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
//点的圆心在父控件的正中心
canvas.drawCircle(getWidth()/2, getHeight()/2, pointW, paint);
super.onDraw(canvas);
}
}
2,圆点有了,在实现dialog之前,还需为他设定一些参数。这个diaolg应该是没有标题栏,没有背景,同时为了美观,背景的四角应该使用圆角效果。明确了这几点,首先我们来实现他的shape和style。
shape:
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<corners android:radius="20dp" />
<solid android:color="@color/BaseColor"/>
</shape>
style:
<style name="PointDialogStyle" parent="@android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@drawable/dialog_bg</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
在这里,我们为这个dialog设置了圆角,背景透明,无标题,背景色为纯色。
3,下面就可以开始自定义我们的dialog了,首先,为他实现一个layout,使用动态布局和静态布局都是OK的,这个layout应该是一个横向的线性布局,可以依次排列我们的圆点,并且居中显示,如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:id="@+id/mianView"
android:orientation="horizontal" >
</LinearLayout>
4,在开始实现自定义dialog之前,我们来规划一下他应该做哪些事
(1),在构造方法中设置style
(2),根据手机屏幕的宽高,动态计算dialog的宽高
(3),动态计算圆点的半径,并为dialog的父控件添加圆点,数量可控
(4),在页面加载完毕后,在回调内为每一个圆点添加属性动画
(5) , 为属性动画添加监听,并实现动画算法
下面给出完整实现代码:
/**
* 自定义Loading效果的Dialog
* @author lizheng
*
*/
public class MyPointDialog extends Dialog {
/**
* 点的总数
*/
public static final int POINT_NUM = 5;
/**
* 延迟时间
*/
public static final int DELAY = 100;
private Context context;
private LinearLayout mian;
private List<MyPointView> views;
private int mScreenW;
private int mScreenH;
private int dialogW;
private int dialogH;
public MyPointDialog(Context context) {
super(context, R.style.PointDialogStyle);
this.context = context;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mypoint_dialog_view);
initView();
getPointView();
}
private void getPointView() {
views = new ArrayList<MyPointView>();
LinearLayout.LayoutParams lay = new LinearLayout.LayoutParams(
mScreenW / 14, mScreenW / 14);
for (int i = 0; i < POINT_NUM; i++) {
MyPointView pointView = new MyPointView(context);
pointView.setLayoutParams(lay);
pointView.setPointW(mScreenW / 54);
views.add(pointView);
mian.addView(pointView);
}
}
private void initView() {
mian = (LinearLayout) findViewById(R.id.mianView);
Display display = getWindow().getWindowManager().getDefaultDisplay();
// 屏幕宽高
mScreenH = display.getHeight();
mScreenW = display.getWidth();
// dialog宽高
dialogW = (int) (mScreenW * 0.23);
dialogH = (int) (mScreenH * 0.1);
FrameLayout.LayoutParams fl = new FrameLayout.LayoutParams(
Utils.dip2px(context, dialogW), Utils.dip2px(context, dialogH));
mian.setLayoutParams(fl);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
//创建完毕回调
if (hasFocus) {
for (int i = 0; i < POINT_NUM; i++) {
final MyPointView view = views.get(i);
//计算圆点基于父控件的Y轴位置,公式为:dialog高度的一半 - 圆点父控件高度的一半
int height = mian.getHeight() / 2 - mScreenW / 28;
ValueAnimator va = ValueAnimator.ofInt(height, height
+ mScreenH / 30, height - mScreenH / 27, height);
va.setDuration(1800);//动画的持续时间
va.setRepeatCount(Integer.MAX_VALUE);//重复播放
va.setStartDelay(DELAY + DELAY * i);//延迟播放
va.setInterpolator(new DecelerateInterpolator());
va.start();
va.addUpdateListener(new AnimatorUpdateListener() {
@SuppressLint("NewApi")
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// Y轴方向变化参数
int parseInt = Integer.parseInt(animation
.getAnimatedValue().toString());
view.setY(parseInt);
// XY轴大小变化参数
float fraction = animation.getAnimatedFraction();
view.setScaleX(fraction < 0.5 ? 1 - fraction : fraction);
view.setScaleY(fraction < 0.5 ? 1 - fraction : fraction);
}
});
}
}
super.onWindowFocusChanged(hasFocus);
}
}
简单说一下动画算法的思路,每个圆点做的事其实是一样的,只是每个点比前一个延迟了近0.1秒的时间,通过使用ValueAnimator,设置圆点的起始位置,上下偏移位置,即可实现,最后在ValueAnimator 的监听方法中对我们圆点的Y轴坐标进行赋值即可。
原图中在位移的同时还有一个透明度的变化,这里为了迎合项目我做了一个修改,动态改变其大小,XY轴方向同时从1缩小至0.5,再从0.5放大至1。实现方法很很简单,getAnimatedFraction()参数返回的是一个从0至1的float值,其意义在于监听动画完成进度,通过判断getAnimatedFraction()的值的大小,就可以实现我们需要的大小变化效果,配合位移,个人觉得比原图更出彩。
至此,自定义扁平化的Loading效果已实现完毕,最近天天加班,比较累,有什么不足的地方还请大家提出,一起学习一起进步。