提到这个到不是因为要做照片处理软件的滤镜效果,而是开发过程中设计师每每只提供一张icon图标,然后说点击效果你自己用程序实现吧;或者产品经理过来说,用户头像加个点击效果吧。以往的开发过程中,设计师多半会提供两张icon图标,一张正常状态,一张选中状态,这样点击的时候会有一个动态的效果。而现在一张图去实现点击效果还真有点难为了,车到山前必有路,谁让我是老司机呢。
首先我印象中Imageview会有个透明度Alpha的属性,于是我最先想到用setAlpha()来改变图片的透明度,但实际效果并不好。后来在Stay的点拨下终于找到了比较好的解决方案,ImageView有一个setColorFilter()的API,完全适合当前场景。
/**
* Set a tinting option for the image.
*
* @param color Color tint to apply.
* @param mode How to apply the color. The standard mode is
* {@link PorterDuff.Mode#SRC_ATOP}
*
* @attr ref android.R.styleable#ImageView_tint
*/
public final void setColorFilter(int color, PorterDuff.Mode mode) {
setColorFilter(new PorterDuffColorFilter(color, mode));
}
/**
* Set a tinting option for the image. Assumes
* {@link PorterDuff.Mode#SRC_ATOP} blending mode.
*
* @param color Color tint to apply.
* @attr ref android.R.styleable#ImageView_tint
*/
@RemotableViewMethod
public final void setColorFilter(int color) {
setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
/**
* Apply an arbitrary colorfilter to the image.
*
* @param cf the colorfilter to apply (may be null)
*
* @see #getColorFilter()
*/
public void setColorFilter(ColorFilter cf) {
if (mColorFilter != cf) {
mColorFilter = cf;
mColorMod = true;
applyColorMod();
invalidate();
}
}
setColorFilter有以上三种调用方式,可以直接传入色值和mode,mode缺省的默认值为PorterDuff.Mode.SRC_ATOP,具体各mode的效果可以参见http://blog.sina.com.cn/s/blog_5da93c8f01012pkj.html, setColorFilter还可以传入ColorFilter的子类。
ColorFilter有三个子类ColorMatrixColorFilter,LightingColorFilter,PorterDuffColorFilter,它的功能应该就是按照一定的规则改变图片的颜色,三个子类各有各的不同的改法规则,其中ColorMatrixColorFilter的改变法则就是ColorMatrix的改变规则,它是ColorMatrix的应用。LightingColorFilter 乘以第一个颜色的RGB通道,然后加上第二个颜色。每一次转换的结果都限制在0到255之间。PorterDuffColorFilter 可以使用数字图像合成的16条Porter-Duff 规则中的任意一条来向Paint应用一个指定的颜色。
我们就挑第一个ColorMatrixColorFilter来举例,前面已经说过,ColorMatrixColorFilter的改变法则就是ColorMatrix的改变规则,它是ColorMatrix的应用。ColorMatrix是Android源码中一个颜色矩阵类,通过对这个类的一系列操作,可以控制改变图片的色调明暗饱和度等,这也就是图片处理软件实现滤镜效果的原理。这个类把颜色定义为一个4*5的矩阵,如果用一个一纬数组表示就是这样:
[ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ]。
/**
* 4x5 matrix for transforming the color+alpha components of a Bitmap.
* The matrix is stored in a single array, and its treated as follows:
* [ a, b, c, d, e,
* f, g, h, i, j,
* k, l, m, n, o,
* p, q, r, s, t ]
* * When applied to a color [r, g, b, a], the resulting color is computed as * (after clamping)
* R' = a*R + b*G + c*B + d*A + e;
* G' = f*R + g*G + h*B + i*A + j;
* B' = k*R + l*G + m*B + n*A + o;
* A' = p*R + q*G + r*B + s*A + t;
*/
这里是源码里的注释,已经解释的很清楚了,RGBA的运算公式也清晰明了,所以实际调用过程中,我们可以通过传入需要的参数来控制RGBA的值,进而改变图片的色彩。
/**
* Set this colormatrix to identity:
* [ 1 0 0 0 0 - red vector
* 0 1 0 0 0 - green vector
* 0 0 1 0 0 - blue vector
* 0 0 0 1 0 ] - alpha vector
*/
这里是一张图片的默认色彩属性值,我们只需要根据需求去调校各参数值,就能实现图片的滤镜效果了。再回到最开始的诉求,我是想通过程序实现单张图片的点击效果,但是设计师只提供了一张图,那么我只需要在手指按下的时候去给当前图片加个滤镜,抬起时再移除即可。简单粗暴点,直接上代码。
/**
* Created by yx on 16/4/3.
*/
public class DiscolorImageView extends ImageView{
/**
* 变暗
*/
private final float[] SELECTED_DARK = new float[]
{1, 0, 0, 0, -80,
0, 1, 0, 0, -80,
0, 0, 1, 0, -80,
0, 0, 0, 1, 0};
/**
* 变亮
*/
private final float[] SELECTED_BRIGHT = new float[]
{1, 0, 0, 0, 80,
0, 1, 0, 0, 80,
0, 0, 1, 0, 80,
0, 0, 0, 1, 0};
/**
* 高对比度
*/
private final float[] SELECTED_HDR = new float[]
{5, 0, 0, 0, -250,
0, 5, 0, 0, -250,
0, 0, 5, 0, -250,
0, 0, 0, 1, 0};
/**
* 高饱和度
*/
private final float[] SELECTED_HSAT = new float[]
{(float) 3, (float) -2, (float) -0.2, 0, 50,
-1, 2, -0, 0, 50,
-1, -2, 4, 0, 50,
0, 0, 0, 1, 0};
/**
* 改变色调
*/
private final float[] SELECTED_DISCOLOR = new float[]
{(float) -0.5, (float) -0.6, (float) -0.8, 0, 0,
(float) -0.4, (float) -0.6, (float) -0.1, 0, 0,
(float) -0.3, 2, (float) -0.4, 0, 0,
0, 0, 0, 1, 0};
public DiscolorImageView(Context context) {
super(context);
this.setOnTouchListener(VIEW_TOUCH_DISCOLOR);
}
public DiscolorImageView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOnTouchListener(VIEW_TOUCH_DISCOLOR);
}
public DiscolorImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setOnTouchListener(VIEW_TOUCH_DISCOLOR);
}
public OnTouchListener VIEW_TOUCH_DISCOLOR = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
ImageView iv = (ImageView) v;
iv.setColorFilter(new ColorMatrixColorFilter(SELECTED_HDR));
//iv.setColorFilter(new ColorMatrixColorFilter(SELECTED_BRIGHT));
//iv.setColorFilter(new ColorMatrixColorFilter(SELECTED_HDR));
//iv.setColorFilter(new ColorMatrixColorFilter(SELECTED_HSAT));
//iv.setColorFilter(new ColorMatrixColorFilter(SELECTED_DISCOLOR));
} else if (event.getAction() == MotionEvent.ACTION_UP) {
ImageView iv = (ImageView) v;
iv.clearColorFilter();
mPerformClick();
} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
ImageView iv = (ImageView) v;
iv.clearColorFilter();
}
return true;
}
};
private void mPerformClick() {
DiscolorImageView.this.performClick();
}
}
我这里随便给出了几个滤镜效果的参数值,但是未经调教,有可能效果一塌糊涂,基本原理很简单了,又要说到onTouch事件了,按下去的时候显示滤镜效果图,抬起时移除滤镜显示原图,这样点击的时候就有了一个动态的selector效果了。如下图:
参考资料:
http://blog.sina.com.cn/s/blog_5da93c8f01012pkj.htm
http://my.oschina.net/gavinjin/blog/208586