开源组件剖析:ShimmerAndroid

转载 2017年10月12日 16:08:14

1、认识ShimmerAndroid

ShimmerAndroid是Facebook贡献的一个开源组件,它可以使你的View产生类似于高光的效果,非常酷炫。 Github:ShimmerAndroid

关于它的使用,也是非常的简单,只需要将你的布局作为ShimmerFrameLayout的子view,并且对ShimmerFrameLayout进行一系列简单的配置,就可以实现相应的效果。Github上面有demo,相信大家看了以后不出5分钟就可以将其投入到生产。

  <ShimmerFrameLayout
      android:id="@+id/shimmer_view_container"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      shimmer:duration="10000">

    <LinearLayout
        android:id="@+id/content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        >

      <ImageView
          android:layout_width="64dp"
          android:layout_height="64dp"
          android:src="@drawable/fb_logo"/>

      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_marginTop="20dp"
          android:gravity="center"
          android:text="Facebook’s mission is to give people the power to share and make the world more open and connected."
          style="@style/thin.white.large"/>
    </LinearLayout>
  </ShimmerFrameLayout>

这就是demo的布局了,在视图当中调用下面的方法:

mShimmerViewContainer = (ShimmerFrameLayout) findViewById(R.id.shimmer_view_container);
mShimmerViewContainer.startShimmerAnimation();

效果图如下:

其实大家不难看出,ShimmerFrameLayout当中的子view一方面按照原貌进行呈现,这个我们姑且称之为『底图』,另一方面又有高亮的效果闪过,这个我们称之为『遮罩』,这样Shimmer的效果就完全的展现在了大家的面前。

它有几个属性,其中比较重要的比如

  • BaseAlpha:子view在绘制中,底图的透明度
  • MaskShape:遮罩形状,后面我们就会知道,遮罩其实是一张绘制了Gradient的Bitmap,而这个形状其实就是Gradient的形状,分别是Linear和Radical两种
  • Angle:遮罩动画的移动方向和角度
  • Dropoff:遮罩边缘的淡出效果的跨度
  • Intensity:遮罩中间高亮区域的跨度

为了让大家直观的理解Dropoff和Intensity,我将遮罩不加混色效果直接绘制出来呈现给大家,如下图:

这样,了解了这几个重要的属性,大家就可以根据自己的喜好为自己的应用添加Shimmer效果了。说到这里,如果大家只是关心它的用法,后面的内容就可以略过了,然而我相信你们肯定不是这样的浅尝辄止:)

下面我们将从Shimmer效果的『闪动』和『遮罩』的生成来剖析一下ShimmerAndroid的原理。

2、闪动的效果如何产生?

大家都是有经验的开发者,既然我们已经知道Shimmer的效果不过是在原图上面覆盖了一层遮罩,那么不难想象,高光略过的闪动效果,无非就是一个位移动画,请看源码:

   private Animator getShimmerAnimation() {
        if (mAnimator != null) {
            return mAnimator;
        }
        int width = getWidth();
        int height = getHeight();
        switch (mMask.shape) {
            default:
            case LINEAR:
                switch (mMask.angle) {
                    default:
                    case CW_0:
                        mMaskTranslation.set(-width, 0, width, 0);
                        break;
                    case CW_90:
                        mMaskTranslation.set(0, -height, 0, height);
                        break;
                    case CW_180:
                        mMaskTranslation.set(width, 0, -width, 0);
                        break;
                    case CW_270:
                        mMaskTranslation.set(0, height, 0, -height);
                        break;
                }
        }
        mRepeatDelay = 500;
        mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f + (float) mRepeatDelay / mDuration);
        mAnimator.setDuration(mDuration + mRepeatDelay);
        mAnimator.setRepeatCount(mRepeatCount);
        mAnimator.setRepeatMode(mRepeatMode);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float animateValue = (float) animation.getAnimatedValue();
                float value = Math.max(0.0f, Math.min(1.0f, animateValue));
                setMaskOffsetX((int) (mMaskTranslation.fromX * (1 - value) + mMaskTranslation.toX * value));
                setMaskOffsetY((int) (mMaskTranslation.fromY * (1 - value) + mMaskTranslation.toY * value));
            }
        });
        return mAnimator;
   }   

