小新近来魂不守舍,一直思索这样一个问题?3行代码解决GIF圆角问题

 
 

前言

新开的重庆麻辣烫味道不错哟,「大师兄」大快朵颐,这时正好看到「小新」走了进来,一副魂不守舍的样子。


PS:文末有人品红包,文末有人品红包,文末有人品红包。


640?wx_fmt=png640?wx_fmt=png

640?wx_fmt=png


这家麻辣烫真好吃,下次还来。接着讲解 GIF 圆角控件。

正文

先来看看效果图:

640?wx_fmt=gif
在这里插入图片描述

初步分析

众所周知,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);

效果图:

640?wx_fmt=png

发现 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();	
}

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

640?wx_fmt=png

上图是两圆都是顺时针的方向,改变一圆的绘制方向为逆时针:
 
 
 mPath.addCircle(600, 400, 200, Path.Direction.CW);	
 mPath.addCircle(900, 400, 200, Path.Direction.CCW);
 
 

效果图如下:

640?wx_fmt=png

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();	
}

效果图如下:

640?wx_fmt=png

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();	
}

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

640?wx_fmt=png

设置一圆的绘制方向为逆时针:
 
 
@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();	
}
 
 

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

640?wx_fmt=png
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();	
}

效果图:

640?wx_fmt=png

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

编写代码

通过上文原理的介绍,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);

相关参数:

大家可能已经注意到了,非圆角矩形区域的颜色,为 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 图圆角,还支持静态图圆角,最终效果图如下:

640?wx_fmt=gif

结束语

都说 80 后的在忙着挣钱;00 后在忙着谈恋爱;只有我们 90 的在忙着赚钱又谈恋爱,最后钱没挣到,恋爱也没谈到。

源码地址:

https://github.com/HpWens/MeiWidgetView

推荐阅读:

如何准备大厂技术面试?偷学「大师兄」秘籍!

小新欢天喜地第一面,不成想被一个Activity的名称的题目难住了

640?wx_fmt=png


扫一扫 关注我的公众号

想了解更多的趣味面试吗~


PS:微信搜索「控件人生」公众号,长按识别下图小程序(或微信扫一扫):

640?wx_fmt=png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值