Google發(fā)布舶替,玩轉(zhuǎn)ShapeableImageView令境,告別第三方庫(kù)

前言

做過(guò)安卓開(kāi)發(fā)的都知道,安卓的UI開(kāi)發(fā)耗時(shí)耗力顾瞪,實(shí)現(xiàn)不規(guī)則圖片效果舔庶,如老生常談的圓角抛蚁、圓形圖片,要么引入第三方控件惕橙,要么自定義ImageView瞧甩,第三方控件不一定滿足,而自定義ImageView對(duì)開(kāi)發(fā)者有一定的要求且花時(shí)間弥鹦。Google在去年發(fā)布的Android Material 組件 (MDC-Android) 1.2.0肚逸,提供了豐富的控件,有助于提高UI開(kāi)發(fā)效率彬坏,今天的主角ShapeableImageView正式其中一員吼虎,類(lèi)似的還有MaterialButton。

看下效果:


先來(lái)看下ShapeableImageView是什么

從類(lèi)繼承關(guān)系看出苍鲜,ShapeableImageView只不過(guò)是ImageView的一個(gè)子類(lèi),但是可以輕松實(shí)現(xiàn)效果圖中的各種樣式玷犹。

xml屬性

屬性名 作用
shapeAppearance 形狀外觀樣式混滔,引用 style 樣式
shapeAppearanceOverlay 外觀疊加樣式,引用 style 樣式
strokeWidth 描邊寬度
strokeColor 描邊顏色

使用

引入material包

implementation 'com.google.android.material:material:1.2.1'

常規(guī)使用

<com.google.android.material.imageview.ShapeableImageView
     android:id="@+id/image"
     android:layout_width="110dp"
     android:layout_height="110dp"
     android:padding="1dp"
     android:src="@drawable/head"
/>

跟ImageView效果一樣歹颓。

各種花俏樣式

1坯屿、圓角圖片

        <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image1"
            android:layout_width="110dp"
            android:layout_height="110dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/roundedCornerStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

    <!-- 圓角圖片 -->
    <style name="roundedCornerStyle">
        <item name="cornerFamily">rounded</item>
        <item name="cornerSize">8dp</item>
    </style>

2、圓形圖片

 <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image2"
            android:layout_width="110dp"
            android:layout_height="110dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/circleStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

    <!-- 圓形圖片 -->
    <style name="circleStyle">
        <item name="cornerFamily">rounded</item>
        <item name="cornerSize">50%</item>
    </style>

3巍扛、切角圖片

 <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image3"
            android:layout_width="110dp"
            android:layout_height="110dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/cutCornerStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

    <!-- 切角圖片 -->
    <style name="cutCornerStyle">
        <item name="cornerFamily">cut</item>
        <item name="cornerSize">12dp</item>
    </style>

4领跛、菱形圖片

  <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image4"
            android:layout_width="110dp"
            android:layout_height="110dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/diamondStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

    <!-- 菱形圖片 -->
    <style name="diamondStyle">
        <item name="cornerFamily">cut</item>
        <item name="cornerSize">50%</item>
    </style>

5、右上角圓角圖片

 <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image5"
            android:layout_width="110dp"
            android:layout_height="110dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/topRightCornerStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

    <!-- 右上角圓角圖片 -->
    <style name="topRightCornerStyle">
        <item name="cornerFamilyTopRight">rounded</item>
        <item name="cornerSizeTopRight">50dp</item>
    </style>

6撤奸、小雞蛋圖片

   <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image6"
            android:layout_width="110dp"
            android:layout_height="110dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/eggStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

    <!-- 小雞蛋圖片 -->
    <style name="eggStyle">
        <item name="cornerFamilyTopRight">rounded</item>
        <item name="cornerSizeTopRight">50dp</item>
        <item name="cornerSizeTopLeft">50dp</item>
        <item name="cornerFamilyTopLeft">rounded</item>
    </style>

7吠昭、組合弧度圖片效果

    <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image7"
            android:layout_width="110dp"
            android:layout_height="110dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/comCornerStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

    <!-- 組合弧度圖片效果 -->
    <style name="comCornerStyle">
        <item name="cornerFamily">rounded</item>
        <item name="cornerSizeTopRight">50%</item>
        <item name="cornerSizeBottomLeft">50%</item>
    </style>

