先看第一个效果图:
前景的背景是一个图片,后景的背景是淡蓝色(即左上角小圈里的颜色),左上角的圆圈还有一个边框
看图片效果其实就是需要在一个图片的某个地方透明显示一个圆形区域(附加一个边框);用图片的混合实现起来其实很简单,直接上代码:
/**
* 在一个原图上绘制一个指定位置和大小的空心透明(可附带边框)的圆
*
* @param src 原图
* @param w 新的宽度
* @param h 新的高度
* @param cx 圆的中心坐标x
* @param cy 圆的中心坐标y
* @param radius 圆的半径
* @param padding 圆的边框大小
* @param color 圆的边框颜色
* @return
*/
public Bitmap processCricle(Bitmap src, int w, int h, int cx, int cy, int radius, int padding, int color) {
//首先你可能需要缩放原图
int sw = src.getWidth();
int sh = src.getHeight();
Matrix matrix = new Matrix();
matrix.setScale(1.0f * w / sw, 1.0f * h / sh);
Bitmap scaleBmp = Bitmap.createBitmap(src, 0, 0, sw, sh, matrix, true);
//开始
Bitmap newBmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBmp);
Paint paint = new Paint();
paint.setAntiAlias(true);
//画空心圆
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(cx + padding, cy + padding, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
canvas.drawBitmap(scaleBmp, 0, 0, paint);
//画边框
if (padding > 0) {
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(padding);
paint.setColor(color);
canvas.drawCircle(cx + padding, cy + padding, radius, paint);
}
scaleBmp.recycle();
return newBmp;
}
代码里面有注释,我就不过多叙述了,主要使用了二种 PorterDuffXfermode模式,当然代码的开头对原图做了一个缩放操作,你可以忽略。下面来看第二种效果图:
整个图片需要显示一个立体效果,效果图的主要部分可以是任何图片(资源/本地/网络)
首先图片里的文字直接PASS
背景遮罩:实现上图中背景遮罩的部分采用了层叠形式,看下图就明白了:
我们知道ImageView有 背景 和 前景 的概念,所以直接将背景遮罩设为它的背景图片就好:
<com.xxxx.view.CardView
android:id="@+id/pic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="@drawable/fl_bg"
android:scaleType="matrix" />
看代码,上面的CardView是一个自定义的ImageView,它的背景为fl_bg,就是上面的那个背景遮罩效果图。下面是自定义view的源码:
public class CardView extends ImageView {
private static final String TAG = "CardView"
public int width, height;//图片实际的宽高
boolean flag = false;//标志位,很重要
public static int WIDTH;//默认的宽,取屏幕的宽度,实际值可随意指定
public CardView (Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (WIDTH == 0) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
//view_padding这里作者的图宽度是占满屏幕,所有采用的默认值是屏幕的宽度
float offset = context.getResources().getDimension(R.dimen.view_padding);
WIDTH = (int) (metrics.widthPixels - offset * 2);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取view的宽度,这里作者的图宽度是占满屏幕,所以采用了MOST,你可以指定一个具体的宽和高
if (width == 0) {
width = MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.AT_MOST);
}
init();
setMeasuredDimension(width, height);
}
//初始化宽高
void init() {
if (width <= 0) {
width = getMeasuredWidth();
}
if (height <= 0) {
height = Util.getFitHeight(width);
}
}
//为了解决部分机型获取width失败的崩溃问题,你在获取CardView的宽高时,务必使用下面二个方式
public int getWidthEx() {
if (width <= 0 || height <= 0) {
width = WIDTH;
height = Util.getFitHeight(width);
}
return width;
}
public int getHeightEx() {
if (width <= 0 || height <= 0) {
width = WIDTH;
height = Util.getFitHeight(width);
}
return height;
}
/**
* when flag is false:
* setImageBitmap ->
* setCardBitmap ->
* setImageBitmap ->
* super.setImageBitmap ->
* setImageDrawable ->
* super.setImageDrawable
* when flag is true:
* setImageBitmap->super.setImageBitmap ->setImageDrawable ->super.setImageDrawable
*
* @param bm
*/
@Override
public void setImageBitmap(Bitmap bm) {
if (!flag) {
flag = true;
Util.setCardBitmap(this, bm);
} else {
super.setImageBitmap(bm);
flag = false;
}
}
@Override
public void setImageDrawable(Drawable drawable) {
if (!flag) {
flag = true;
Util.setCardBitmap(this, drawable);
} else {
super.setImageDrawable(drawable);
flag = false;
}
}
上面配合注释,读起来应该很简单,
这里要特殊说明一下,如果你像采用指定大小的宽高,请确保宽高和背景遮罩图的宽高比例一致,其中二个Util方法的实现如下,最后我会专门解释下上面的flag:
/*
688比302.59 背景遮罩图的宽高
圆中心与顶部的距离与原图高度比 50比302.59
圆半径:9像素
*/
public final static float FB_ASPECT_1 = 302.59f / 688;
public final static float FB_ASPECT_2 = 50 / 302.59f;
public final static int FB_RADIUS = 9;
//根据宽高比以及实际宽度获取高
public static int getFitHeight(int width) {
return (int) (width * FB_ASPECT_1);
}
//设置前景图,drawableToBitmap转换我就不写了
public static void setCardBitmap(CardView view, Drawable source) {
setCardBitmap(view, drawableToBitmap(source));
}
//设置前景图
public static void setCardBitmap(CardView view, Bitmap source) {
final float density = getResources().getDisplayMetrics().density;
//偏移值,实际上,前景图和背景图并不是完全重叠了,前景图的宽高比背景图稍微小一点,如果完全一样,你就看不到阴影了
final int of = (int) (6 * density);
//注意这里调用的方法名称
final int width = view.getWidthEx() - of;
final int height = view.getHeightEx() - of;
//圆的半径以及距离顶部的偏移
final int radius = (int) (FB_RADIUS * density);
final int offset = (int) (height * FB_ASPECT_2);
//first scale the source
float w = source.getWidth();
float h = source.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(width / w, height / h); //长和宽放大缩小的比例
Log.i(TAG, width + "," + height + "|" + w + "," + h);
final Bitmap scale = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
final Paint paint = new Paint();
paint.setAntiAlias(true);
final Bitmap target = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
/**
* 产生一个同样大小的画布
*/
Canvas canvas = new Canvas(target);
/**
* 首先绘制圆形
*/
canvas.drawCircle(0, offset, radius, paint);
canvas.drawCircle(width, offset, radius, paint);
/**
* 使用SRC_OUT
*/
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
/**
* 绘制图片
*/
canvas.drawBitmap(scale, 0, 0, paint);
//last recycle all unuseful bitmap
source.recycle();
scale.recycle();
Bitmap round = createRoundConerImage(target, width, height, radius >> 1);
view.setImageBitmap(round);
}
/**
* 根据原图添加圆角
*
* @param source
* @return
*/
public static Bitmap createRoundConerImage(Bitmap source, int width, int height, int radius) {
final Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap target = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(target);
RectF rect = new RectF(0, 0, source.getWidth(), source.getHeight());
canvas.drawRoundRect(rect, radius, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(source, 0, 0, paint);
source.recycle();
return target;
}
根据注释应该不难理解,实际上你可以把他放在一个文件内。
最后我说下flag的问题,我的原图是从网络获取的,好吧有一个开源的universalimageloader,那就用上吧,但这个库的displayImage会直接把图片缩放后设置到imageview中,该如何拦截了?这就是上面flag以及重载ImageView二个设置前景图的原因了。看ImageView源码,可以发现它的 setImageBitmap 实际调用的是 setImageDrawable 方法,据此,结合CardView的那个简单注释,我想你画个调用图,应该就可以理解了