自定义控件系列开始连载了,今天我们要做的是知乎的进度条的效果,先上图
刚开始不太会做动态图,自己截了几张图合在一起,看起来会比较卡,自己运行源码就比较流畅了。
回归正题,看到这样一个效果会想到如何实现呢,帧动画也是可以的,但是不平滑,放很多帧又会卡,所以我们先观察这个动画,底部是一个蓝色的线段,中间有4个白色滑块向右移动,所以我们可以用两个画笔来进行绘制,一个绘制底部的线条,一个绘制白色的滑块。
View和SurfaceView不一样,view是运行在UI线程也就是主线程上的,而SurfaceView通过一个callback回调,在surfaceCreated中可以启动一个工作线程,如果我们在view中直接绘制的话就会阻塞UI线程,程序就会ANR了,所以我们可以声明一个定时器,然后在其中去进行更新滑块的位置,然后postInvalidate,这样就能onDraw更新了,而且不会阻塞主线程。
public class LoadingView extends View {
private Paint mBgPaint;
private Paint mBlockPaint1;
private Timer timer;
private Rect rect1;
private Rect rect2;
private Rect rect3;
private Rect rect4;
int left1,right1;
int left2,right2;
int left3,right3;
int left4,right4;
public LoadingView(Context context) {
super(context ,null);
}
public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i("---LoadingView", "LoadingView");
// int w = getWidth();
left1 =0;
right1 =10;
left2 = 1080/4;
right2 =1080/4 +10;
left3 =1080/2;
right3 = 1080/2 +10;
left4 = 1080*3/4;
right4 = 1080*3/4+10;
mBgPaint = new Paint();
mBgPaint.setAntiAlias(true);
mBgPaint.setColor(Color.BLUE);
mBlockPaint1= new Paint();
mBlockPaint1.setAntiAlias(true);
mBlockPaint1.setColor(Color.WHITE);
rect1 = new Rect(left1, 0, right1, 10);
rect2 = new Rect(left2, 0, right2, 10);
rect3 = new Rect(left3, 0,right3, 10);
rect4 = new Rect(left4, 0, right4, 10);
timer = new Timer();
newThread();
}
public void newThread(){
timer.schedule(new TimerTask() {
@Override
public void run() {
Log.i("---width", ""+getWidth());
if (right4 < getWidth()) {
left1 =left1+20;
left2 =left2+20;
left3 =left3+20;
left4 = left4+20;
right1 =right1+20;
right2=right2+20;
right3 = right3+20;
right4 =right4+20;
}else {
left1= 0;
right1 = 10;
left2= getWidth()/4;
right2 = getWidth()/4+10;
left3= getWidth()/2;
right3 = getWidth()/2+10;
left4 =getWidth()*3/4;
right4 =getWidth()*3/4+10;
}
rect1 = new Rect(left1, 0, right1, 10);
rect2 = new Rect(left2, 0, right2, 10);
rect3 = new Rect(left3, 0, right3, 10);
rect4 = new Rect(left4, 0, right4, 10);
postInvalidate();
}
}, 200, 30); //设置延迟是为了使onDraw先调用
}
@Override
protected void onDraw(Canvas canvas) {
Log.i("---ondraw", "ondraw");
super.onDraw(canvas);
canvas.drawRect(new Rect(0, 0, getWidth(), 10), mBgPaint);
canvas.drawRect(rect1, mBlockPaint1);
canvas.drawRect(rect2, mBlockPaint1);
canvas.drawRect(rect3, mBlockPaint1);
canvas.drawRect(rect4, mBlockPaint1);
}
}
写这个之后才发现一个问题,在构造函数中我们调用getWidth()是不会返回手机的宽度的,而是会返回0,这是因为onDraw方法还没有调用,查阅了很多资料,基本上都是在onCreate中取直接获取width,除此之外没有比较好的办法,知道的话可以评论一句。这里我直接写死了数据。
其实写到这里已经可以算完成任务了,因为效果已经实现了,但是自定义控件,不能自定义当然感觉缺点什么,比如知乎也有两种皮肤,滑块的颜色并不一样,所以我们再继续完善一下。
先在values下面建立一个attrs.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LoadingView">
<attr name="blockcolor" format="color"/>
<attr name="bgcolor" format="color"/>
</declare-styleable>
</resources>
然后我们就能在代码里调用了
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
int bgColor = array.getColor(R.styleable.LoadingView_bgcolor, 0xFF0000FF);
int blockColor = array.getColor(R.styleable.LoadingView_blockcolor, 0xFFFFFFFF);
mBgPaint = new Paint();
mBgPaint.setAntiAlias(true);
mBgPaint.setColor(bgColor);
mBlockPaint1= new Paint();
mBlockPaint1.setAntiAlias(true);
mBlockPaint1.setColor(blockColor);
最后在布局文件里面使用
xmlns:sheepm="http://schemas.android.com/apk/res/com.example.zhihuloading"
<com.example.zhihuloading.LoadingView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
sheepm:blockcolor ="#FFFF00"
sheepm:bgcolor ="#FF0000"
/>