内容简概
一、画波浪线
二、CircleView(画文字和圆环)
三、WaveLoadingView(组合加速球)
四、activity_main
五、MainActivity
六、运行效果
具体内容
一、画波浪线
(一)wave_view_attr
系统里没有专门的波浪线样式,我们可以自定义属性。在res/values中创建一个资源文件attrs.xml(Values Resource file),并定义为declare-styleable。
知识储备 |
---|
declare-styleable的使用 |
深入理解Android 自定义attr Style styleable以及其应用 |
android:pivotX 和 android:pivotY 属性的含义 |
onMeasure方法理解 |
<resources>
<declare-styleable name="WaveView">
<attr name="lineColor" format="integer|color|reference"/>
<attr name="lineSize" format="integer"/>
<attr name="waveLength" format="integer"/>
<attr name="waveCrest" format="integer"/>
</declare-styleable>
</resources>
(二)WaveView
新建一个继承于View的类,用于画波浪。首先头脑要清楚自己最后想要实现的样式
,才能在onDraw里画出来。getPivotY是获取控件中心的Y坐标
,上面知识储备中的一篇文章有助于理解。
public class WaveView extends View {
private ValueAnimator va;
private Paint mPaint;
private Path mPath;
float density = getResources().getDisplayMetrics().density;
private int waveLength = (int) (100*density); // 波长
private int waveCrest = (int) (50*density); // 波峰
private int speed; // 变化的快慢
private int lineColor = Color.BLACK; // 线条颜色
private int lineSize = 10; // 线条粗细
public WaveView(Context context) {
super(context);
init();
}
public WaveView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 从xml获取自定义属性的值
initAttr(context,attrs);
// 初始化画笔
init();
}
// 设置画笔样式
private void init(){
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(lineColor);
mPaint.setStrokeWidth(lineSize);
mPaint.setStyle(Paint.Style.STROKE);
}
// 通过自定义属性设置波浪的样式
private void initAttr(Context context, @Nullable AttributeSet attrs){
// 读取所有自定义属性的值
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.WaveView);
// 读取每一个属性的值
waveLength = array.getInteger(R.styleable.WaveView_waveLength, (int) (100*density));
waveCrest = array.getInteger(R.styleable.WaveView_waveCrest, (int) (50*density));
lineColor = array.getColor(R.styleable.WaveView_lineColor, Color.BLACK);
lineSize = array.getInteger(R.styleable.WaveView_lineSize, 10);
}
// 设置波浪的具体属性(波长、波峰、波谷)
private void initPath() {
// 创建曲线
mPath = new Path();
// 计算屏幕装得下多少个周期(屏幕宽度除以一个波长)
int count = getWidth() / waveLength;
// 设置起始点 距离x左边的一个波长
mPath.moveTo(-waveLength + speed, getHeight() / 2);
// 获取垂直中心的坐标
int centerY = (int) getPivotY();
// 确定曲线路径
for (int start = -waveLength + speed; start < getWidth(); start += waveLength) {
// 画前半周期
mPath.cubicTo(start, centerY, start + waveLength / 4, centerY - waveCrest, start + waveLength / 2, centerY);
// 画后半周期
mPath.cubicTo(start + waveLength / 2, centerY, start + waveLength * 3 / 4, centerY + waveCrest, start + waveLength, centerY);
}
}
// 画波浪
@Override
protected void onDraw(Canvas canvas) {
initPath();
canvas.drawPath(mPath, mPaint);
}
// 获取父视图对应的模式和尺寸
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// 绘制过程中的回调
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 开始动画
startWave();
}
// 暂停动画
public void stopWave(){
if (va != null){
va.cancel();
}
}
// 通过属性动画实现波浪动画
private void startWave(){
va = ValueAnimator.ofInt(0,waveLength);
va.setDuration(500);
va.setRepeatCount(ValueAnimator.INFINITE);
va.setRepeatMode(ValueAnimator.RESTART);
va.setInterpolator(new LinearInterpolator());
// 启动动画之后, 会不断回调此方法来获取最新的值
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// // 获取最新的速度值
speed = (int) valueAnimator.getAnimatedValue();
// 刷新
invalidate();
}
});
va.start();
}
public void setWaveLength(int waveLength) {
this.waveLength = waveLength;
}
public void setWaveCrest(int waveCrest) {
this.waveCrest = waveCrest;
}
public void setLineColor(int lineColor) {
this.lineColor = lineColor;
mPaint.setColor(lineColor);
}
public void setLineSize(int lineSize) {
this.lineSize = lineSize;
mPaint.setStrokeWidth(lineSize);
}
}
二、CircleView
同样新建一个继承于View的类来画圆环和文字。设置属性为私有化更安全,要使用时通过setter\getter方法。
public class CircleView extends View {
private Paint circlePaint;
private Paint textPaint;
private int centerYSpace; // 和中心线的距离
private int lineSize = 20;
private int lineColor = Color.BLACK;
private int textColor = Color.BLACK;
private int textSize = 50;
private float progress; // 进度
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
// 初始化画笔
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(Color.BLACK);
circlePaint.setStrokeWidth(lineSize);
circlePaint.setStyle(Paint.Style.STROKE);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(50);
}
@Override
protected void onDraw(Canvas canvas) {
// 确定半径,系统默认是沿半径外一圈画圆,我们让其沿内画,故减去画笔粗细
int radius = Math.min(getWidth(),getHeight())/2-lineSize;
// 画圆
canvas.drawCircle(getPivotX(),getPivotY(),radius,circlePaint);
// 画文本
String text = (int)(progress*100) + "%";
// 计算文本宽度
int width = (int) textPaint.measureText(text);
// 获取文字矩阵fontMetrics
Paint.FontMetricsInt fm = textPaint.getFontMetricsInt();
canvas.drawText(text,getPivotX()-width/2,getPivotY()+(-fm.ascent)/2+centerYSpace,textPaint);
}
public void setLineSize(int lineSize) {
this.lineSize = lineSize;
circlePaint.setStrokeWidth(lineSize);
}
public void setLineColor(int lineColor) {
this.lineColor = lineColor;
circlePaint.setColor(lineColor);
}
public void setTextColor(int textColor) {
this.textColor = textColor;
circlePaint.setColor(textColor);
}
public void setTextSize(int textSize) {
this.textSize = textSize;
circlePaint.setTextSize(textSize);
}
public float getProgress() {
return progress;
}
public void setProgress(float progress) {
this.progress = progress;
// 刷新
invalidate();
}
public void setCenterYSpace(int centerYSpace) {
this.centerYSpace = centerYSpace;
}
}
三、WaveLoadingView
将波浪、圆环和文字组合成加速球。新建一个继承于ViewGroup的类来设置,实例化CircleView和WaveView的对象。
public class WaveLoadingView extends ViewGroup {
private float progress; // 进度
private CircleView cv; // 进度圆圈
private WaveView wv; // 波浪视图
public WaveLoadingView(Context context) {
super(context);
}
public WaveLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
// 创建circleView
cv = new CircleView(getContext());
cv.setLineColor(Color.RED);
cv.setLineSize(20);
cv.setTextColor(Color.RED);
cv.setCenterYSpace(-70);
// 对子视图进行布局
cv.layout(0,0,getWidth(),getHeight());
// 将子视图添加到容器中
addView(cv);
// 创建WaveView
wv = new WaveView(getContext());
wv.setLineSize(8);
wv.setWaveCrest(30);
wv.setWaveLength(100);
// 布局(波浪位于圆环中间)
wv.layout(getWidth()/4,getHeight()/2-30,getWidth()*3/4,getHeight()/2+30);
addView(wv);
}
// 绘制过程中的回调
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
public float getProgress() {
return progress;
}
public void setProgress(float progress) {
this.progress = progress;
// 改变CircleView的进度值
if (progress < 1.01){
cv.setProgress(progress);
}
if ((int)progress == 1){
wv.stopWave();
}
}
}
四、activity_main
设置加速球(WaveLoadingView)的布局属性。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.a14waveloading.WaveLoadingView
android:id="@+id/load"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_centerInParent="true">
</com.example.a14waveloading.WaveLoadingView>
</RelativeLayout>
五、MainActivity
在主函数中调用。
public class MainActivity extends AppCompatActivity {
WaveLoadingView loadingView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadingView = findViewById(R.id.load);
}
// 设置点击事件,点击一次process增加
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN){
new Timer().schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
loadingView.setProgress((float) (loadingView.getProgress()+0.01));
}
});
}
},0,100);
}
return true;
}
}
六、运行效果
点击屏幕开始运行,再次点击则加速,进度值达到100%后文字和波浪不再变化。