这段代码也简单,其实就是生成了一个Animator的实例,我们看到这里面在Animator Update的时候,会去根据Animator的进度不断改变MaskOffsetX和MaskOffsetY。没错,就是一个非常简单的位移动画,至于说其他代码(比如switch里面的代码),无非是为了计算动画每一帧的这两个参数的结果所做的准备工作而已。

既然知道了,动画本质就是对MaskOffsetX和MaskOffsetY的操作,下一步就要知道怎么应用这两个数值了。

   private void drawMasked(Canvas renderCanvas) {
        Bitmap maskBitmap = getMaskBitmap();
        if (maskBitmap == null) {
            return;
        }
        //注意,源码当中下面的drawColor和clipRect的顺序是反过来的
        renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
        renderCanvas.clipRect(
                mMaskOffsetX,
                mMaskOffsetY,
                mMaskOffsetX + maskBitmap.getWidth(),
                mMaskOffsetY + maskBitmap.getHeight());

        super.dispatchDraw(renderCanvas);

        renderCanvas.drawBitmap(maskBitmap, mMaskOffsetX, mMaskOffsetY, mMaskPaint);
    }

在这里我们就可以看到mMaskOffsetX和mMaskOffsetY的用处了,他们决定了遮罩绘制的位置,这样也就好解释我们看到的高光掠过底图的闪动效果了。

代码当中,我把源文件的drawColor和clipRect的顺序倒置了。按照原来的顺序,如果底图是静止的,或者BaseAlpha几乎接近于1.0时,问题并不是很明显,而一但让底图动起来,或者把BaseAlpha设置为0,那么高光掠过时,会留下残影,有兴趣的读者不妨一试。原因也很容易看出,drawColor是为了清屏,但清屏的区域是上一次绘制的区域,上一次绘制的区域跟这一次自然是不同的。因此按照我给出的代码顺序更为妥当。

3、遮罩如何生成?

有经验的开发者肯定能想到,遮罩其实就是一些颜色的变化图,通过遮罩与底图的色值混合运算,我们就可以看到比较酷炫的效果。

其实在为了讲清楚Dropoff和Intensity时,我们就讲遮罩直接绘制了出来,大家应该也不陌生了,为了生成这张遮罩,我们就要了解下面的一个概念:shader

此shader并非OpenGL的shader,我们都知道,Android底层的图形引擎主要是Skia,想必大家也能猜到所谓shader不过是对Skia引擎的SkShader的一个封装而已,而这个shader其实是决定我们的paint在绘制到canvas的那一刻,所绘制的色值的对象。

在SkShader当中,有个叫shadeSpan的函数,还有一个shadeProc函数指针,这二者一起决定了paint的绘制内容。

关于Shader的一些细节,大家可以参考Skia引擎的源码,我后续也会整理一篇文章来介绍这一部分知识。

Shader和PorterDuff的关系和区别:前者决定了绘制的SRC是什么,而后者决定了SRC和DST如何混色,进而产生出最终的结果。

Shimmer效果有两种,分别是线性、圆形,对应的Shader分别是LinearGradient和RadicalGradient。

gradient = new LinearGradient(
                x1, y1,
                x2, y2,
                mMask.getGradientColors(),
                mMask.getGradientPositions(),
                Shader.TileMode.CLAMP);
int x = width / 2;
int y = height / 2;
gradient = new RadialGradient(
       x,
       y,
       (float) (Math.max(width, height) / Math.sqrt(2)),
       mMask.getGradientColors(),
       mMask.getGradientPositions(),
       Shader.TileMode.CLAMP);

一旦生成遮罩之后,就在绘制过程中用PorterDuff进行混色,产生高亮的效果:

   private void drawMasked(Canvas renderCanvas) {
        Bitmap maskBitmap = getMaskBitmap();
        if (maskBitmap == null) {
            return;
        }
        renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
        renderCanvas.clipRect(
                mMaskOffsetX,
                mMaskOffsetY,
                mMaskOffsetX + maskBitmap.getWidth(),
                mMaskOffsetY + maskBitmap.getHeight());

        super.dispatchDraw(renderCanvas);
        renderCanvas.drawBitmap(maskBitmap, mMaskOffsetX, mMaskOffsetY, mMaskPaint);
    }

