編輯:關於Android編程
在平時的開發中,我們在顯示圖片是有時候需要顯示圓角圖片,我們應該都知道圓角顯示肯定是更加耗費內存和性能,會導致圖片的過度繪制等問題。但是有時候產品的設計就是這樣,我們開發也不得不做,本篇文章講一下最基本的圓角圖片實現方法:
/** * 我簡單理解為設置畫筆在繪制時圖形堆疊時候的顯示模式 * SRC_IN:取兩層繪制交集。顯示上層。 */ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
這個方法很復雜,我這裡只是為本次使用做出解釋,這個方法大概的意思就是:設置畫筆在繪制時圖形堆疊時候的顯示模式。畫筆在繪制時圖形堆疊時候的顯示模式有16種之多,google也給出了圖文解釋:
看上圖就可以知道,就是兩個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
自定義控件的基本步驟就是測量控件大小,確定控件位置,繪制控件,我們這個圓角圖片控件是不需要確定控件位置。
原理上面講了,先繪制一個圓角矩形,再把我們的源圖片繪制在這個圓角矩形的畫布上,畫筆的顯示模式是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);
/** * 測量控件大小 * @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代表了一組寬度和高度的要求。
/** * 繪制控件 * @param canvas */ @Override protected void onDraw(Canvas canvas) { Log.d("danxx" ,"onDraw"); // super.onDraw(canvas); canvas.drawBitmap(createRoundConerImage(mSrc) ,0 ,0 ,null); }
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); } }
三種寬高設置模式都有:
Android的窗口體系中,WindowManager占有非常重要的地位,它封裝了添加、移除、更新窗口的方法,它是Activity、View的更加底層的管理類,使用Win
OKHttp3是如今非常流行的Android網絡請求框架,那麼如何利用Android實現斷點續傳呢,今天寫了個Demo嘗試了一下,感覺還是有點意思准備階段我們會用到OKH
一,為什麼說是真正的高仿? 闡述這個問題前,先說下之前網上的,各位可以復制這段字,去百度一下 仿微信打開網頁的進度條效果 ,你會看到有很多類似的文章,不過他
在很多方面,藍牙是一種能夠發送或接受兩個不同的設備之間傳輸的數據。 Android平台包含了藍牙框架,使設備以無線方式與其他藍牙設備進行數據交換的支持。Android提供