UI组件——SwipeRefrshLayout最详细的源码解析——UI绘制

本文详细解析SwipeRefreshLayout的UI绘制,主要分为三个步骤:1) 自定义CircleImageView实现阴影效果;2) 使用MaterialProgressDrawable作为ImageView的Drawable;3) 将CircleImageView添加到SwipeRefreshLayout并处理滑动事件。文章深入分析了CircleImageView的构造和绘制过程,包括density的使用、阴影的处理以及5.0版本的新特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 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的绘制的源码分析完了。


如有错误,请留言更正,以免误导其他开发者!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值