这段代码我们已经看到过了,这次我们主要关注mMaskPaint。在绘制遮罩时,遮罩就是PorterDuff应用中涉及的的SRC,而底图就是DST。二者混色的模式是什么呢?

mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

而DST_IN又是什么意思呢?

        /** [Sa * Da, Sa * Dc] */
        DST_IN      (6),

这种模式下,混色的结果中,alpha为DST和SRC的alpha的乘积(如 Ra = Sa * Da = 0.5 * 0.6 = 0.3),color按RGB通道计算的结果为DST的color乘以SRC的alpha。换句话说,SRC只是输入了一个alpha而已,绘制的具体色值,取决于DST。

4、结语

第一眼看到ShimmerAndroid的时候,觉得效果确实不错,细细读下来,代码只有千行。真是大道至简。

本文作者:bennyhuo

转载请注明出处:开源组件剖析:ShimmerAndroid

实用的开源组件整理

1.https://github.com/zcweng/ToggleButton 状态切换的 Button,类似 iOS,用 View 实现 2.https://github.com/lip...
  • daitu_liang
  • daitu_liang
  • 2016年05月04日 16:18
  • 1104

分享一些第三方组件和开源的库

分享一些第三方组件和开源的库, 感谢开源, 减少了我们的开发成本, 节约了我们大量的时间, 让我们有更多的时间和精力专注做我们自己的产品. 项目名称 项目信息 A...
  • wolaikanyanhau
  • wolaikanyanhau
  • 2016年01月09日 18:55
  • 259

Excel开源组件介绍

Excel开源组件介绍 目前比较流行的读写Excel开源组件主要包括两种,分别为NPOI和EPPLUS,相较于后者前者流行时间比较早,而后者在程序设计上更人性化。 1组件简介 1.1 NPOI介...
  • xiaohou66
  • xiaohou66
  • 2015年07月30日 17:19
  • 1553

采用HTML5的开源组件绘制复杂图形

项目原因由于需要做一些复杂的图表,在开源社区中也到到了一些
  • lijun952048910
  • lijun952048910
  • 2014年05月05日 16:41
  • 1411

利用开源组件POI3.0.2动态导出EXCEL文档

利用开源组件POI3.0.2动态导出EXCEL文档
  • ck3345143
  • ck3345143
  • 2017年02月28日 14:12
  • 243

.NET平台常用的开发组件

工欲善其事,必先利其器。学习.NET也10年有余,其优雅的编程风格,高效率的开发速度,极度简单的可扩展性,足够强大开发类库,较小的学习曲线,让我对这个平台产生了浓厚的兴趣,在工作和学习中也积累了一些开...
  • diligentcat
  • diligentcat
  • 2017年02月24日 09:20
  • 1844

Android 开源组件和第三方库汇总

出自(https://github.com/Tim9Liu9/TimLiu-Android) TimLiu-Android 自己总结的Android开源项目及库。 github排名 https...
  • renjianhhong
  • renjianhhong
  • 2016年07月01日 09:51
  • 6421

一款开源免费的.NET文档操作组件——DocX

在目前的软件项目中,都会较多的使用到对文档的操作,用于记录和统计相关业务信息。由于系统自身提供了对文档的相关操作,所以在一定程度上极大的简化了软件使用者的工作量。     在.NET项目中如果用户提...
  • eouaq448466
  • eouaq448466
  • 2017年01月06日 02:43
  • 477

Android 开源组件和第三方库汇总

自己总结的Android开源项目及库。 1、 github排名 https://github.com/trending,github搜索:https://github.com/search 2、h...
  • u014120638
  • u014120638
  • 2016年12月26日 16:37
  • 5822

JS拖拽组件开发

http://blog.csdn.net/liuyan19891230/article/details/50195867?ref=myread  分类: JS高级程序设计(12) ...
  • zdy0_2004
  • zdy0_2004
  • 2016年01月19日 16:09
  • 355
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:开源组件剖析:ShimmerAndroid
举报原因:
原因补充:

(最多只允许输入30个字)