自定义Loading套路
一般自定义loading都是重写dialog,修改dialog内部的contentview,
先看下效果图,
demo里面包含了两个小loading,今天的目标就是实现这个两个小玩意,
- 描写自定义类,继承dialog,
- 在oncreat中设置外面传递过来的自定义view,
- 在builder属性中,给外面的dialog设置属性
- 这里我们模仿Android系统的做法用建造者模式来配置属性
下面看下dialog的代码,
public class ATWaterDialog extends Dialog {
// 提供配置dialog的一些属性
private Context mContext;
private int mLoadingViewLayout;
private String mLoadingDesc;
private boolean mCancelable;
private boolean mOutsideCancelable;
private TextView mTextView;
// 因为我们要用建造者模式,构造函数需要私有化
private ATWaterDialog(Context context) {
this(context, 0);
}
// 提供接受建造者的,构造函数,不对外公开,给builder用的
private ATWaterDialog(Builder builder) {
this(builder.mContext, 0);
mContext = builder.mContext;
mLoadingViewLayout = builder.mLoadingViewLayout;
mLoadingDesc = builder.mLoadingDesc;
mCancelable = builder.mCancelable;
mOutsideCancelable = builder.mOutsideCancelable;
}
private ATWaterDialog(Context context, int themeResId) {
super(context, R.style.Translucent_NoTitle);
}
下面是建造者的代码
public static class Builder {
private Context mContext;
private int mLoadingViewLayout;
private String mLoadingDesc;
private boolean mCancelable;
private boolean mOutsideCancelable;
// 构造函数中传入必传参数,这里我们dialog需要拿到context和外面的view布局
public Builder(Context context, int loadingViewLayout) {
mContext = context;
mLoadingViewLayout = loadingViewLayout;
}
public Builder loadingDesc(String loadingDesc) {
this.mLoadingDesc = loadingDesc;
return this;
}
public Builder cancelable(boolean cancelable) {
this.mCancelable = cancelable;
return this;
}
public Builder outsideCancelable(boolean outsideCancelable) {
this.mOutsideCancelable = outsideCancelable;
return this;
}
// 建造者用dialog接受他自己的方法,构造出来一个dialog
public ATWaterDialog build() {
return new ATWaterDialog(this);
}
}
下面就是给dialog设置样子,就是我们外面传递过来的layout
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 这里很简单就是在dialog onCreate中设置布局填充dialog
setContentView(mLoadingViewLayout);
mTextView = (TextView) findViewById(R.id.tv_gif_oading_desc);
if (null != mTextView) {
mTextView.setText(mLoadingDesc);
}
setCancelable(mCancelable);
setCanceledOnTouchOutside(mOutsideCancelable);
}
最后使用的我们的dialog
//有没有很简单像造房子一样,建造各种门,床,窗户,最后show就行了
atWaterDialog = new ATWaterDialog.Builder(this, R.layout.dialog_water_loading)
.cancelable(true)
.outsideCancelable(true)
.loadingDesc("我擦,加载....")
.build();
所以自定义dialog的时候,其实还是自定义view,
下面我们看下水瓶子的View如何实现
自定义view的过程都老生常谈的话题了,套路就不写了,
- 自定义属性
- 获取属性
- 测量
- 绘制
这里用的一个知识点就是画笔的绘制模式,那张景点的图大家都不陌生我就不copy了,我们把外面传递过来的水瓶子bitmap绘制到画布上,
在绘制之前,先把画布用橡皮擦,擦成透明的,然后把水瓶画上去,
这个时候就是一个透明的画布上面有一个空瓶子,
这时候我们绘制水流,用贝塞尔曲线绘制path,
然后画笔的图层模式设置成SRC_IN,就是绘制的水波和瓶子相交的时候,我们取上层,这样就留下了瓶子样式的水流,并且根据水波的Y值一直变动
private void drawTargetBitmap() {
path.reset();
bg.eraseColor(getResources().getColor(android.R.color.transparent));
// 当控制点的x坐标大于或等于终点x坐标时更改标识值
if (controlX >= defW) {
isIncrease = false;
}
// 当控制点的x坐标小于或等于起点x坐标时更改标识值
else if (controlX <= 0) {
isIncrease = true;
}
// 根据标识值判断当前的控制点x坐标是该加还是减
controlX = isIncrease ? controlX + 10 : controlX - 10;
if (controlY >= 0) {
// 波浪上移
controlY -= 1;
waveY -= 1;
} else {
// 超出则重置位置
waveY = WAVEY_SCALE * defH;
controlY = CONTROLY_SCALE * defH;
}
// 贝塞尔曲线的生成
path.moveTo(0, waveY);
// 两个控制点通过controlX,controlY生成
path.cubicTo(controlX / 2, waveY - (controlY - waveY), (controlX + defW) / 2, controlY, defW, waveY);
// 与下下边界闭合
path.lineTo(defW, defH);
path.lineTo(0, defH);
// 进行闭合
path.close();
mCanvas.drawBitmap(mBitmap, 0, 0, paint);
paint.setXfermode(porterDuffXfermode);
mCanvas.drawPath(path, paint);
paint.setXfermode(null);
}
然后在onDraw里面,把我们绘制的水流画布放到系统的canvas里面
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawTargetBitmap();
//这里直接把我们自己创建的bitmap放到系统的canvas里面,因为变化都在这里
canvas.drawBitmap(bg, getPaddingLeft(), getPaddingTop(), null);
if (isReflesh) {
invalidate();
}
}
主要的就是画笔模式的使用,和一个path的使用
圆环的view就更简单了,
- 提供自定义属性圆环宽度
- 距离view的外边距距离
绘制圆环
OK完成了
圆环的知识点就一个画笔的阴影模式,以及如何让绘制的圆环动起来
- 阴影模式
// 这里有四种模式有兴趣的可以看下源代码继承结构树
circlePaint.setShader(new SweepGradient(viewWidth, viewHeight, doughnutColors, null));
- 圆环的旋转,这里因为我们背景是圆角矩形,圆形都是我们绘制了,
- 所以我们作用动画的时候不能给view设置,因为那样圆形的矩形也会旋转,
- 所以我们在绘制圆角背景后,旋转画布,然后绘制圆环这样圆环就东西来了—哈哈
代码如下:
//绘制背景
canvas.drawRoundRect(rectBg, circleCorner, circleCorner, bgPaint);
//旋转画布
canvas.rotate(rotateDegree, viewWidth / 2, viewHeight / 2);
//绘制圆环
canvas.drawArc(oval, 90, 360, false, circlePaint);
属性动画提供画布旋转的偏移量
private void getValAniamtion() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1.F);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
rotateDegree = 360 * Float.valueOf(valueAnimator.getAnimatedValue().toString());
invalidate();
}
});
valueAnimator.setDuration(800);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
}