Header
看见一些loading,有时候对于刚入门的开发者来说,当产品中有一些效果需求时,一般会找找别人写的,那么就分享下关于loading和progressbar的自定义,看完就会,很简单,也就不再需要用别人的了,看效果吧
关于自定义控件的一些知识点网上可以搜搜。
CircleProgressBar
TextProgressBar
WaterRippleProgressBar
RoundBallProgressBar
FlyLoadingView
HeartBeatLoadingView
Body
六个效果中,其实所用到的东西都大同小异,大致说一下吧。
CircleProgressBar
1.绘制灰色背景
2.绘制蓝色圆弧,弧度要使用动态数据
3.绘制中间字体,数据取自动态数据
看代码吧
attrs.xml
<declare-styleable name="CircleProgressBar">
<attr name="circle_background_color" format="color"/>
<attr name="circle_color" format="color"/>
<attr name="circle_text_color" format="color"/>
<attr name="circle_text_size" format="dimension"/>
</declare-styleable>
public class CircleProgressBar extends View {
private int border_length;
private RectF rectF;
private Paint background_circle_paint;
private Paint circle_paint;
private Paint text_paint;
private int background_color;
private int circle_color;
private int text_color;
private int text_size = 23;
private int currentData = 0;
private int maxData = 100;
public CircleProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs,R.styleable.CircleProgressBar);
background_color = typedArray.getColor(R.styleable.CircleProgressBar_circle_background_color, Color.parseColor("#cccccc"));
circle_color = typedArray.getColor(R.styleable.CircleProgressBar_circle_color,Color.parseColor("#3333ff"));
text_color = typedArray.getColor(R.styleable.CircleProgressBar_circle_text_color,Color.parseColor("#3333ff"));
text_size = (int) typedArray.getDimension(R.styleable.CircleProgressBar_circle_text_size,23);
initView();
}
public CircleProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void initView(){
background_circle_paint = new Paint();
background_circle_paint.setStyle(Paint.Style.STROKE);
background_circle_paint.setColor(background_color);
background_circle_paint.setAntiAlias(true);
circle_paint = new Paint();
circle_paint.setStyle(Paint.Style.STROKE);
circle_paint.setColor(circle_color);
circle_paint.setAntiAlias(true);
circle_paint.setStrokeCap(Paint.Cap.ROUND);
text_paint = new Paint();
text_paint.setColor(text_color);
text_paint.setTextSize(dp2px(text_size));
text_paint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
border_length = Math.min(width,height);
rectF = new RectF(border_length * (float)0.1,border_length * (float)0.1,border_length * (float)0.9,border_length * (float)0.9);
background_circle_paint.setStrokeWidth(border_length * (float)0.05);
circle_paint.setStrokeWidth(border_length * (float)0.05);
setMeasuredDimension(border_length,border_length);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackgroundCircle(canvas);
drawCircle(canvas);
drawText(canvas);
}
private void drawBackgroundCircle(Canvas canvas){
canvas.drawArc(rectF,-90,360,false,background_circle_paint);
canvas.save();
}
private void drawCircle(Canvas canvas){
float sweepAngle = (currentData/(float)maxData) * (float)360;
canvas.drawArc(rectF,-90,sweepAngle,false,circle_paint);
canvas.save();
}
private void drawText(Canvas canvas){
Rect rect = new Rect();
text_paint.getTextBounds(currentData + "%",0,(currentData + "%").length(),rect);
canvas.drawText(currentData + "%",getWidth() / 2 - rect.width() / 2,getHeight() / 2 + rect.height() / 2,text_paint);
canvas.save();
}
public void progress(int data){
if (data < 0){
data = 0;
}
if (data > maxData){
data = maxData;
}
currentData = data;
invalidate();
}
public void goToProgress(int data){
if (data < 0){
data = 0;
}
if (data > maxData){
data = maxData;
}
final ValueAnimator animator = ValueAnimator.ofFloat(currentData,data);
animator.setDuration((long) Math.abs(currentData - data) * 30);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float)animator.getAnimatedValue();
currentData = (Math.round(value*10))/10;
invalidate();
}
});
animator.start();
}
public int getCurrentData(){
return currentData;
}
public int getMaxData(){
return maxData;
}
public void setMaxData(int maxData){
this.maxData = maxData;
}
/**
* px dp 转换
* @param dpval
* @return
*/
protected int dp2px(int dpval){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpval,getResources().getDisplayMetrics());
}
}
TextProgressBar
1.绘制灰色直线
2.绘制蓝色动态直线
3.绘制字体
4.绘制一条白线,先于第三步,位置动态处于字体下方,以保证字体不会被灰色直线混淆
attrs.xml
<declare-styleable name="TextProgressBar">
<attr name="bar_background_color" format="color"/>
<attr name="bar_color" format="color"/>
<attr name="text_color" format="color"/>
<attr name="text_size" format="dimension"/>
<attr name="unit_text" format="string"/>
</declare-styleable>
public class TextProgressBar extends View {
/**
* bar 背景颜色
*/
private int bar_background_color = Color.parseColor("#cccccc");
/**
* bar 颜色
*/
private int bar_color = Color.parseColor("#3333FF");
/**
* 文字颜色
*/
private int text_color = Color.parseColor("#3333FF");
/**
*文字字体大小
*/
private int text_size = 18;
/**
* 单位内容
*/
private String unit_text = "%";
/**
* bar 背景画笔
*/
private Paint barBgPaint;
/**
* bar 画笔
*/
private Paint barPaint;
/**
* 文字画笔
*/
private Paint textPaint;
/**
* 文字背景画笔
*/
private Paint textBgPaint;
/**
* 当前数据
*/
private float currentData = 0;
/**
* 总数据
*/
private float maxData = 100;
/**
* 文字矩形范围
*/
private Rect text_rect;
public TextProgressBar(Context context) {
super(context,null);
}
public TextProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs,0);
/**
* xml 属性设置获取
*/
TypedArray typedArray = getContext().obtainStyledAttributes(attrs,R.styleable.TextProgressBar);
bar_background_color = typedArray.getColor(R.styleable.TextProgressBar_bar_background_color,Color.parseColor("#cccccc"));
bar_color = typedArray.getColor(R.styleable.TextProgressBar_bar_color,Color.parseColor("#3333FF"));
text_color = typedArray.getColor(R.styleable.TextProgressBar_text_color,Color.parseColor("#3333FF"));
text_size = (int) typedArray.getDimension(R.styleable.TextProgressBar_text_size,18);
if (!TextUtils.isEmpty(typedArray.getString(R.styleable.TextProgressBar_unit_text))){
unit_text = typedArray.getString(R.styleable.TextProgressBar_unit_text);
}
initView();
}
public TextProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 画笔初始化
*/
private void initView(){
barBgPaint = new Paint();
barBgPaint.setColor(bar_background_color);
barBgPaint.setStrokeWidth(dp2px(2));
barBgPaint.setAntiAlias(true);
barBgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
barPaint = new Paint();
barPaint.setColor(bar_color);
barPaint.setStrokeWidth(dp2px(2));
barPaint.setAntiAlias(true);
barPaint.setStyle(Paint.Style.FILL_AND_STROKE);
textPaint = new Paint();
textPaint.setColor(text_color);
textPaint.setStrokeWidth(dp2px(1));
textPaint.setTextSize(text_size);
textPaint.setAntiAlias(true);
textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
text_rect = new Rect();
textPaint.getTextBounds(currentData + unit_text,0,(currentData + unit_text).length(),text_rect);
textBgPaint = new Paint();
textBgPaint.setColor(Color.parseColor("#ffffff"));
textBgPaint.setStrokeWidth(dp2px(2));
textBgPaint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置画布宽高为:宽度为设置宽度,高度为文字高度的2倍
*/
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),text_rect.height() * 2);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBarBackground(canvas);
drawBar(canvas);
drawText(canvas);
}
/**
* 绘制进度条背景
* @param canvas
*/
private void drawBarBackground(Canvas canvas){
canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,barBgPaint);
canvas.save();
}
/**
* 绘制进度条bar
* @param canvas
*/
private void drawBar(Canvas canvas){
int scale = (int)((float)currentData * getWidth() / maxData);
canvas.drawLine(0,getHeight()/2,scale,getHeight()/2,barPaint);
canvas.save();
}
/**
* 绘制进度文字
* @param canvas
*/
private void drawText(Canvas canvas){
text_rect = new Rect();
textPaint.getTextBounds(currentData + unit_text,0,(currentData + unit_text).length(),text_rect);
int currentX = (int) ((getWidth() - text_rect.width()) * currentData / maxData) ;
canvas.drawLine(currentX - 2 ,getHeight() / 2,currentX + text_rect.width() + 2,getHeight() / 2,textBgPaint);
canvas.save();
canvas.drawText(currentData + unit_text,(int)((float)currentData * (getWidth() - text_rect.width()) / maxData),(getHeight() * 3) / 4,textPaint);
canvas.save();
}
/**
* px dp 转换
* @param dpval
* @return
*/
protected int dp2px(int dpval){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpval,getResources().getDisplayMetrics());
}
/**
* 获取当前数值
* @return
*/
public float getCurrentData(){
return currentData;
}
/**
* 获取最大数值
* @return
*/
public float getMaxData(){
return maxData;
}
/**
* 设置最大数值
* @param maxData
*/
public void setMaxData(int maxData){
this.maxData = maxData;
}
/**
* 设置单位文字
* @param text
*/
public void setUnitText(String text){
unit_text = text;
}
/**
* 静态显示进度
* @param i
*/
public void progress(float i){
if (i > maxData){
i = maxData;
}
if (i < 0){
i = 0;
}
currentData = i;
invalidate();
}
/**
* 动态显示进度
* @param i
* @param interpolator
*/
public void goToProgress(float i, TimeInterpolator interpolator){
if (i > maxData){
i = maxData;
}
if (i < 0){
i = 0;
}
final ValueAnimator animator = ValueAnimator.ofFloat(currentData,i);
animator.setDuration((long) Math.abs(currentData - i) * 30);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float)animator.getAnimatedValue();
currentData = (float)(Math.round(value*10))/10;
invalidate();
}
});
if (null != interpolator){
animator.setInterpolator(interpolator);
}
animator.start();
}
}
WaterRippleProgressBar
这个控件,其实就是用到了正弦函数
y = Asin(wx+b)+h ,这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;
还用到了 canvas.clipPath,裁剪
attrs.xml
<declare-styleable name="WaterRippleProgressBar">
<attr name="water_color" format="color"/>
<attr name="water_text_color" format="color"/>
<attr name="water_text_size" format="dimension"/>
</declare-styleable>
public class WaterRippleProgressBar extends View {
// 影响三角函数的初相
private float move;
// 正方形的宽高
private int len;
// 存放第一条水波Y值
private float[] firstWaterLine;
// 第二条
private float[] secondWaterLine;
// 水球的增长值
int up = 0;
// 画水球的画笔
private Paint waterPaint;
//颜色
private int color;
// 剪切圆的半径
private int clipRadius;
//当前进度值
private float targetData = 0;
//总进度值
private float MaxData = 100;
//文字画笔
private Paint textPaint;
//字体大小
private int text_size = 20;
//字体颜色
private int text_color = Color.parseColor("#333333");
public WaterRippleProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs,R.styleable.WaterRippleProgressBar);
color = typedArray.getColor(R.styleable.WaterRippleProgressBar_water_color,Color.parseColor("#6633ff"));
text_color = typedArray.getColor(R.styleable.WaterRippleProgressBar_water_text_color,Color.parseColor("#333333"));
text_size = (int) typedArray.getDimension(R.styleable.WaterRippleProgressBar_water_text_size,20);
initView();
}
public WaterRippleProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//初始化画笔,颜色,水波纹动态等
private void initView() {
waterPaint = new Paint();
waterPaint.setAntiAlias(true);
textPaint = new Paint();
textPaint.setColor(text_color);
textPaint.setAntiAlias(true);
color = Color.parseColor("#6633ff");
moveWaterLine();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 通过测量规则获得宽和高
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
// 取出最小值
len = Math.min(width, height);
Log.e("width", "width = " + len);
firstWaterLine = new float[len];
secondWaterLine = new float[len];
clipRadius = (len / 2) - 45;
// text_size = len / 15;
textPaint.setTextSize(dp2px(text_size));
setMeasuredDimension(len, len);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawWaterView(canvas);
drawText(canvas);
drawCircle(canvas);
}
//动态水波纹
private void moveWaterLine() {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
move += 0.1;
postInvalidate();
}
}, 200, 20);
}
/**
* 画水球的功能
*
* @param canvas
*/
private void drawWaterView(Canvas canvas) {
// y = Asin(wx+b)+h ,这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;
// 将周期定为view总宽度
float mCycleFactorW = (float) (2 * Math.PI / len);
// 得到第一条波的y值
for (int i = 0; i < len; i++) {
firstWaterLine[i] = (float) (7.5 * Math
.sin(mCycleFactorW * i + move) - up);
}
// 得到第一条波的y值
for (int i = 0; i < len; i++) {
secondWaterLine[i] = (float) (11.25 * Math.sin(mCycleFactorW * i
+ move + 10) - up);
}
canvas.save();
// 裁剪成圆形区域
Path path = new Path();
waterPaint.setColor(color);
path.reset();
canvas.clipPath(path);
path.addCircle(len / 2, len / 2, clipRadius, Path.Direction.CCW);
canvas.clipPath(path, android.graphics.Region.Op.REPLACE);
// 将坐标系移到底部
canvas.translate(0, len / 2 + clipRadius);
for (int i = 0; i < len; i++) {
canvas.drawLine(i, firstWaterLine[i], i, len, waterPaint);
}
for (int i = 0; i < len; i++) {
canvas.drawLine(i, secondWaterLine[i], i, len, waterPaint);
}
canvas.restore();
}
private void drawCircle(Canvas canvas){
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(color);
paint.setStrokeWidth(dp2px(1));
paint.setAntiAlias(true);
canvas.drawCircle(getWidth()/2,getHeight()/2,clipRadius,paint);
canvas.save();
}
/**
* 绘制中间文字
* @param canvas
*/
private void drawText(Canvas canvas){
Rect rect = new Rect();
textPaint.getTextBounds(targetData + "%",0,(targetData + "%").length(),rect);
canvas.drawText(targetData + "%",getWidth() / 2 - rect.width() / 2,getHeight() / 2 + rect.height() / 2,textPaint);
canvas.save();
}
/**
* 动态水波纹
* @param trueData
*/
public void goToProgress(float trueData) {
if (trueData > MaxData){
trueData = MaxData;
}
if (trueData < 0){
trueData = 0;
}
final ValueAnimator animator = ValueAnimator.ofFloat(targetData,trueData);
animator.setDuration((long) Math.abs(targetData - trueData) * 30);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float)animator.getAnimatedValue();
targetData = (float)(Math.round(value*10))/10;
up = (int) (targetData / MaxData * clipRadius * 2);
postInvalidate();
}
});
postDelayed(new Runnable() {
@Override
public void run() {
animator.start();
}
},300);
}
public float getCurrentData(){
return targetData;
}
public float getMaxData(){
return MaxData;
}
public void setMaxData(float maxData){
this.MaxData = maxData;
}
/**
* px dp 转换
* @param dpval
* @return
*/
protected int dp2px(int dpval){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpval,getResources().getDisplayMetrics());
}
}
RoundBallProgressBar
这个比较简单,就是起线程或者Timer,动态修改每个圆点的半径值就可以了
public class RoundBallProgressBar extends View {
private double cosX;
private Paint ball_paint;
private int ball_color = Color.parseColor("#3333ff");
private float[] radius = {(float)0.01,(float)0.02,(float)0.03,(float)0.04,(float)0.03,(float)0.002,(float)0.001,(float)0.000};
private int state = 2;
private int speed = 100;
public enum speeds{
slow,
normal,
fast
}
private speeds speedss = speeds.normal;
public RoundBallProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs,R.styleable.RoundBallProgressBar);
ball_color = typedArray.getColor(R.styleable.RoundBallProgressBar_ball_color,Color.parseColor("#3333ff"));
state = typedArray.getInt(R.styleable.RoundBallProgressBar_loading_speed,2);
if (state == 1){
speed = 200;
speedss = speeds.slow;
}else if (state == 2){
speed = 100;
speedss = speeds.normal;
}else{
speed = 50;
speedss = speeds.fast;
}
initView();
}
private void initView(){
ball_paint = new Paint();
ball_paint.setColor(ball_color);
ball_paint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int len = Math.min(width,height);
// rectF = new RectF(len * (float)0.1,len * (float)0.1,len * (float) 0.9,len * (float)0.9);
cosX = Math.sqrt(Math.pow(len * 0.3,2)/2);
setMeasuredDimension(len,len);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawRoundBall(canvas);
}
private void drawRoundBall(Canvas canvas){
canvas.drawCircle(getWidth()/2,getHeight() * (float)0.2,getWidth() * radius[0],ball_paint);
canvas.drawCircle(getWidth()/2 + (float)cosX,getHeight()/2-(float)cosX,getWidth() * radius[1],ball_paint);
canvas.drawCircle(getWidth()/2 + getWidth() * (float)0.3,getHeight()/2,getWidth() * radius[2],ball_paint);
canvas.drawCircle(getWidth()/2 + (float)cosX,getHeight()/2 + (float)cosX,getWidth() * radius[3],ball_paint);
canvas.drawCircle(getWidth()/2,getHeight()/2 + getHeight() * (float)0.3,getWidth() * radius[4],ball_paint);
canvas.drawCircle(getWidth()/2 - (float)cosX,getHeight()/2 + (float)cosX,getWidth() * radius[5],ball_paint);
canvas.drawCircle(getWidth()/2 - getWidth() * (float)0.3,getHeight()/2,getWidth() * radius[6],ball_paint);
canvas.drawCircle(getWidth()/2 - (float)cosX,getWidth()/2 - (float)cosX,getWidth() * radius[7],ball_paint);
canvas.save();
}
public void setSpeed(speeds speed1){
speedss = speed1;
if (speedss == speeds.fast){
speed = 50;
state = 3;
}else if (speedss == speeds.normal){
speed = 100;
state = 2;
}else{
speed = 200;
state = 1;
}
postInvalidate();
}
public void startLoading(){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
changeFloat();
postInvalidate();
}
},300,speed);
}
private void changeFloat(){
float temp = radius[radius.length - 1];
for (int i = radius.length - 1;i > 0 ; i --){
radius[i] = radius[i - 1];
}
radius[0] = temp;
}
}
FlyLoadingView
这个其实也用到了正弦函数,不过是取绝对值,这样云的效果就出来了,然后飞机的抖动其实也是取函数某个点的高度,加上飞机静态高度,就可以让飞机随着云的变化而变化
public class FlyLoadingView extends View {
private Paint bacPaint;
private Paint cloudPaint1;
private Paint cloudPaint2;
// 存放第一条水波Y值
private float[] firstWaterLine;
// 第二条
private float[] secondWaterLine;
// 影响三角函数的初相
private float move;
// 剪切圆的半径
private int clipRadius;
public FlyLoadingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
}
public void initView(){
bacPaint = new Paint();
bacPaint.setColor(Color.parseColor("#ff51add8"));
bacPaint.setAntiAlias(true);
bacPaint.setStrokeWidth(1);
bacPaint.setStyle(Paint.Style.FILL_AND_STROKE);
cloudPaint1 = new Paint();
cloudPaint1.setColor(Color.parseColor("#ffffffff"));
cloudPaint1.setAntiAlias(true);
cloudPaint1.setStrokeWidth(1);
cloudPaint2 = new Paint();
cloudPaint2.setColor(Color.parseColor("#ffffffff"));
cloudPaint2.setAntiAlias(true);
cloudPaint2.setStrokeWidth(1);
moveWaterLine();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int len = Math.min(width,height);
firstWaterLine = new float[len];
secondWaterLine = new float[len];
clipRadius = (int) ((len / 2) * 0.8);
setMeasuredDimension(len,len);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBac(canvas);
drawWaterView(canvas);
drawPlane(canvas);
}
private void drawBac(Canvas canvas){
RectF rectF = new RectF(getWidth() * (float)0.1,getWidth() * (float)0.1,getWidth() * (float)0.9,getWidth() * (float)0.9);
canvas.drawArc(rectF,0,360,false,bacPaint);
canvas.save();
}
private void drawWaterView(Canvas canvas) {
// y = Asin(wx+b)+h ,这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;
// 将周期定为view总宽度
// float mCycleFactorW = (float) (2 * Math.PI / getWidth());
float mCycleFactorW = (float) 0.05;
// 得到第一条波的y值
for (int i = 0; i < getWidth(); i++) {
firstWaterLine[i] = (float) (13 * -Math.abs(Math
.sin(mCycleFactorW * i + move)) - (getWidth() / 6) - 10);
}
// 得到第一条波的y值
for (int i = 0; i < getWidth(); i++) {
secondWaterLine[i] = (float) (13 * -Math.abs(Math.sin(mCycleFactorW * i
+ move + 15)) - ((getWidth() * 2) / 9) - 13);
}
canvas.save();
// 裁剪成圆形区域
Path path = new Path();
path.reset();
canvas.clipPath(path);
path.addCircle(getWidth() / 2, getWidth() / 2, clipRadius, Path.Direction.CCW);
canvas.clipPath(path, Region.Op.REPLACE);
// 将坐标系移到底部
canvas.translate(0, getWidth() / 2 + clipRadius);
for (int i = 0; i < getWidth(); i++) {
canvas.drawLine(i, firstWaterLine[i], i, getWidth(), cloudPaint1);
}
for (int i = 0; i < getWidth(); i++) {
canvas.drawLine(i, secondWaterLine[i], i, getWidth(), cloudPaint2);
}
canvas.restore();
canvas.save();
}
//动态水波纹
private void moveWaterLine() {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
move += 0.1;
postInvalidate();
}
}, 200, 15);
}
private void drawPlane(Canvas canvas){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.plane);
int newWidth = (int) (getWidth() * 0.4);
float scale = ((float)newWidth)/bitmap.getWidth();
Matrix matrix = new Matrix();
matrix.setRotate(12,bitmap.getWidth()/2,bitmap.getHeight()/2);
matrix.postScale(scale,scale);
Bitmap newbitmap = bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
canvas.drawBitmap(newbitmap,getWidth()/2 - newbitmap.getWidth()/2,getHeight()/2 - newbitmap.getHeight() *2/3 + firstWaterLine[0] + (getWidth() / 6) + 20,bacPaint);
bitmap.recycle();
newbitmap.recycle();
}
}
HeartBeatLoadingView
这个控件,用到了 canvas.clipPath
public class HeartBeatLoadingView extends View {
private Paint bacPaint;
private Paint whitePaint;
private Paint redPaint;
private float current = 0;
public HeartBeatLoadingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView(){
bacPaint = new Paint();
bacPaint.setColor(Color.parseColor("#555555"));
bacPaint.setAntiAlias(true);
bacPaint.setStrokeWidth(1);
whitePaint = new Paint();
whitePaint.setColor(Color.parseColor("#ffffff"));
whitePaint.setAntiAlias(true);
whitePaint.setStrokeWidth(1);
redPaint = new Paint();
redPaint.setColor(Color.RED);
redPaint.setAntiAlias(true);
redPaint.setStrokeWidth(1);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 通过测量规则获得宽和高
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int len = Math.min(width,height);
setMeasuredDimension(len,len);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBac(canvas);
}
private void drawBac(Canvas canvas){
RectF rectF = new RectF(0,0,getWidth(),getHeight());
canvas.drawRect(rectF,whitePaint);
RectF rectF1 = new RectF(current - getWidth() * (float)0.3,0,current,getHeight());
canvas.drawRect(rectF1,redPaint);
Path path = new Path();
path.moveTo(0,(float) getHeight()/2 - (getHeight() * (float)0.01));
path.lineTo(getWidth() * (float)0.3 - getWidth() * (float)0.01,(float)getHeight()/2 - (getHeight() * (float)0.01));
path.lineTo(getWidth() * (float)0.4,(float)getHeight()/2 - (getHeight() * (float)0.2) - (getHeight() * (float)0.02));//上30
// path.lineTo(getWidth() * (float)0.4 + getWidth() * (float)0.01,(float)getHeight()/2 - (getHeight() * (float)0.2));
path.lineTo(getWidth()* (float)0.6,(float)getHeight()/2 - (getHeight() * (float)0.02) + (getHeight() * (float)0.2));//下30
path.lineTo(getWidth() * (float)0.7 - getWidth() * (float)0.01,(float)getHeight()/2 - (getHeight() * (float)0.01));
path.lineTo((float) getWidth(),(float)getHeight()/2 - (getHeight() * (float)0.01));
path.lineTo((float)getWidth(),(float)getHeight()/2 + (getHeight() * (float)0.01));
path.lineTo(getWidth() * (float)0.7 + getWidth() * (float)0.01,(float)getHeight()/2 + (getHeight() * (float)0.01));
path.lineTo(getWidth() * (float)0.6,(float)getHeight()/2 + (getHeight() * (float)0.03) + (getHeight() * (float)0.2));
// path.lineTo(getWidth() * (float)0.6 - getWidth() * (float)0.01,(float)getHeight()/2 + (getHeight() * (float)0.01) + (getHeight() * (float)0.2));
path.lineTo(getWidth() *(float)0.4,(float)getHeight()/2 + (getHeight() * (float)0.02) - (getHeight() * (float)0.2));
path.lineTo(getWidth() * (float)0.3 + getWidth() * (float)0.01,(float)getHeight()/2 + (getHeight() * (float)0.01));
path.lineTo(0,(float) getHeight()/2 + (getHeight() * (float)0.01));
path.close();
canvas.clipPath(path, Region.Op.DIFFERENCE);
canvas.drawRect(rectF,whitePaint);
}
public void run(){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
current += (float)0.5;
if (current > getWidth() * (1.3)){
current = 0 - getWidth() * (float)0.3;
}
postInvalidate();
}
},200,1);
}
}
End
其实,多练习练习,api熟了之后,思路就宽了,有思路了控件就不是什么难事儿了。