身为android开发者,ImageView那一定是玩的滚瓜烂熟,现在如今Material Design设计也越来越流行,给ImageView实现阴影也不是什么难事,用CardView包裹一下,就能实现了,但是阴影都是一种颜色,实在太单调了╮(╯▽╰)╭,我就在想ImageView阴影能不能设置成图片自身的颜色呢?,答案当然是可以的,基于这个思路就有了PaletteImageView这个控件,PaletteImageView不仅仅可以将阴影设置成图片自身颜色,还可以解析图片的颜色,提供可以跟图片匹配的UI颜色方案,是不是很酷,接下来让我们一步步来实现它。
先看一下效果图:
要实现的功能:
- 默认情况下为图片添加相应颜色的阴影
- 可以自定义阴影的颜色
- 可以为图片添加圆角
- 可以设置阴影半径大小,提供可以调控的用户体验
- 可以自定义设置阴影分别在x和y方向上的偏移量,更高的可控性
- 根据图片自身的颜色提供与图片匹配的颜色方案(智能推荐颜色功能)
难点就是怎么才能获取到图片本身的颜色呢?,这就要用到Palette这个类了,Palette就是调色板的意思,不了解的可以参考这篇文章Palette使用详解。 我们先来实现给图片添加相应颜色的阴影,下面只贴出关键代码,要阅读完整代码请移步github https://github.com/DingMouRen/PaletteImageView,主要思路是解析获取到的bitmap的颜色,当解析出bitmap颜色的时候,使用handler来通知控件来绘制带有颜色的阴影
在onSizeChanged(…)中能够获取到控件宽高的时候,对图片先进行压缩处理 ,为了防止内存溢出情况的发生,然后就是获取图片颜色得操作
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
zipBitmap(mImgId, mBitmap, mOnMeasureHeightMode);//先压缩图片,再去解析图片bitmap中的颜色
mRectFShadow = new RectF(mPadding, mPadding, getWidth() - mPadding, getHeight() - mPadding);//阴影的载体
...
}
压缩图片
private void zipBitmap(int imgId, Bitmap bitmap, int heightNode) {
WeakReference<Matrix> weakMatrix = new WeakReference<Matrix>(new Matrix());
if (weakMatrix.get() == null) return;
Matrix matrix = weakMatrix.get();
int reqWidth = getWidth() - mPadding - mPadding;
int reqHeight = getHeight() - mPadding - mPadding;
if (reqHeight <= 0 || reqWidth <= 0) return;
int rawWidth = 0;
int rawHeight = 0;
if (imgId != 0 && bitmap == null) {
WeakReference<BitmapFactory.Options> weakOptions = new WeakReference<BitmapFactory.Options>(new BitmapFactory.Options());
if (weakOptions.get() == null) return;
BitmapFactory.Options options = weakOptions.get();
BitmapFactory.decodeResource(getResources(), imgId, options);
options.inJustDecodeBounds = true;
rawWidth = options.outWidth;
rawHeight = options.outHeight;
options.inSampleSize = calculateInSampleSize(rawWidth, rawHeight, getWidth() - mPadding * 2, getHeight() - mPadding * 2);
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeResource(getResources(), mImgId, options);
} else if (imgId == 0 && bitmap != null) {
rawWidth = bitmap.getWidth();
rawHeight = bitmap.getHeight();
float scale = rawHeight * 1.0f / rawWidth;
mRealBitmap = Bitmap.createScaledBitmap(bitmap, reqWidth, (int) (reqWidth * scale), true);
initShadow(mRealBitmap);
return;
}
if (heightNode == 0) {
float scale = rawHeight * 1.0f / rawWidth;
mRealBitmap = Bitmap.createScaledBitmap(bitmap, reqWidth, (int) (reqWidth * scale), true);
} else {
int dx = 0;
int dy = 0;
int small = Math.min(rawHeight, rawWidth);
int big = Math.max(reqWidth, reqHeight);
float scale = big * 1.0f / small;
matrix.setScale(scale, scale);
if (rawHeight > rawWidth) {
dy = (rawHeight - rawWidth) / 2;
} else if (rawHeight < rawWidth) {
dx = (rawWidth - rawHeight) / 2;
}
mRealBitmap = Bitmap.createBitmap(bitmap, dx, dy, small, small, matrix, true);
}
initShadow(mRealBitmap);//解析图片颜色,关键
}
解析图片的关键代码
private void initShadow(Bitmap bitmap) {
if (bitmap != null) {
mAsyncTask = Palette.from(bitmap).generate(paletteAsyncListener);//异步解析图片的颜色
}
}
//异步线程中解析图片颜色的监听器
private Palette.PaletteAsyncListener paletteAsyncListener = new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
if (palette != null) {
...
mMainColor = palette.getDominantSwatch().getRgb();//获取图片的主色,也就是默认的阴影颜色
mHandler.sendEmptyMessage(MSG);//解析到图片的颜色,此时发送重绘的消息
...
}
}
};
//处理发送的消息
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//阴影半径大小以及x、y方向上阴影偏移量的限制
if (mOffsetX < DEFAULT_OFFSET) mOffsetX = DEFAULT_OFFSET;
if (mOffsetY < DEFAULT_OFFSET) mOffsetY = DEFAULT_OFFSET;
if (mShadowRadius < DEFAULT_SHADOW_RADIUS) mShadowRadius = DEFAULT_SHADOW_RADIUS;
//设置画笔阴影的颜色,也就是图片阴影的颜色了
mPaintShadow.setShadowLayer(mShadowRadius, mOffsetX, mOffsetY, mMainColor);
invalidate();//重新绘制控件
}
};
绘制阴影
@Override
protected void onDraw(Canvas canvas) {
if (mRealBitmap != null) {
canvas.drawRoundRect(mRectFShadow, mRadius, mRadius, mPaintShadow);//绘制带有颜色的阴影
...
if (mMainColor != -1) mAsyncTask.cancel(true);//取消解析图片颜色的异步任务
}
}
经过上面就可以绘制出带有颜色的阴影,要自定义阴影颜色的话,使用这句代码
public void setShadowColor(int color){
this.mMainColor = color;
mHandler.sendEmptyMessage(MSG);//修改了阴影颜色,发送重新绘制的消息
}
接下来我们给图片设置圆角,在onSizeChange(…)获取到宽高以及bitmap的同时,我们就创建好了带有圆角的mRoundBitmap,绘制过阴影后,就绘制可以带有圆角的图片
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
...
mRoundRectF = new RectF(0, 0, getWidth() - mPadding * 2, getHeight() - mPadding * 2);
mRoundBitmap = createRoundConerImage(mRealBitmap,mRadius);//创建带有圆角的bitmap图片
}
//创建带有圆角的bitmap图片
private Bitmap createRoundConerImage(Bitmap source, int radius) {
Bitmap target = Bitmap.createBitmap(getWidth() - mPadding * 2, getHeight() - mPadding * 2, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(target);
canvas.drawRoundRect(mRoundRectF, radius, radius, mPaint);
mPaint.setXfermode(mPorterDuffXfermode);
canvas.drawBitmap(source, 0, 0, mPaint);
mPaint.setXfermode(null);
return target;
}
@Override
protected void onDraw(Canvas canvas) {
if (mRealBitmap != null) {
...
canvas.drawBitmap(mRoundBitmap, mPadding, mPadding, null);//绘制带有圆角的bitmap
if (mMainColor != -1) mAsyncTask.cancel(true);
}
}
修改圆角半径的效果图
经过以上带有多彩颜色阴影的图片已经出笼了,现在我们要可以修改阴影半径的大小,修改阴影半径大小的方式有两种,一种是在xml中定义,另一种是在代码中设置
//xml中添加属性:
app:paletteShadowRadius 表示阴影半径
//代码中设置阴影半径大小的源码
public void setPaletteShadowRadius(int radius) {
this.mShadowRadius = radius;//修改阴影半径大小
mHandler.sendEmptyMessage(MSG);//发送消息,阴影半径修改了,发送重绘消息
}
修改阴影半径的效果图
修改阴影在x y方向的偏移量的原理跟上面一样,修改参数再发送消息重绘
public void setPaletteShadowOffset(int offsetX, int offsetY) {
if (offsetX >= mPadding) {
this.mOffsetX = mPadding;
} else {
this.mOffsetX = offsetX;
}
if (offsetY > mPadding) {
this.mOffsetX = mPadding;
} else {
this.mOffsetY = offsetY;
}
mHandler.sendEmptyMessage(MSG);//阴影在x y方向的偏移量改变了,此时发送消息进行重绘。
}
修改阴影在x y方向的偏移量的效果图
现在终于说到智能配色的地方了,看过Palette使用详解这篇文章的都知道获取到颜色方案的过程了,不了解的可以先去看看,没有太神秘的东西O(∩_∩)O哈哈~,下面讲一下实现过程:
首先定义一个监听器,用来监听图片颜色的解析过程,解析完成我们返回看控件本身的引用,我们可以获取六种不同颜色主题,每种主题颜色都有与之匹配的标题颜色、正文颜色、背景色。怎么获取到这些颜色呢,实现是将每种主题的是三种子颜色放入一个数组中存储,只要拿到相应的数组就可以获取到想要的颜色了。
public interface OnParseColorListener {
void onComplete(PaletteImageView paletteImageView);
void onFail();
}
//异步线程中解析图片颜色的监听器
private Palette.PaletteAsyncListener paletteAsyncListener = new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
if (palette != null) {
mPalette = palette;
mMainColor = palette.getDominantSwatch().getRgb();
mHandler.sendEmptyMessage(MSG);
if (mListener != null) mListener.onComplete(mInstance);//解析颜色完成
} else {
if (mListener != null) mListener.onFail();//解析颜色失败
}
}
};
//获取Vibrant主题的颜色,
public int[] getVibrantColor() {
if (mPalette == null || mPalette.getVibrantSwatch() == null) return null;
int[] arry = new int[3];
arry[0] = mPalette.getVibrantSwatch().getTitleTextColor();
arry[1] = mPalette.getVibrantSwatch().getBodyTextColor();
arry[2] = mPalette.getVibrantSwatch().getRgb();
return arry;
}
智能配色效果图
大功告成,可以愉快的玩耍啦。PaletteImageView的具体使用方法,请移步github
项目源码地址https://github.com/DingMouRen/PaletteImageView