效果图如下:
加载中。。。
FansLoadingView主体代码:
/**
* zlww 2020-08-23
*/
public class FansLoadingView extends View {
private Paint progressPaint;
private Paint progressRectPaint;
private Paint progressStrokePaint;
private Paint progressShaderPaint;
private RectF progressBackgroundRect;
private final FansView fansView = new FansView();
private float progressdegree;// 旋转的角度
private float progress;
private Bitmap bitmap;// 记录当前 bitmap 资源的
private int leavesCount;// 记录需要生成的树叶数目的
private LinearGradient gradient;
private Path clipPath = new Path();//裁剪的路径
private String TAG = "debuging";
private static int COLOR_PROGRESS_BACKGROUND = Color.parseColor("#97FFFF");//背景的
private static int COLOR_PROGRESS = Color.parseColor("#EEEE00");//进度条
private static int COLOR_PROGRESS_STROKE = Color.argb(255, 60, 179, 113);
private int[] colors = {0x00000000, 0xf0ffffff, 0x00000000, 0xf0ffffff, 0x00000000};
private float[] pos = {0f, 0.50f, 0.70f, 0.76f, 1.0f};
private int defaultwidth = 20;
private int defaultheight = 20;
private float strokeWidth;
public FansLoadingView(Context context) {
super(context);
Log.d(TAG, "FansLoadingView: ssssssss1111111111");
init(context, null);
}
public FansLoadingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);//warp content 走这里
Log.d(TAG, "FansLoadingView: ssssssss22222222222222");
init(context, attrs);
}
public FansLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.d(TAG, "FansLoadingView: ssssssss3333333333333");
init(context, attrs);
}
private void init(Context ctx, AttributeSet attrs) {
// 初始化属性↓
TypedArray array = ctx.obtainStyledAttributes(attrs, R.styleable.FansLoadingView);
COLOR_PROGRESS_BACKGROUND = array.getColor(R.styleable.FansLoadingView_progress_background_color, COLOR_PROGRESS_BACKGROUND);
COLOR_PROGRESS_STROKE = array.getColor(R.styleable.FansLoadingView_progress_stroke_color, COLOR_PROGRESS_STROKE);
leavesCount = array.getInteger(R.styleable.FansLoadingView_leaves_count, 15);
int bitmapRes = array.getResourceId(R.styleable.FansLoadingView_petal_res, R.drawable.leaf);
bitmap = scaleBitmap(BitmapFactory.decodeResource(getResources(), bitmapRes), dp2px(16), dp2px(16));//将dp尺寸转为px,我们默认的花瓣大小为15
strokeWidth = array.getDimension(R.styleable.FansLoadingView_strokewidth,8);
array.recycle();
initPaint();
// 下面是初始化矩形
progressBackgroundRect = new RectF();// 具体还要在 onmesure中具体设置
}
private void initPaint() {
progressPaint = new Paint(); //进度的
progressPaint.setStyle(Paint.Style.FILL);
progressPaint.setColor(COLOR_PROGRESS_BACKGROUND);
progressPaint.setAntiAlias(true);
progressRectPaint = new Paint();
progressRectPaint.setStyle(Paint.Style.FILL);
progressRectPaint.setColor(COLOR_PROGRESS);
progressRectPaint.setAntiAlias(true);
progressShaderPaint = new Paint();
progressShaderPaint.setStyle(Paint.Style.FILL);
progressStrokePaint = new Paint(); //边框的
progressStrokePaint.setStyle(Paint.Style.STROKE);
progressStrokePaint.setColor(COLOR_PROGRESS_STROKE);
progressStrokePaint.setAntiAlias(true);
progressStrokePaint.setStrokeWidth(strokeWidth);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
|| MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
Log.d(TAG, "onMeasure: witdh is "+width+" height is "+height);
setMeasuredDimension(width, height);
// autoresetSize(width, height);
} else {//使用默认大小
this.getLayoutParams().width = dp2px(300);
this.getLayoutParams().height = dp2px(60);
setMeasuredDimension(this.getLayoutParams().width,this.getLayoutParams().height);
// autoresetSize(this.getLayoutParams().width , this.getLayoutParams().height);
}
}
// 自动根据当前的视图大小修改宽高
private void autoresetSize(int width, int height) {
Log.d(TAG, "autoresetSize: width"+width+" height "+height);
progressBackgroundRect.set(defaultwidth, defaultheight, width - defaultwidth, height - defaultheight);
fansView.setCurPostion(height);
clipPath.reset();
clipPath.addRoundRect(progressBackgroundRect,90,90, Path.Direction.CCW);//重新设置clippath
if (gradient == null)
gradient = new LinearGradient((float) (width * 0.60), defaultheight, (float) (width * 0.75), defaultheight * 4, colors, pos, Shader.TileMode.CLAMP);//具体也需要在onmesure中改
progressShaderPaint.setShader(gradient);//透明遮罩的
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//背景部分
canvas.drawRoundRect(progressBackgroundRect, 90, 90, progressPaint);
canvas.clipPath(clipPath);
canvas.drawRoundRect(defaultwidth,defaultheight,defaultwidth+progress,progressBackgroundRect.height() + defaultheight,90,90,progressRectPaint);
canvas.drawRoundRect(progressBackgroundRect, 90, 90, progressShaderPaint);//阴影在上
canvas.drawRoundRect(progressBackgroundRect, 90, 90, progressStrokePaint);//外边框
//画花瓣的,先画花瓣
drawPetal(canvas);
//画风扇的
drawFansAndCycle(canvas);
}
/*画风扇的,一些动画操作也主要针对这里
*每一次 draw 都是画布的新建,要有这个意识,这里要旋转
*/
private void drawFansAndCycle(Canvas canvas) {
canvas.save();
canvas.rotate(progressdegree, defaultwidth + fansView.getRadius(), defaultheight + fansView.getRadius());
canvas.drawCircle(defaultwidth + fansView.getRadius(), defaultheight + fansView.getRadius(), fansView.getRadius(), fansView.getFansCyclePaint());
canvas.drawPath(fansView.getPath(), fansView.getFansPaint());//画路径
canvas.restore();//恢复旋转前的样式
}
public void setProgressdegree(float progressdegree) {
this.progressdegree = progressdegree;
invalidate();
}
public float getProgress() {
return progress;
}
public void setProgress(float progress) {
this.progress = progress;
}
//关于小风扇的 start
private static class FansView {
private Paint fansPaint;
private Paint fansCyclePaint;
private static final int COLOR_FANS = COLOR_PROGRESS_STROKE;
private Path path;
private int radius;
public FansView() {
setupPaint();//这里只需要初始化画笔即可
}
private void setupPaint() {
fansPaint = new Paint();//扇叶的
fansPaint.setStyle(Paint.Style.FILL);
fansPaint.setColor(COLOR_FANS);
fansPaint.setAntiAlias(true);
fansCyclePaint = new Paint();//外面的边框
fansCyclePaint.setStyle(Paint.Style.STROKE);
fansCyclePaint.setColor(COLOR_FANS);
fansCyclePaint.setAntiAlias(true);
fansCyclePaint.setStrokeWidth(8);
}
private void initPath(int curheight) {
path = new Path();
// 风扇多高由高决定,所有很多地方都是用的 curheight
int offsetheight = 20;
radius = (curheight - 2 * offsetheight) / 2;//风扇圆的半径
int offsetwidth = 20;
RectF rectF1 = new RectF(offsetwidth + (radius >> 1), offsetheight, radius + offsetwidth + (radius >> 1), offsetheight + radius);// 上
RectF rectF2 = new RectF(offsetwidth + (radius >> 1), offsetheight + radius, radius + offsetwidth + (radius >> 1), curheight - offsetheight);// 下
RectF rectF3 = new RectF(offsetwidth, offsetheight + (radius >> 1), offsetwidth + radius, offsetheight + (radius >> 1) + radius);// 左
RectF rectF4 = new RectF(offsetwidth + radius, offsetheight + (radius >> 1), curheight - offsetwidth, offsetheight + (radius >> 1) + radius);// 右
path.addArc(rectF1, -90, 180);
path.addArc(rectF2, 90, 180);
path.addArc(rectF3, 180, 180);
path.addArc(rectF4, 0, 180);
}
public Paint getFansPaint() {
return fansPaint;
}
public Paint getFansCyclePaint() {
return fansCyclePaint;
}
public Path getPath() {
return path;
}
//重置位置的
public void setCurPostion(int length) {
initPath(length);//在此才重新设置高度等
}
public int getRadius() {
return radius;
}
}
private int dp2px(int dpValue) {
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
return px;
}
private Bitmap scaleBitmap(@NonNull Bitmap bitmap, float newwidth, float newheight) {
Bitmap newBitmap = null;
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scalewidth = newwidth / width;
float scaleheight = newheight / height;
Matrix matrix = new Matrix();
matrix.postScale(scalewidth, scaleheight);
newBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
return newBitmap;
}
//关于吹出的叶子的 start
private List<LeaveView> leaveViews;
private static class LeaveView {
//初始属性
private int rotateDirection;//顺时针还是逆时针转动的
private int phase;//有些运动的快,有些动的慢取值
private float defaultDelay;
private float waveHigh;
private int rotateAngle;
public int getRotateDirection() {
return rotateDirection;
}
public void setRotateDirection(int rotateDirection) {
this.rotateDirection = rotateDirection;
}
public int getPhase() {
return phase;
}
public void setPhase(int phase) {
this.phase = phase;
}
public float getDefaultDelay() {
return defaultDelay;
}
public void setDefaultDelay(float defaultDelay) {
this.defaultDelay = defaultDelay;
}
public float getWaveHigh() {
return waveHigh;
}
public void setWaveHigh(float waveHigh) {
this.waveHigh = waveHigh;
}
public int getRotateAngle() {
return rotateAngle;
}
public void setRotateAngle(int rotateAngle) {
this.rotateAngle = rotateAngle;
}
}
/**
* 画花瓣自我旋转和运动
*/
private void drawPetal(Canvas canvas) {
canvas.save();
canvas.translate(fansView.getRadius() + defaultwidth, fansView.getRadius());//叶子从扇叶中间开始出现
for (int i = 0; i < leaveViews.size(); i++) {
LeaveView leaveView = leaveViews.get(i);
Matrix matrixPetal = new Matrix();
double w = 2 * Math.PI / leaveView.getPhase();
float x = leaveView.getDefaultDelay() * progressBackgroundRect.width();
float xTranslate = x + distance;
float xNew = xTranslate % progressBackgroundRect.width();
double y = (leaveView.getWaveHigh() * Math.sin(w * xNew));// k 默认为 0 , y 要取反负数先
float angle = 4 * distance / progressBackgroundRect.width() * leaveView.getRotateDirection() * leaveView.getRotateAngle();//看转动的角度
matrixPetal.postRotate(angle, bitmap.getWidth() >> 1, bitmap.getHeight() >> 1);
matrixPetal.postTranslate(xNew, (float) y);//主要是 xnew在变化
if (xTranslate > progressBackgroundRect.width()){
//大于progressBackgroundRect.width()后才显示
if (xNew < progressBackgroundRect.width() - 2 * dp2px(defaultwidth)){
canvas.drawBitmap(bitmap, matrixPetal, null);
}
}
}
canvas.restore();
}
private float distance;//花瓣移动距离的
private Handler handler = new Handler();
private final Runnable runnable = new Runnable() {
@Override
public void run() {
distance++;
handler.postDelayed(this, 8);//每 8 毫秒执行一次
}
};
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
autoresetSize(getWidth(), getHeight());
initSetting();//初始化动画
}
/**
* 初始化叶子的数量
*/
private boolean initData = false;
private void initLeaveData() {
leaveViews = new ArrayList<>();
for (int i = 0; i < leavesCount; i++) {
LeaveView leaf = new LeaveView();
int randomDirection = (int) (Math.random() * 2 + 1);//生成结果是 [1,2)区间内的
int rotateDirection = randomDirection % 2 == 0 ? 1 : -1;// 1 为顺时针 -1 为逆时针
leaf.setRotateDirection(rotateDirection); //叶子旋转方向
double periodmax = progressBackgroundRect.width() / 3;
double periodmin = progressBackgroundRect.width() / 4;
int period = (int) (Math.random() * (periodmax - periodmin) + periodmin);
leaf.setPhase(period);//周期设置好了
double waveMax = fansView.getRadius() >> 1;
double waveMin = fansView.getRadius() >> 2;
float randomWave = (float) (Math.random() * (waveMax - waveMin) + waveMin);
leaf.setWaveHigh(randomWave);
double delay = Math.random();//[0,1)
leaf.setDefaultDelay((float) delay);//默认延迟系数
int randomAngle = (int) (Math.random() * 360 + 1);//[1,361)
leaf.setRotateAngle(randomAngle);
leaveViews.add(leaf);
}
}
private void initSetting() {
if (initData)
return;//已经初始化过了
initData = true;
Log.d(TAG, "initSetting: 初始化一系列的东西....");
initLeaveData();//初始化树叶数量,在这里才初始化,只进行一次
//风扇旋转的动画
ObjectAnimator rotate = ObjectAnimator.ofFloat(FansLoadingView.this, "progressdegree", 0, 360);
rotate.setDuration(2000);
rotate.setRepeatCount(ValueAnimator.INFINITE);//无限循环播放
rotate.setInterpolator(new LinearInterpolator());
rotate.setRepeatMode(ValueAnimator.RESTART);
rotate.start();
//加载进度
ObjectAnimator translate = ObjectAnimator.ofFloat(FansLoadingView.this, "progress", 0, progressBackgroundRect.width());
translate.setDuration(8 * 1000);
translate.setInterpolator(new LinearInterpolator());
translate.setStartDelay(2000);//延迟 2 秒开始
translate.start();
handler.post(runnable);//开始扇叶
}
}
布局文件:
<com.zhlw.animationexample.ui.home.FansLoadingView
android:id="@+id/fansloadingview"
android:layout_width="300dp"
android:layout_height="60dp"
app:petal_res="@drawable/leaf"
app:strokewidth="8dp"
app:leaves_count="15"/>
相关attr:
<declare-styleable name="FansLoadingView">
<attr name="progress_background_color" format="color"/>
<attr name="progress_stroke_color" format="color"/>
<attr name="color_fans" format="color"/>
<attr name="leaves_count" format="integer"/>
<attr name="petal_res" format="reference"/>
<attr name="strokewidth" format="dimension"/>
</declare-styleable>