Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android圓角圖片最簡單的實現方法詳解

Android圓角圖片最簡單的實現方法詳解

編輯:關於Android編程

1. 前言:

在平時的開發中,我們在顯示圖片是有時候需要顯示圓角圖片,我們應該都知道圓角顯示肯定是更加耗費內存和性能,會導致圖片的過度繪制等問題。但是有時候產品的設計就是這樣,我們開發也不得不做,本篇文章講一下最基本的圓角圖片實現方法:

2. 原理講解之Paint.setXfermode:

2.1 Paint.setXfermode就是本次實現圓角圖片的關鍵地方:

       /**
         * 我簡單理解為設置畫筆在繪制時圖形堆疊時候的顯示模式
         * SRC_IN:取兩層繪制交集。顯示上層。
         */
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

這個方法很復雜,我這裡只是為本次使用做出解釋,這個方法大概的意思就是:設置畫筆在繪制時圖形堆疊時候的顯示模式。畫筆在繪制時圖形堆疊時候的顯示模式有16種之多,google也給出了圖文解釋:
setXfermode
看上圖就可以知道,就是兩個view堆疊在一起的時候是怎麼現實的,是顯示交集部分,非交集部分,交集部分的上層還是下層等等。
具體的PorterDuff.Mode請看:

1.PorterDuff.Mode.CLEAR

   所繪制不會提交到畫布上。

2.PorterDuff.Mode.SRC

   顯示上層繪制圖片

3.PorterDuff.Mode.DST

  顯示下層繪制圖片

4.PorterDuff.Mode.SRC_OVER

  正常繪制顯示,上下層繪制疊蓋。

5.PorterDuff.Mode.DST_OVER

  上下層都顯示。下層居上顯示。

6.PorterDuff.Mode.SRC_IN

   取兩層繪制交集。顯示上層。

7.PorterDuff.Mode.DST_IN

  取兩層繪制交集。顯示下層。

8.PorterDuff.Mode.SRC_OUT

 取上層繪制非交集部分。

9.PorterDuff.Mode.DST_OUT

 取下層繪制非交集部分。

10.PorterDuff.Mode.SRC_ATOP

 取下層非交集部分與上層交集部分

11.PorterDuff.Mode.DST_ATOP

  取上層非交集部分與下層交集部分

12.PorterDuff.Mode.XOR
    現實非交集部分
13.PorterDuff.Mode.DARKEN

14.PorterDuff.Mode.LIGHTEN

15.PorterDuff.Mode.MULTIPLY

16.PorterDuff.Mode.SCREEN

2.2 圓角圖片實現原理:

這裡寫圖片描述

3. 代碼講解

自定義控件的基本步驟就是測量控件大小,確定控件位置,繪制控件,我們這個圓角圖片控件是不需要確定控件位置。

3.1 createRoundConerImage把源圖片圓角顯示:

原理上面講了,先繪制一個圓角矩形,再把我們的源圖片繪制在這個圓角矩形的畫布上,畫筆的顯示模式是SRC_IN,取上層交集部分,直接看代碼:

    /**
     * 根據給定的圖片和已經測量出來的寬高來繪制圓角圖形
     * 原理:
     * 基本原理就是先畫一個圓角的圖形出來,然後在圓角圖形上畫我們的源圖片,
     * 圓角圖形跟我們的源圖片堆疊時我們取交集並顯示上層的圖形
     * 原理就是這樣,很簡單。
     */
    private Bitmap createRoundConerImage(Bitmap source){
        final Paint paint = new Paint();
        /**開啟抗鋸齒**/
        paint.setAntiAlias(true);
        /****/
        Bitmap target  = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888);
        /**
         * Construct a canvas with the specified bitmap to draw into. The       bitmapmust be mutable
         * 以bitmap對象創建一個畫布,則將內容都繪制在bitmap上,bitmap不得為null;
         */
        Canvas canvas = new Canvas(target);
        /**新建一個矩形繪制區域,並給出左上角和右下角的坐標**/
        RectF rect = new RectF(0 , 0 ,mWidth ,mHeight);
        /**
         * 把圖片縮放成我們想要的大小
         */
        source = Bitmap.createScaledBitmap(source,mWidth,mHeight,false);
        /**在繪制矩形區域繪制用畫筆繪制一個圓角矩形**/
        canvas.drawRoundRect(rect ,mRadius ,mRadius ,paint);
        /**
         * 我簡單理解為設置畫筆在繪制時圖形堆疊時候的顯示模式
         * SRC_IN:取兩層繪制交集。顯示上層。
         */
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(source ,0 ,0 ,paint);
        /****/
        return target;
    }