8、 小 Tips

 <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image8"
            android:layout_width="110dp"
            android:layout_height="50dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/tipsCornerStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

    <!-- 小 Tips -->
    <style name="tipsCornerStyle">
        <item name="cornerFamilyTopLeft">rounded</item>
        <item name="cornerSizeTopLeft">50%</item>
        <item name="cornerFamilyBottomLeft">rounded</item>
        <item name="cornerSizeBottomLeft">50%</item>
        <item name="cornerFamilyTopRight">cut</item>
        <item name="cornerSizeTopRight">50%</item>
        <item name="cornerFamilyBottomRight">cut</item>
        <item name="cornerSizeBottomRight">50%</item>
    </style>

9胧瓜、扇形圖片

 <com.google.android.material.imageview.ShapeableImageView
            android:id="@+id/image9"
            android:layout_width="110dp"
            android:layout_height="110dp"
            android:padding="1dp"
            android:src="@drawable/head"
            app:shapeAppearance="@style/fanStyle"
            app:strokeColor="@android:color/holo_blue_bright"
            app:strokeWidth="2dp"/>

對(duì)應(yīng)的style:

  <!-- 扇形 -->
    <style name="fanStyle">
        <item name="cornerFamilyBottomLeft">rounded</item>
        <item name="cornerFamilyBottomRight">rounded</item>
        <item name="cornerFamilyTopLeft">rounded</item>
        <item name="cornerFamilyTopRight">rounded</item>
        <item name="cornerSizeBottomLeft">0dp</item>
        <item name="cornerSizeBottomRight">0dp</item>
        <item name="cornerSizeTopLeft">0%</item>
        <item name="cornerSizeTopRight">100%</item>
    </style>

通過(guò)源碼學(xué)知識(shí)

從前面應(yīng)用可以發(fā)現(xiàn)矢棚,通過(guò)定義ShapeableImageView的shapeAppearance屬性style值,可以實(shí)現(xiàn)各種不同的樣式府喳,而style有哪些的屬性蒲肋,分別表示什么,定義了這些style實(shí)現(xiàn)這些樣式效果的原理是什么钝满,帶著這些疑問(wèn)閱讀源碼兜粘。

 public ShapeableImageView(Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(wrap(context, attrs, defStyle, DEF_STYLE_RES), attrs, defStyle);
    // Ensure we are using the correctly themed context rather than the context that was passed in.
    context = getContext();

    clearPaint = new Paint();
    clearPaint.setAntiAlias(true);
    clearPaint.setColor(Color.WHITE);
    clearPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
    destination = new RectF();
    maskRect = new RectF();
    maskPath = new Path();
    TypedArray attributes =
        context.obtainStyledAttributes(
            attrs, R.styleable.ShapeableImageView, defStyle, DEF_STYLE_RES);

    strokeColor =
        MaterialResources.getColorStateList(
            context, attributes, R.styleable.ShapeableImageView_strokeColor);

    strokeWidth = attributes.getDimensionPixelSize(R.styleable.ShapeableImageView_strokeWidth, 0);

    borderPaint = new Paint();
    borderPaint.setStyle(Style.STROKE);
    borderPaint.setAntiAlias(true);
    shapeAppearanceModel =
        ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build();
    shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
      setOutlineProvider(new OutlineProvider());
    }
  }

在構(gòu)造方法中有兩行核心代碼:

   shapeAppearanceModel =
        ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build();
    shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel);

可以看出,style的屬性由ShapeAppearanceModel來(lái)管理弯蚜,得到style屬性后構(gòu)造MaterialShapeDrawable對(duì)象孔轴,由MaterialShapeDrawable繪制形狀。

設(shè)置邊和角的屬性

通過(guò) R 文件可以查看當(dāng)前 ShapeAppearanceModel 具有的屬性:

    <declare-styleable name="ShapeAppearance">
      <!-- Corner size to be used in the ShapeAppearance. All corners default to this value -->
      <attr format="dimension|fraction" name="cornerSize"/>
      <!-- Top left corner size to be used in the ShapeAppearance. -->
      <attr format="dimension|fraction" name="cornerSizeTopLeft"/>
      <!-- Top right corner size to be used in the ShapeAppearance. -->
      <attr format="dimension|fraction" name="cornerSizeTopRight"/>
      <!-- Bottom right corner size to be used in the ShapeAppearance. -->
      <attr format="dimension|fraction" name="cornerSizeBottomRight"/>
      <!-- Bottom left corner size to be used in the ShapeAppearance. -->
      <attr format="dimension|fraction" name="cornerSizeBottomLeft"/>

      <!-- Corner family to be used in the ShapeAppearance. All corners default to this value -->
      <attr format="enum" name="cornerFamily">
        <enum name="rounded" value="0"/>
        <enum name="cut" value="1"/>
      </attr>
      <!-- Top left corner family to be used in the ShapeAppearance. -->
      <attr format="enum" name="cornerFamilyTopLeft">
        <enum name="rounded" value="0"/>
        <enum name="cut" value="1"/>
      </attr>
      <!-- Top right corner family to be used in the ShapeAppearance. -->
      <attr format="enum" name="cornerFamilyTopRight">
        <enum name="rounded" value="0"/>
        <enum name="cut" value="1"/>
      </attr>
      <!-- Bottom right corner family to be used in the ShapeAppearance. -->
      <attr format="enum" name="cornerFamilyBottomRight">
        <enum name="rounded" value="0"/>
        <enum name="cut" value="1"/>
      </attr>
      <!-- Bottom left corner family to be used in the ShapeAppearance. -->
      <attr format="enum" name="cornerFamilyBottomLeft">
        <enum name="rounded" value="0"/>
        <enum name="cut" value="1"/>
      </attr>
    </declare-styleable>
      <declare-styleable name="ShapeableImageView">
      <attr name="strokeWidth"/>
      <attr name="strokeColor"/>

      <!-- Shape appearance style reference for ShapeableImageView. Attribute declaration is in the
           shape package. -->
      <attr name="shapeAppearance"/>
      <!-- Shape appearance overlay style reference for ShapeableImageView. To be used to augment
           attributes declared in the shapeAppearance. Attribute declaration is in the shape package.
           -->
      <attr name="shapeAppearanceOverlay"/>
    </declare-styleable>

可以看出碎捺,通過(guò)ShapeAppearanceModel 可以定義各種邊和角的屬性距糖。

繪制圖形

自定義不規(guī)則圖片的一般的做法是重寫(xiě)ImageView的onDraw方法玄窝,處理邊角則使用\color{red}{PorterDuffXfermode}。何為PorterDuffXfermode悍引?

類(lèi)\color{red}{android.graphics.PorterDuffXfermode}繼承自android.graphics.Xfermode恩脂。在用Android中的Canvas進(jìn)行繪圖時(shí),可以通過(guò)使用PorterDuffXfermode將所繪制的圖形的像素與Canvas中對(duì)應(yīng)位置的像素按照一定規(guī)則進(jìn)行混合趣斤,形成新的像素值俩块,從而更新Canvas中最終的像素顏色值,這樣會(huì)創(chuàng)建很多有趣的效果浓领。

使用方法也必將簡(jiǎn)單玉凯,將其作為參數(shù)傳給Paint.setXfermode(Xfermode xfermode)方法,這樣在用該畫(huà)筆paint進(jìn)行繪圖時(shí)联贩,Android就會(huì)使用傳入的PorterDuffXfermode漫仆,如果不想再使用Xfermode,那么可以執(zhí)行Paint.setXfermode(null)泪幌。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //設(shè)置背景色
        canvas.drawARGB(255, 139, 197, 186);

        int canvasWidth = canvas.getWidth();
        int r = canvasWidth / 3;
        //正常繪制黃色的圓形
        paint.setColor(0xFFFFCC44);
        canvas.drawCircle(r, r, r, paint);
        //使用CLEAR作為PorterDuffXfermode繪制藍(lán)色的矩形
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        paint.setColor(0xFF66AAFF);
        canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
        //最后將畫(huà)筆去除Xfermode
        paint.setXfermode(null);
    }

效果如下:


而可以實(shí)現(xiàn)的混合效果非常多盲厌,如圖:


