- Ui绘制概述
SwipeRefreshLayout的绘制基本上分为三步,第一步是自定义一个CircleImageView,这个CircleImageView加入了5.0的新特效,既有个阴影的效果,不知道平常大家注意到没,第二步是自定义了一个MaterialProgressDrawable作为ImageView的 Drawable,既通过imageView.setImageDrawable(Drawable)赋值给CircleImageView,第三步是将CircleImageView添加到SwipeRefreshLayout中,同时在SwipeRefreshLayout中处理滑动事件,这里只先将UI的绘制,动画及滑动事件的处理以后再讲。 - CircleImageView的绘制
在SwipeRefreshLayout的构造中我们可以找到一个方法createProgressView();
,Ctrl+鼠标左键跟随源码看下
private void createProgressView() {
mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT);
mProgress = new MaterialProgressDrawable(getContext(), this);
mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
mCircleView.setImageDrawable(mProgress);
mCircleView.setVisibility(View.GONE);
addView(mCircleView);
}
很明显的在这里创建了CirleImageView和MaterialProgressDrawable,然后将MaterialProgressDrawable通过setImageDrawable给了CircleImageView,最后将CircleImageView添加到SwipeRefreshLayout中,我们先看看CircleImageView是如何自定义的。当跟随源码进入CircleImageView的时候,我们发现这里只有一个构造,和我们平时看到的不太一样,可能你以前见到的有好几个构造,为什么这里只有一个呢,只要看看构造里面有何不同,也许你就猜到为什么了,因为我们这里的自定义全是用Java代码来写出来的布局,并没有用那些xml属性,我们我们无需多写具有AttributeSet等参数的构造
CircleImageView(Context context, int color) {
........此处省略很多代码,只看构造
}
接着逐句解析源码
final float density = getContext().getResources().getDisplayMetrics().density;
在构造里面第一句就是获取density,这个是什么东西呢?Google的原话是:The logical density of the display (设备的逻辑密度).这是个什么意思呢?举个例子你就明白了,比如dip为160的设备,它的density是1,那么当dip为120的设备,它的density就是0.75,当然,这个东西不用你来算,你获取到的density就是当前设备的density,直接用就行了,这个值就是为了适配各种屏幕密度的。怎么用?等下看源码是怎么用的,你就知道了。
final int shadowYOffset = (int) (density * Y_OFFSET);
final int shadowXOffset = (int) (density * X_OFFSET);
mShadowRadius = (int) (density * SHADOW_RADIUS);
这里的三行代码分别是设置阴影在水平和垂直方向的偏移量以及阴影的半径的,Y_OFFSET、X_OFFSET和SHADOW_RADIUS是常量,分别为1.75f、0f、3.5f。
接下来看关于5.0新特效的(阴影)的处理,关于是不是5.0
private boolean elevationSupported() {
return android.os.Build.VERSION.SDK_INT >= 21;
}
傻子都能看懂,还需要解释吗
看下如果是5.0的处理
if (elevationSupported()) {
circle = new ShapeDrawable(new OvalShape());
ViewCompat.setElevation(this, SHADOW_ELEVATION * density);
这里直接new ShapeDrawable(ovalShape)
可以这么理解,这里我们需要一个OvalShape(椭圆类型)的ShapeDrawable作为我们CircleImageView的Background,所有在5.0以上我们就直接new了,但是要注意加上ViewCompat.setElevation(this, SHADOW_ELEVATION * density);
这句是设置投影高度的,不设置就没阴影效果了。
接下来看5.0以下的版本的兼容处理,就是else里面的
else {
OvalShape oval = new OvalShadow(mShadowRadius);
circle = new ShapeDrawable(oval);
ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
KEY_SHADOW_COLOR);
final int padding = mShadowRadius;
// set padding so the inner image sits correctly within the shadow.
setPadding(padding, padding, padding, padding);
}
先看第一行OvalShape oval = new OvalShadow(mShadowRadius)
这是一个重写了OvalShape的类,它干了什么事情呢,我们看下源码
private class OvalShadow extends OvalShape {
private RadialGradient mRadialGradient;
private Paint mShadowPaint;
OvalShadow(int shadowRadius) {
super();
mShadowPaint = new Paint();
mShadowRadius = shadowRadius;
updateRadialGradient((int) rect().width());
}
@Override
protected void onResize(float width, float height) {
super.onResize(width, height);
updateRadialGradient((int) width);
}
@Override
public void draw(Canvas canvas, Paint paint) {
final int viewWidth = CircleImageView.this.getWidth();
final int viewHeight = CircleImageView.this.getHeight();
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint);
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint);
}
private void updateRadialGradient(int diameter) {
mRadialGradient = new RadialGradient(diameter / 2, diameter / 2,
mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT },
null, Shader.TileMode.CLAMP);
mShadowPaint.setShader(mRadialGradient);
}
}
它重写了两个方法,onResize 和 draw
,我们先看下draw干了什么
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint);
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint);
这里画了两个圆,圆心是一样的,只是半径不一样,就是下面这样的:
然后在看一下构造和onResize
里都调用的方法updateRadialGradient(int)
,这个方法是干嘛的呢,它是为着色器设置一个梯度的渐变效果,设置从中间到边缘逐渐变成透明。通过这两个圆达到实现阴影效果的曲线救国方针,巴特,还没完呢,接着看else里面的代码
circle = new ShapeDrawable(oval);
//设置图层
ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
//设置阴影层 circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
KEY_SHADOW_COLOR);
final int padding = mShadowRadius;
// set padding so the inner image sits correctly within the shadow.
setPadding(padding, padding, padding, padding);
首先将我们画的两个圆放到ShapeDrawable中,然后设置图层,接着设置阴影层
最后别忘了把我们的ShapeDrawable设置为CircleImageView的Background
circle.getPaint().setColor(color);
ViewCompat.setBackground(this, circle);
最最后,看看我们自定义View必看的一个方法onMeasure
if (!elevationSupported()) {
setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight()
+ mShadowRadius * 2);
}
这是5.0以下版本需要我们加上阴影部分的宽度,既mShadowRadius * 2;5.0以上版本就不需要我们自己把阴影部分加上去了,系统已经默认测量了
至此:CircleImageView的绘制的源码分析完了。
如有错误,请留言更正,以免误导其他开发者!!!