利用 2D 圖形和 PorterDuffXferMode 等實現被遮罩的圖片
阿新 • • 發佈:2019-02-12
圖片的遮罩就是將裁剪遮罩應用於圖片或形狀,定義應用中另一張圖片的可見邊界。
利用 2D 圖形和 PorterDuffXferMode,可以將各種遮罩應用於某張點陣圖。
第一張效果圖:
其基本步驟:
1. 建立一個可變的空白 Bitmap 例項,以及在其中繪圖的 Canvas。
2. 首先在 Canvas 上畫好遮罩模式。
3. 將 PorterDuffXferMode 應用到 Paint 上。
4. 用傳輸模式將原圖繪製到 Canvas 上。
其中的關鍵是 PorterDuffXferMode,它會考慮到 Canvas 中已有的資料的狀態和應用到當前操作的圖形資料的狀態。
第一中方法實現遮罩,使用圖片作為 BitmapShader 將內容繪製到另一個元素中。通過這種方式,就可以將圖片畫素視為用於繪製形狀或者元素的“顏色”,這些形狀或者元素將組成遮罩圖片。
RoundedCornerImageView.java :
該類中關於自定義 view 時,用到的測量等,可以參見我以前的部落格《簡單的完全自定義檢視(同心圓)》:http://blog.csdn.net/antimage08/article/details/50103433點選開啟連結 MainActivity.java :<span style="font-size:18px;">package com.scxh.imagecover; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Shader; import android.util.AttributeSet; import android.view.View; public class RoundedCornerImageView extends View{ private Bitmap mImage; private Paint mBitmapPaint; private RectF mBounds; private float mRadius = 25.0f; public RoundedCornerImageView(Context context) { this(context, null); } public RoundedCornerImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundedCornerImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { // 建立圖片塗繪 mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 建立作為繪圖邊界的矩形 mBounds = new RectF(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height = 0; int width = 0; // 所請求大小是圖片內容的大小 int imageHeight, imageWidth; if (mImage == null) { imageHeight = imageWidth = 0; } else { imageHeight = mImage.getHeight(); imageWidth = mImage.getWidth(); } // 獲得最佳測量值並在檢視上設定該值 width = getMeasurement(widthMeasureSpec, imageWidth); height = getMeasurement(heightMeasureSpec, imageHeight); setMeasuredDimension(width, height); } private int getMeasurement(int measureSpec, int contentSize) { int specSize = MeasureSpec.getSize(measureSpec); switch (MeasureSpec.getMode(measureSpec)) { case MeasureSpec.AT_MOST: return Math.min(specSize, contentSize); case MeasureSpec.UNSPECIFIED: return contentSize; case MeasureSpec.EXACTLY: return specSize; default: return 0; } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (w != oldw || h != oldh) { // 我們要使圖片居中,因此在檢視改變大小時偏移值 int imageWidth, imageHeight; if (mImage == null) { imageWidth = imageHeight = 0; } else { imageWidth = mImage.getWidth(); imageHeight = mImage.getHeight(); } int left = (w - imageWidth) / 2; int top = (h - imageHeight) / 2; // 設定邊界以偏移圓角矩形(整個圖形居中) mBounds.set(left, top, left+imageWidth, top+imageHeight); // 偏移著色器以在矩形內部繪製點陣圖 // 如果沒有此步驟,點陣圖將在檢視中的(0, 0)處 if (mBitmapPaint.getShader() != null) { Matrix m = new Matrix(); m.setTranslate(left, top); mBitmapPaint.getShader().setLocalMatrix(m); } } } /** * 供使用者呼叫,並建立一個 BitmapShader 來封裝圖片畫素,並在用於繪圖 * 的畫筆上進行相應的設定。 * @param bitmap 點陣圖 */ public void setImage(Bitmap bitmap) { if (mImage != bitmap) { mImage = bitmap; if (mImage != null) { BitmapShader shader = new BitmapShader(mImage, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mBitmapPaint.setShader(shader); } else { mBitmapPaint.setShader(null); } // 繪製成 bitmap requestLayout(); } } // 讓檢視繪製背景等物件 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 使用計算得出的值繪製圖片 if (mBitmapPaint != null) { canvas.drawRoundRect(mBounds, mRadius, mRadius, mBitmapPaint); } } } </span>
第二中方法實現遮罩:此處採用兩張圖片,一張如上圖的效果所示;另一張採用一個黑色的倒三角形(從 300 * 300 畫素上扣取)。效果如下: 首先在 Canvas 中繪製三角形圖片,這就是圖片的遮罩。然後,在同一個 Canvas 上繪製原圖時應用 PorterDuff.Mode.SRC_IN 轉換,得到的就是帶圓角的原圖。 這是因為 SRC_IN 轉換模式就是告訴 Paint 物件,只在 Canvas 上原圖和目標圖(已經畫好的三角形)重疊視為地方繪製畫素點,畫素點則來自原圖。 在執行 Android 5.0 及更高版本的裝置上,Android 框架支援通過動態陰影表明檢視的提高(通過 elevation 和 translationZ 屬性)。 在簡單的示例中,可以在內部進行處理,但如果應用任意遮罩,則還必須使用匹配的 ViewOutlineProvider 指示在何處產生陰影。 ViewOutlineProvider 有一個必須的方法 getOutline(),如果由於大小或配置發生變化而需要更新輪廓,就會呼叫該方法。 MaskActivity.java :<span style="font-size:18px;">package com.scxh.imagecover; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); RoundedCornerImageView imageView = new RoundedCornerImageView(this); Bitmap source = BitmapFactory.decodeResource(getResources(), R.drawable.image01); imageView.setImage(source); setContentView(imageView); } } </span>
<span style="font-size:18px;">package com.scxh.imagecover;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
public class MaskActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ImageView imageView = new ImageView(this);
imageView.setScaleType(ImageView.ScaleType.CENTER);
// 建立並載入圖片(通常是不可修改的)
Bitmap source = BitmapFactory.decodeResource(getResources(), R.drawable.image01);
Bitmap mask = BitmapFactory.decodeResource(getResources(), R.drawable.dsjx);
// 建立一個可修改的位置以及一個在其中繪製的 Canvas
final Bitmap result = Bitmap.createBitmap(source.getWidth(),
source.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
canvas.drawBitmap(mask, 0, 0, paint);
// PorterDuff.Mode.SRC_IN 模式:會根據目標邊界對原圖進行裁剪
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(source, 0, 0, paint);
paint.setXfermode(null);
imageView.setImageBitmap(result);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 提高檢視以建立可見陰影(數值越大陰影擴散的範圍就越大)
imageView.setElevation(30f);
// 繪製匹配遮罩的輪廓,從而提供適當的陰影
imageView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
int x = (view.getWidth() - result.getWidth()) / 2;
int y = (view.getHeight() - result.getHeight()) / 2;
Path path = new Path();
// 路徑的起始位置(倒三角形的左上角)
path.moveTo(x, y);
// 沿路徑繪製直線 (倒三角形的右上角)
path.lineTo(x + result.getWidth(), y);
// 沿路徑繪製直線 (倒三角形的下頂點)
path.lineTo(x + result.getWidth() / 2, (float) (y + result.getHeight()/1.6));
// 沿路徑繪製直線 (倒三角形的左上角)
path.lineTo(x, y);
// 繪製成封閉圖形後,關閉路徑
path.close();
outline.setConvexPath(path);
}
});
}
setContentView(imageView);
}
}
</span>
如果輪廓足夠簡單,Android 還可以將其作為檢視的剪下遮罩。只需要呼叫 setClipToOutline(true),即可表明檢視應使用其輪廓作為剪下遮罩。 到Android5.0為止,僅支援通過矩形,圓形和圓角矩形輪廓進行剪下。上圖的三角形就不能用作剪下。 圓形輪廓剪下的效果圖: OutlineActivity.java :
package com.scxh.imagecover;
import android.app.Activity;
import android.graphics.Outline;
import android.os.Bundle;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
public class OutlineActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ImageView imageView = new ImageView(this);
imageView.setScaleType(ImageView.ScaleType.CENTER);
// 提高檢視以建立可見陰影(數值越大陰影擴散的範圍就越大)
imageView.setElevation(30f);
imageView.setImageResource(R.drawable.image02);
// 告訴檢視使用其輪廓作為剪下遮罩
imageView.setClipToOutline(true);
// 為剪下和陰影提供圓形檢視輪廓
imageView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
ImageView mImageView = (ImageView)view;
int radius = mImageView.getDrawable().getIntrinsicHeight()/2;
int centerX = (view.getRight() - view.getLeft())/2;
int centerY = (view.getBottom() - view.getTop())/2;
outline.setOval(centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius);
}
});
setContentView(imageView);
}
}