需要注意,我們在繪制第二層,也就是我們的源圖片的時候,所需要的繪制矩形的寬高一定是跟我們測量的寬高是一直的,要是圖片的本身的寬高與測量得到的寬高不一致時,我們就對源圖片進行縮放,所以會有下面的代碼:

        /**新建一個矩形繪制區域,並給出左上角和右下角的坐標**/
        /**mWidt和mHeight是測量得到的控件寬高 **/
        RectF rect = new RectF(0 , 0 ,mWidth ,mHeight);
        /**
         * 把圖片縮放成我們想要的大小
         */
        source = Bitmap.createScaledBitmap(source,mWidth,mHeight,false);

3.2 onMeasure測量控件大小:

    /**
     * 測量控件大小
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("danxx" ,"onMeasure");
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /**獲取寬高的測量模式**/
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        /**獲取寬高的尺寸**/
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        /**
         * 測量寬度
         */
        if(widthSpecMode == MeasureSpec.EXACTLY){  //寬為具體值或者是填滿父控件就直接賦值 match_parent , accurate
            mWidth = widthSpecSize;
        }else{
            /**圖片顯示時原始大小**/
            int srcWidth = mSrc.getWidth() + getPaddingLeft() + getPaddingRight();
            if(widthSpecMode == MeasureSpec.AT_MOST){ //wrap_content,子控件不能超過父控件,此時我們取傳遞過來的大小和圖片本身大小的小者
                mWidth = Math.min(widthSpecSize , srcWidth);
            }else{
                //沒有要求,可以隨便大小
                mWidth = srcWidth;
            }
        }

        /**
         * 測量高度,邏輯跟測量寬度是一樣的
         */
        if(heightSpecMode == MeasureSpec.EXACTLY){  //match_parent , accurate
            mHeight = heightSpecSize;
        }else{
            /**圖片顯示時原始大小**/
            int srcHeigth = mSrc.getHeight() + getPaddingTop() + getPaddingBottom();
            if(heightSpecMode == MeasureSpec.AT_MOST){ //wrap_content
                mHeight = Math.min(heightSpecSize , srcHeigth);
            }else{
                //沒有要求,可以隨便大小
                mHeight = srcHeigth;
            }
        }
        setMeasuredDimension(mWidth ,mHeight);
    }

寬高分兩次測量,下面簡單介紹一下控件的測量:
控件的測量就是處理我們在創建控件時設置的寬高大小,一般非為三種情況,具體寬高值、填滿父控件,包含內容。在OnMeasure方法中我們使用MeasureSpec來獲取控件寬高到底是哪一種情況。

一個MeasureSpec封裝了父布局傳遞給子布局的布局要求,每個MeasureSpec代表了一組寬度和高度的要求。

三種測量模式解釋:

UNSPECIFIED:父布局沒有給子布局任何限制,子布局可以任意大小。 EXACTLY:父布局決定子布局的確切大小。不論子布局多大,它都必須限制在這個界限裡。match_parent AT_MOST:此時子控件尺寸只要不超過父控件允許的最大尺寸,子布局可以根據自己的大小選擇任意大小。wrap_content

簡單的映射關系:

wrap_content -> MeasureSpec.AT_MOST match_parent -> MeasureSpec.EXACTLY 具體值 -> MeasureSpec.EXACTLY

3.3 onDraw繪制控件:

    /**
     * 繪制控件
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        Log.d("danxx" ,"onDraw");
//        super.onDraw(canvas);
        canvas.drawBitmap(createRoundConerImage(mSrc) ,0 ,0 ,null);
    }

4. 全部代碼:

package danxx.library.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import danxx.library.R;

/**
 * Created by Danxx on 2016/7/29.
 * 最簡單的方式實現圓角圖片
 */
public class SampleCircleImageView extends View {
    /**
     * 默認圓角大小
     */
    private static final int DEFUALT_RADIUS = 20;
    /**
     * 源圖片
     */
    private Bitmap mSrc;
    /**
     * 圓角大小,默認為20
     */
    private int mRadius = DEFUALT_RADIUS;
    /**
     * 控件的寬度
     */
    private int mWidth;
    /**
     * 控件的高度
     */
    private int mHeight;
    private Context mContext;


    public SampleCircleImageView(Context context) {
        super(context);
        init(context ,null ,0);
    }

    public SampleCircleImageView(Context context ,Bitmap bitmap) {
        super(context);
        Log.d("danxx" ,"create SampleCircleImageView");
        this.mSrc = bitmap;
        init(context ,null ,0);
    }