在ShapeableImageView的構(gòu)造方法中可用看到一行:

  clearPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));

可知ShapeableImageView的原理也是使用PorterDuffXfermode將圖片和指定的圖形混合得到想要的不規(guī)則圖片。其核心代碼如下:

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawPath(maskPath, clearPaint);
    drawStroke(canvas);
  }
  @Override
  protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
    super.onSizeChanged(width, height, oldWidth, oldHeight);
    updateShapeMask(width, height);
  }
 private void updateShapeMask(int width, int height) {
    destination.set(
        getPaddingLeft(), getPaddingTop(), width - getPaddingRight(), height - getPaddingBottom());
    pathProvider.calculatePath(shapeAppearanceModel, 1f /*interpolation*/, destination, path);
    // Remove path from rect to draw with clear paint.
    maskPath.rewind();
    maskPath.addPath(path);
    // Do not include padding to clip the background too.
    maskRect.set(0, 0, width, height);
    maskPath.addRect(maskRect, Direction.CCW);
  }

代碼比較容易看懂祸泪,從onDraw看到繪制的流程:
1吗浩、先調(diào)用父類(lèi)ImageView的onDraw繪制基本圖片;
2没隘、生成不規(guī)則的圖片懂扼,clearPaint設(shè)置了PorterDuffXfermode(Mode.DST_OUT),即去掉src圖片重疊部分右蒲,僅保留剩下部分阀湿,而maskPath正是由不規(guī)則圖形與矩形圖片邊框組成;
3瑰妄、繪制邊界炕倘。

總結(jié)

上面只是分析了ShapeableImageView的核心代碼,很多細(xì)節(jié)沒(méi)有展開(kāi)翰撑,ShapeableImageView提供了豐富的屬性罩旋,通過(guò)改變邊角的值的組合可以實(shí)現(xiàn)各種各樣的圖形,使用起來(lái)是非常方便的眶诈,值得推薦涨醋。

參考

ShapeableImageView官方文檔
Android中Canvas繪圖之PorterDuffXfermode使用及工作原理詳解

關(guān)注V: “碼農(nóng)翻身記”,回復(fù)888逝撬,免費(fèi)領(lǐng)取Android/Java高頻面試題解析浴骂、進(jìn)階知識(shí)整理、圖解網(wǎng)絡(luò)宪潮、圖解操作系統(tǒng)等資料溯警。關(guān)注后趣苏,你將不定期收到優(yōu)質(zhì)技術(shù)及職場(chǎng)干貨分享。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梯轻,一起剝皮案震驚了整個(gè)濱河市食磕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喳挑,老刑警劉巖彬伦,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異伊诵,居然都是意外死亡单绑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)曹宴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搂橙,“玉大人,你說(shuō)我怎么就攤上這事笛坦∏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵弯屈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我恋拷,道長(zhǎng)资厉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任蔬顾,我火速辦了婚禮宴偿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诀豁。我一直安慰自己窄刘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布舷胜。 她就那樣靜靜地躺著娩践,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烹骨。 梳的紋絲不亂的頭發(fā)上翻伺,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音沮焕,去河邊找鬼吨岭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛峦树,可吹牛的內(nèi)容都是我干的辣辫。 我是一名探鬼主播旦事,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼急灭!你這毒婦竟也來(lái)了姐浮?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤化戳,失蹤者是張志新(化名)和其女友劉穎单料,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體点楼,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扫尖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掠廓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片换怖。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蟀瞧,靈堂內(nèi)的尸體忽然破棺而出沉颂,到底是詐尸還是另有隱情,我是刑警寧澤悦污,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布铸屉,位于F島的核電站,受9級(jí)特大地震影響切端,放射性物質(zhì)發(fā)生泄漏彻坛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一踏枣、第九天 我趴在偏房一處隱蔽的房頂上張望昌屉。 院中可真熱鬧,春花似錦茵瀑、人聲如沸间驮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竞帽。三九已至,卻和暖如春鸿捧,著一層夾襖步出監(jiān)牢的瞬間抢呆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工笛谦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抱虐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓饥脑,卻偏偏與公主長(zhǎng)得像恳邀,于是被迫代替她去往敵國(guó)和親懦冰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容