美文网首页Android开发Android开发经验谈Android开发
探一探,非常实用的GIF图圆角控件(3行代码)

探一探,非常实用的GIF图圆角控件(3行代码)

作者: 文淑 | 来源:发表于2019-04-08 08:57 被阅读33次

    前言

    现代人的崩溃是一种默不吭声的崩溃,看起来很正常,会说笑,会打闹,会社交,表面平静,实际上心里的糟心事已经积累到一定程度了。不会摔门砸东西,不会流眼泪或歇斯心底,但可能某一秒突然就积累到极致,也不说话,也不真的崩溃,也不太想活着。也不敢去死。

    这里引用了「张帅B」的一段话,说的很贴切,我们这一代年轻人到底怎么了?

    正文

    先来看看效果图:


    在这里插入图片描述

    初步分析

    众所周知,Android 中主流的图片加载框架有 Picasso,Glide,Fresco。Picasso 加载 gif 图没有动画效果,Glide 与 Fresco 支持 gif 动效图。Fresco 自带的控件 SimpleDraweeView 支持圆角属性,Glide 需要手动给 ImageView 设置 shape,那么 Glide,Fresco 是否支持 gif 图圆角?

    先来看一个例子,Glide 加载 gif 圆角,先看看 xml 布局:

        <ImageView
            android:id="@+id/iv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="64dp"
            android:background="@drawable/corners_bg"
            android:src="@mipmap/gif_01"
            />
    

    圆角文件 corners_bg :

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
        <corners android:radius="8dp"></corners>
    </shape>
    

    Glide 加载动图:

        Glide.with(this).load(R.mipmap.gif_01)
                .asGif()
                .override(720, 512)
                .into(mImageView);
    

    效果图:


    在这里插入图片描述

    发现 gif 图并没有显示圆角,经过测试 Fresco 的圆角属性,在 gif 图上也会失效。那么我们怎么才能让 gif 图显示圆角呢?

    大家都知道 gif 是由多张静态图组合而成,如果处理单张图片效率将会极其低下,既然不能对源图片进行处理,那么就只能在显示控件上想办法了。

    经过初步分析有以下三种可行方案:

    1. 裁剪图片控件
    2. 在 gif 图片控件上覆盖一层圆角图片(4角圆角中间透明)
    3. Path 的填充样式 FillType

    方案一裁剪控件,改变图片显示区域,但裁剪的效率并不高,顾排除方案一;方案二,ui 切圆角图片,如果在换肤的情况下,ui 需要切多套圆角图片,可扩展性太差,同时覆盖的圆角图片加载会消耗性能,排除方案二;那就只有第三方案了,在性能与可扩展性方面优于前两种方案。

    Path填充样式FillType

        /**
         * Set the path's fill type. This defines how "inside" is computed.
         *
         * @param ft The new fill type for this path
         */
        public void setFillType(FillType ft) {
            // 调用 c 层的 jni 方法
        }
    

    设置路径的填充样式,定义了 "内部" 是如何计算的。 参数 ft 是个枚举值,有 4 种类型:

        /**
         * Enum for the ways a path may be filled.
         */
        public enum FillType {
            // these must match the values in SkPath.h
            /**
             * Specifies that "inside" is computed by a non-zero sum of signed
             * edge crossings.
             */
            WINDING         (0),
            /**
             * Specifies that "inside" is computed by an odd number of edge
             * crossings.
             */
            EVEN_ODD        (1),
            /**
             * Same as {@link #WINDING}, but draws outside of the path, rather than inside.
             */
            INVERSE_WINDING (2),
            /**
             * Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside.
             */
            INVERSE_EVEN_ODD(3);
        }
    
    WINDING (0)

    默认值是 WINDING (0) ,我们一起来探究下这几个值产生的效果,先来看看下面这段代码:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.save();
            mPath.addCircle(600, 400, 200, Path.Direction.CW);
            mPath.addCircle(900, 400, 200, Path.Direction.CW);
    
            canvas.drawPath(mPath, mPaint);
            canvas.restore();
        }
    

    绘制两相交圆,并两圆的方向都是顺时针绘制。效果图是这样的:


    在这里插入图片描述

    上图是两圆都是顺时针的方向,改变一圆的绘制方向为逆时针:

        mPath.addCircle(600, 400, 200, Path.Direction.CW);
        mPath.addCircle(900, 400, 200, Path.Direction.CCW);
    

    效果图如下:


    在这里插入图片描述

    WINDING 的原理:

    其实WINDING表示非零环绕原则,从任意一点发射一条线,默认值是 0,遇到顺时针交点则 +1,遇到逆时针交点则 -1,最终如果不等于 0,则认为这个点是图形内部的点,则需要绘制颜色;反之,如果这个值是 0,则认为这个点不在图形内部,则不需要绘制颜色。

    正好解释上图相交的部分没有绘制颜色,相交的部分首先是绘制顺时针方向的圆 +1 ,然后绘制逆时针方向的圆 -1 ,正好等于 0,不需要绘制颜色。

    EVEN_ODD (1)

    这个值和 WINDING 不同,WINDING 要求每个图形都是有方向的。EVEN_ODD 并不要求圆的方向,看看下面一段代码:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.save();
            mPath.setFillType(Path.FillType.EVEN_ODD);
            // 改成 Path.Direction.CCW 的效果一样
            mPath.addCircle(600, 400, 200, Path.Direction.CW);
            mPath.addCircle(900, 400, 200, Path.Direction.CW);
    
            canvas.drawPath(mPath, mPaint);
            canvas.restore();
        }
    

    效果图如下:


    在这里插入图片描述

    EVEN_ODD 原理:

    英文单词中 EVEN 是偶数,ODD 是奇数的意思。这个原则也被称为奇偶原则。从任意一点射出一条线,与图形的交线是奇数,则认为这个点在图形内部,需要绘制颜色;反之如果是偶数,则认为这个点在图形外部,不需要绘制颜色。

    INVERSE_WINDING (2)

    inverse 表示反转的意思,相同的一段代码,绘制出来的结果是相反的。如下代码:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.save();
            mPath.setFillType(Path.FillType.INVERSE_WINDING);
            mPath.addCircle(600, 400, 200, Path.Direction.CW);
            mPath.addCircle(900, 400, 200, Path.Direction.CW);
    
            canvas.drawPath(mPath, mPaint);
            canvas.restore();
        }
    

    圆的区域都不需要绘制颜色,非圆区域绘制颜色。效果图如下:


    在这里插入图片描述

    设置一圆的绘制方向为逆时针:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.save();
            mPath.setFillType(Path.FillType.INVERSE_WINDING);
            mPath.addCircle(600, 400, 200, Path.Direction.CW);
            mPath.addCircle(900, 400, 200, Path.Direction.CCW);
    
            canvas.drawPath(mPath, mPaint);
            canvas.restore();
        }
    

    两圆相交的部分需要绘制颜色,非圆区域绘制颜色。效果图一览:


    在这里插入图片描述
    INVERSE_EVEN_ODD(3)

    INVERSE_EVEN_ODD 模式与 INVERSE_WINDING 模式的不同的绘制方向效果一样,代码如下:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.save();
            mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
            mPath.addCircle(600, 400, 200, Path.Direction.CW);
            mPath.addCircle(900, 400, 200, Path.Direction.CCW);
    
            canvas.drawPath(mPath, mPaint);
            canvas.restore();
        }
    

    效果图:


    在这里插入图片描述

    相信各位小伙伴们看到这里,心里很清楚了 gif 圆角控件的填充样式为以下两种情况:

    • INVERSE_WINDING 样式,逆时针绘制圆角矩形
    • INVERSE_EVEN_ODD 样式

    编写代码

    通过上文原理的介绍,gif 图圆角控件的代码就非常简单。

    起名字

    接地气的名字,能够让人眼前一亮,就叫 CornersGifView吧。

    CornersGifView圆角控件

    核心代码如下(3 行代码):

            mPath.reset();
            // add round rect
            mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
            mPath.addRoundRect(new RectF(0, 0, w, h), mCorners, Path.Direction.CCW);
    

    相关参数:

    • w 表示控件的宽度
    • h 表示控件的高度
    • mCorners 圆角数组(大小为 8,一组圆角包含长宽两个参数,4 组圆角顾 8 个参数)
    • 绘制方向 Path.Direction.CCW 与 Path.Direction.CW 效果一致

    大家可能已经注意到了,非圆角矩形区域的颜色,为 mPaint 画笔的颜色,那么就需要保证画笔的颜色与父控件的背景颜色一致,如果父控件颜色为透明,那么就需要取父控件的父控件颜色,以此类推,递归获取父控件的颜色,请参考以下两个方法:

        /**
         * 获取父控件颜色
         * @param parent
         * @return
         */
        private int getParentBackGroundColor(ViewParent parent) {
            if (parent == null) {
                return Color.WHITE;
            }
            if (parent instanceof View) {
                View parentView = (View) parent;
                int parentColor = getViewBackGroundColor(parentView);
                if (parentColor != Color.TRANSPARENT) {
                    return parentColor;
                } else {
                    getParentBackGroundColor(parentView.getParent());
                }
            }
            return Color.WHITE;
        }
    

    通过反射获取 View 的背景颜色值:

        /**
         * 获取 View 的背景色
         * @param view
         * @return
         */
        private int getViewBackGroundColor(View view) {
            Drawable drawable = view.getBackground();
            if (drawable != null) {
                Class<Drawable> mDrawable_class = (Class<Drawable>) drawable.getClass();
                try {
                    Field mField = mDrawable_class.getDeclaredField("mColorState");
                    mField.setAccessible(true);
                    Object mColorState = mField.get(drawable);
                    Class mColorState_class = mColorState.getClass();
                    Field mColorState_field = mColorState_class.getDeclaredField("mUseColor");
                    mColorState_field.setAccessible(true);
                    int color = (int) mColorState_field.get(mColorState);
                    if (color != Color.TRANSPARENT) {
                        return color;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return Color.TRANSPARENT;
        }
    

    不仅支持 gif 图圆角,还支持静态图圆角,最终效果图如下:


    在这里插入图片描述

    结束语

    「张帅B」的那段话,概述的非常经典。都说 80 后的在忙着挣钱;00 后在忙着谈恋爱;只有我们 90 的在忙着赚钱又谈恋爱,最后钱没挣到,恋爱也没谈到。

    最后再啰嗦一点,生活都不容易,一直坚持做一件事情更不容易,还希望各位朋友,能够多多支持,小编新开的公众号「控件人生」,有你们的相伴,才有写下去的动力。还会有超额的现金红包发放~


    扫一扫 关注我的公众号,与小伙伴们一同成长~

    相关文章

      网友评论

        本文标题:探一探,非常实用的GIF图圆角控件(3行代码)

        本文链接:https://www.haomeiwen.com/subject/dibyiqtx.html