    public SampleCircleImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context ,attrs ,0);
    }

    public SampleCircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context ,attrs ,defStyleAttr);
    }

    private void init(Context context ,AttributeSet attrs ,int defStyleAttr){
        mContext = context;
        if(attrs != null){
            /**Load the styled attributes and set their properties**/
            TypedArray typedArray = context.obtainStyledAttributes(attrs , R.styleable.SampleCircleImageView ,defStyleAttr ,0);
            mSrc = BitmapFactory.decodeResource(context.getResources() ,typedArray.getResourceId(R.styleable.SampleCircleImageView_src ,0));
            mRadius = (int) typedArray.getDimension(R.styleable.SampleCircleImageView_radius ,dp2px(DEFUALT_RADIUS));
            typedArray.recycle();
        }

    }

    /**
     * 測量控件大小
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("danxx" ,"onMeasure");
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /**
         * 一個MeasureSpec封裝了父布局傳遞給子布局的布局要求,每個MeasureSpec代表了一組寬度和高度的要求。
         * 三種測量模式解釋:
         *  UNSPECIFIED:父布局沒有給子布局任何限制,子布局可以任意大小。
         *  EXACTLY:父布局決定子布局的確切大小。不論子布局多大,它都必須限制在這個界限裡。match_parent
         *  AT_MOST:此時子控件尺寸只要不超過父控件允許的最大尺寸,子布局可以根據自己的大小選擇任意大小。wrap_content
         *
         * 簡單的映射關系:
         *  wrap_content -> MeasureSpec.AT_MOST
         *  match_parent -> MeasureSpec.EXACTLY
         *  具體值 -> MeasureSpec.EXACTLY
         */

        /**獲取寬高的測量模式**/
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        /**獲取寬高的尺寸**/
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        /**
         * 測量寬度
         */
        if(widthSpecMode == MeasureSpec.EXACTLY){  //寬為具體值或者是填滿父控件就直接賦值 match_parent , accurate
            mWidth = widthSpecSize;
        }else{
            /**圖片顯示時原始大小**/
            int srcWidth = mSrc.getWidth() + getPaddingLeft() + getPaddingRight();
            if(widthSpecMode == MeasureSpec.AT_MOST){ //wrap_content,子控件不能超過父控件,此時我們取傳遞過來的大小和圖片本身大小的小者
                mWidth = Math.min(widthSpecSize , srcWidth);
            }else{
                //沒有要求,可以隨便大小
                mWidth = srcWidth;
            }
        }

        /**
         * 測量高度,邏輯跟測量寬度是一樣的
         */
        if(heightSpecMode == MeasureSpec.EXACTLY){  //match_parent , accurate
            mHeight = heightSpecSize;
        }else{
            /**圖片顯示時原始大小**/
            int srcHeigth = mSrc.getHeight() + getPaddingTop() + getPaddingBottom();
            if(heightSpecMode == MeasureSpec.AT_MOST){ //wrap_content
                mHeight = Math.min(heightSpecSize , srcHeigth);
            }else{
                //沒有要求,可以隨便大小
                mHeight = srcHeigth;
            }
        }
        setMeasuredDimension(mWidth ,mHeight);
    }

    /**
     * 繪制控件
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        Log.d("danxx" ,"onDraw");
//        super.onDraw(canvas);
        canvas.drawBitmap(createRoundConerImage(mSrc) ,0 ,0 ,null);
    }

    /**
     * 設置圓角大小
     * @param radius
     */
    public void setRadius(int radius){
        this.mRadius = radius;
    }

    /**
     * 設置圖片
     * @param bitmap
     */
    public void setSrc(Bitmap bitmap){
        this.mSrc = bitmap;
    }

    /**
     * 根據給定的圖片和已經測量出來的寬高來繪制圓角圖形
     * 原理:
     * 基本原理就是先畫一個圓角的圖形出來,然後在圓角圖形上畫我們的源圖片,
     * 圓角圖形跟我們的源圖片堆疊時我們取交集並顯示上層的圖形
     * 原理就是這樣,很簡單。
     */
    private Bitmap createRoundConerImage(Bitmap source){
        final Paint paint = new Paint();
        /**開啟抗鋸齒**/
        paint.setAntiAlias(true);
        /****/
        Bitmap target  = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888);
        /**
         * Construct a canvas with the specified bitmap to draw into. The bitmapmust be mutable
         * 以bitmap對象創建一個畫布,則將內容都繪制在bitmap上,bitmap不得為null;
         */
        Canvas canvas = new Canvas(target);
        /**新建一個矩形繪制區域,並給出左上角和右下角的坐標**/
        RectF rect = new RectF(0 , 0 ,mWidth ,mHeight);
        /**
         * 把圖片縮放成我們想要的大小
         */
        source = Bitmap.createScaledBitmap(source,mWidth,mHeight,false);
        /**在繪制矩形區域繪制用畫筆繪制一個圓角矩形**/
        canvas.drawRoundRect(rect ,mRadius ,mRadius ,paint);
        /**
         * 我簡單理解為設置畫筆在繪制時圖形堆疊時候的顯示模式
         * SRC_IN:取兩層繪制交集。顯示上層。
         */
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(source ,0 ,0 ,paint);
        /****/
        return target;
    }

    protected int sp2px(float spValue) {
        final float fontScale = mContext.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    protected int dp2px(float dp) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }

}

5. 效果圖和源碼地址:

GitHub源碼地址https://github.com/Dawish

三種寬高設置模式都有:

xml布局文件:



    
        
        

        
        

        
        

        
        
    

效果圖:

這裡寫圖片描述

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved