下面来实现一个loading效果。具体效果如下:
首先对这个效果进行拆分,它由以下部分组成:
- 1 一个”闪电”样式的图案;
- 2 “闪电”图案背后是一个圆角矩形;
- 3 “闪电”图案上面有一层颜色不断”飘过”
拆分完效果后,思考下如何实现。下面是我的思考过程。
- 1 android sdk并没有提供这样的控件,很显然是需要自定义控件;
- 2 很显然是一个View而不是ViewGroup,所以可以继承View;
- 3 重点是onDraw的逻辑;
- 4 怎样绘制”闪电”的图案?可以通过Path绘制;
- 5 怎样绘制”闪电”背后的圆角矩形?canvas.drawRoundRect;
- 6 怎样实现”闪电”的动效?仔细观察,发现上方那层颜色的运动规律是从0~闪电高度不断扩大,到达闪电高度的时候,高度不断减小直到0。所以可以通过控制高度的方式实现。只要有两个变量scanTop/scanBottom记录绘制的上下界限即可,然后控制scanTop/scanBottom进行变化即可,怎样控制变化很显然可以通过post/postDelayed实现。另外一个难点是如何绘制部分”闪电”?思索一番,可以通过canvas.clipRect的方式控制绘制区域,这样间接实现了我们需要的效果;
- 7 核心逻辑实现之后,需要考虑到应该让这个自定义控件支持wrap_content.这必然需要重写onMeasure,并考虑到父容器的MeasureSpec(view的默认实现下wrap_content和match_parent效果一样);
- 8 需要让这个控件支持padding。所以得在measure和draw的过程中充分考虑到padding这个因素;
- 9 当view被detach的时候,需要remove掉动画;
- 10 应该提供几个默认大小,比如small/midium/large,这可以通过自定义属性实现。
大致思考完之后,可以写代码了。
首先是measure过程:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//需要计算自己实际需要的宽高
//需要把padding考虑进来
//需要考虑父容器的测量规则
int width,height;
width = (int)mViewMinWidth+getPaddingLeft()+getPaddingRight();
height = (int)mViewMinHeight+getPaddingTop()+getPaddingBottom();
setMeasuredDimension(getMeasuredSize(widthMeasureSpec, width), getMeasuredSize(heightMeasureSpec, height));
}
通过getMeasuredSize计算考虑父容器限制后的实际大小:
private int getMeasuredSize(int measureSpec,int desiredSize){
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode){
case MeasureSpec.EXACTLY:
result = size;
break;
default:
result = desiredSize;
if(mode == MeasureSpec.AT_MOST)
result = Math.min(result,size);
break;
}
return result;
}
然后是draw的过程:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(mViewBackground);
//如果xml中设置layout_width/layout_height大于默认宽高,那么居中(不允许小于默认宽高)
if(getWidth()-getPaddingLeft()-getPaddingRight() > (int)mViewMinWidth || getHeight()-getPaddingTop()-getPaddingBottom() > (int)mViewMinHeight){
canvas.translate((getWidth()-mViewMinWidth)/2.0f,(getHeight()-mViewMinHeight)/2.0f);
}
//画圆角矩形
canvas.drawRoundRect(mBounds, dp2px(5), dp2px(5), mPaint);
//平移到圆角矩形中心点,画闪电
canvas.translate((mViewMinWidth - mDefaultWidth) / 2.0f, (mViewMinHeight - mDefaultHeight) / 2.0f);
mPaint.setColor(mBackgroundColor);
canvas.drawPath(mThunderPath, mPaint);
mPaint.setColor(mCoverColor);
//通过clicpRect的方式控制可绘制区域(在外界看来好像有闪动的动画效果)
canvas.clipRect(getPaddingLeft(), mScanTop + getPaddingTop(), mDefaultWidth + getPaddingLeft(), mScanBottom + getPaddingTop());
canvas.drawPath(mThunderPath, mPaint);
}
mScanTop/mScanBottom变量可以通过post()进行改变:
class AnimRunnable implements Runnable{
@Override
public void run() {
if (!flag) {
mScanBottom += mGap;
if (mScanBottom >= mDefaultHeight) {
mScanBottom = (int) mDefaultHeight;
flag = true;
}
postInvalidate();
post(this);
} else {
mScanTop += mGap;
if (mScanTop >= mDefaultHeight) {
mScanTop = mScanBottom = 0;
flag = false;
postInvalidate();
postDelayed(this, 700);
} else {
postInvalidate();
post(this);
}
}
}
}
private void startAnim() {
mRunnable = new AnimRunnable();
post(mRunnable);
}
核心代码就这么多。
完整代码在这里:https://github.com/Rowandjj/ThunderLoadingView
版权声明:本文为博主原创文章,未经博主允许不得转载。
时间: 2024-10-26 16:00:26