1.前言
项目中有需要点击展示大图的,然而发现手机端的图片展示更多的是黑色背景+图片展示,最多加上个缩放动画。更让强迫症纠结的是:因为android的本身的原因(从nexus到小米魅族),使用activity进行图片展示会有几率看到statusbar下滑消失动画(对的,没看错,有几率),单单是几率两字就让人纠结不已......随萌生出了些本博客代码——dialog+高斯模糊+图片展示的想法。
效果:
录制看着卡,真机上流畅。
2.高斯模糊
高斯模糊在苹果系统中很常见,然而在android系统中却鲜有闻,很多app对于高斯模糊的态度是能避免就避免的,究其原因很简单——费时,这里大致讲下高斯模糊的原理:假设一张100*100的图片,上边有10000个像素点,每个像素点作为中心点,按照高斯分布的概率算均值,也就是说,每多一个点,计算时间就会增加很多。闲话不叙,上例子:
这是截的原图。然后,以30个像素点为半径进行高斯模糊:
打印了下log,可以看出耗时是:I/need time: 671 .....(高斯模糊采用的是fastblur方法,如果嫌效率一般,可以考虑用jni,据说速度能降低一半,但是,放在这里的,还是不够)
然而图片并不能让我们满意,虽然有点模糊的意思了,但是感觉还是略微有点棱角......
好吧,那就将半径设为60,看看结果如何:
只能说感觉还好吧,但是用了868ms,上一张我个人比较满意的150的高斯模糊:
然而耗时1156ms.
如果以这种速度去处理图片那就很尴尬了,总不能在做相应操作的时候去等个1s多吧,虽然可以把图片处理放在子线程中,不影响主线程对其他任务的处理,然而这并不是一个靠谱的解决方案,所以决定去尝试下图片压缩:处理速度慢的根本原因是因为想像素数量多,如果我进行压缩呢?
嗯 先上个对比图:
看着差别大么?
一个是长宽压缩过,并进行8像素半径高斯模糊的,一个是原图基础上150高斯模糊的,更喜欢哪个?
左边是原图的,右边是压缩的,压缩后的长宽是原来的十六分之一,更让人高兴的是,压缩+高斯模糊全过程持续了3ms......对没错,一个1156 一个3ms。然而唯一比较遗憾的是,android貌似没有局部截图的工具,如果自己获取整个页面再生成bitmap处理,整个生成bitmap在nexus5上就需要接近40ms,所以暂时来说,没想到有什么好的实时高斯模糊的办法。所以,高斯从淡到完全显示用的是原图上边铺上高斯模糊图,然后alpha动画走起
ok,进入今天的正题,高斯模糊+图片显示
3.高斯模糊背景的图片展示
之前难点都讲的差不多了,直接上代码吧:
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.example.angusfine.mywidget.R;
/**
* Created by AngusFine on 2016/3/22.
*/
public class ImagePreviewDialog extends Dialog implements View.OnClickListener{
private final int animationTime = 250; //动画时间
private Context context;
private ImageView targetView; //目标ImageView
private View mParentView; //DisplayFrame氛围内的RootView
private int[]location = new int[2]; //targetView 屏幕坐标
private Bitmap gaussBg; //mParentView高斯的结果
private ImageView ivGauss;
private ImageView ivShadow;
private ImageView ivShow; //targetView展示
private View currentView; //dialog对应的View
private int currentW;
private int currentH;
private int displayH;
private Rect rect; //displayframe对应的存储信息
private boolean vertical = false;
private boolean backable = true;
float fY; //动画终点Y坐标
float dY;
float fX;
float dX;
/**
*
* @param context
* 类中方法调用对应View的绘制顺序,不可修改
*/
public ImagePreviewDialog(Context context){
super(context, R.style.GaussDialog);
this.context = context;
Log.i("DecordhashCode",String.valueOf(((Activity)context).getWindow().getDecorView().hashCode()));
ViewGroup decor = (ViewGroup) ((Activity)context).getWindow().getDecorView();
Log.i("DecordhashCoded",String.valueOf(decor.hashCode()));
for(int i = 0;i<decor.getChildCount();i++){
if(decor.getChildAt(i)instanceof LinearLayout){
ViewGroup ll = (ViewGroup)decor.getChildAt(i);
for(int j = 0;j<ll.getChildCount();j++){
if (ll.getChildAt(j)instanceof FrameLayout){
this.mParentView = ll.getChildAt(j);
break;
}
}
}
}
initBg();
getStatusBarInfo();
}
public ImagePreviewDialog setTargetView(ImageView view){
((Activity)context).getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
targetView = view;
getImageViewInfo();
initTargetView();
initGaussBitmap();
initAnimation();
return this;
}
/**
* bg初始化
*/
private void initBg(){
currentView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_gauss_bg_pic,null);
setContentView(currentView);
ivGauss = (ImageView) currentView.findViewById(R.id.iv_gauss);
ivShadow = (ImageView) currentView.findViewById(R.id.iv_shadow);
currentView.setOnClickListener(this);
}
/**
* 获取展示View的对应信息
*/
private void getImageViewInfo(){
targetView.getLocationOnScreen(location);
currentW = targetView.getMeasuredWidth();
currentH = targetView.getMeasuredHeight();
}
/**
* 状态栏信息
*/
private void getStatusBarInfo(){
rect = new Rect();
((Activity)context).getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
displayH = ((Activity)context).getWindow().getDecorView().getMeasuredHeight();
}
/**
* 高斯模糊
*/
private void initGaussBitmap(){
gaussBg = convertViewToBitmap(mParentView,mParentView.getWidth(),mParentView.getHeight());
gaussBg = zoomImage(gaussBg,mParentView.getMeasuredWidth()/16,mParentView.getMeasuredHeight()/16);
gaussBg = gaussBlur(gaussBg,5,true);
ivGauss.setImageBitmap(gaussBg);
}
/**
* 加载展示View
*/
private void initTargetView(){
FrameLayout.LayoutParams layoutParams= new FrameLayout.LayoutParams(currentW,currentH);
ivShow = new ImageView(context);
ivShow.setImageDrawable(targetView.getDrawable());
ivShow.setX(location[0]);
ivShow.setY(location[1]-rect.top);
ivShow.setScaleType(targetView.getScaleType());
ivShow.setLayoutParams(layoutParams);
((ViewGroup)currentView).addView(ivShow);
}
private void initAnimation(){
float w = targetView.getMeasuredWidth();
float h = targetView.getMeasuredHeight();
float dw = rect.right - rect.left;
final float dh = rect.bottom - rect.top;
float ratio = h/w;
float dRatio = dh/dw;
final float maxRatio;
backable = true;
ivShadow.setAlpha(0f);
if(ratio>dRatio){
vertical = true;
maxRatio = dh/h;
fY = ((maxRatio-1)*h)/2;
dY = location[1]-fY;
fX = location[0]-dw/2+w/2;
}else{
maxRatio = dw/w;
dY = location[1] - dh/2 +h/2;
fX = ((maxRatio-1)*w)/2;
dX = location[0]-fX;
}
final AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f,1.0f);
alphaAnimation.setDuration(animationTime);
alphaAnimation.setFillAfter(true);
ObjectAnimator animator = ObjectAnimator.ofFloat(ivShow,"an",1.0f,maxRatio).setDuration(200);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float cVal = (Float) animation.getAnimatedValue();
float YDRatio = (cVal-1)/(maxRatio-1);
float dRatio = 1-YDRatio;
if(vertical) {
ivShow.setScaleX(cVal);
ivShow.setScaleY(cVal);
ivShow.setX(location[0] - fX * (YDRatio));
ivShow.setY(dY * dRatio + fY-rect.top*dRatio);
}else{
ivShow.setY(location[1]-rect.top*dRatio-dY*YDRatio);
ivShow.setX(dX*dRatio+fX);
ivShow.setScaleX(cVal);
ivShow.setScaleY(cVal);
}
ivShadow.setAlpha(YDRatio);
}
});
ivGauss.startAnimation(alphaAnimation);
animator.start();
}
private void backAnimation(){
if(!backable)
return;
backable = false;
float w = targetView.getMeasuredWidth();
float h = targetView.getMeasuredHeight();
float dw = rect.right - rect.left;
final float dh = rect.bottom - rect.top;
float ratio = h/w;
float dRatio = dh/dw;
final float maxRatio;
if(ratio>dRatio){
vertical = true;
maxRatio = dh/h;
}else{
maxRatio = dw/w;
fX = ((maxRatio-1)*w)/2;
dX = location[0] - fX;
}
AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f,0f);
alphaAnimation.setDuration(animationTime);
alphaAnimation.setFillAfter(true);
alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
ivShow.setVisibility(View.GONE);
ImagePreviewDialog.super.dismiss();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
ObjectAnimator animator = ObjectAnimator.ofFloat(ivShow,"an",maxRatio,1f).setDuration(animationTime);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float cVal = (Float) animation.getAnimatedValue();
float YDRatio = (cVal-1)/(maxRatio-1);
float dRatio = 1-YDRatio;
if(vertical) {
ivShow.setScaleX(cVal);
ivShow.setScaleY(cVal);
ivShow.setX(location[0] - fX * (YDRatio));
ivShow.setY(dY * dRatio + fY-rect.top*dRatio);
}else{
ivShow.setY(location[1]-rect.top*dRatio-dY*YDRatio);
ivShow.setX(fX+dRatio*dX);
ivShow.setScaleX(cVal);
ivShow.setScaleY(cVal);
}
ivShow.setAlpha(YDRatio);
}
});
animator.start();
ivGauss.startAnimation(alphaAnimation);
ivShadow.startAnimation(alphaAnimation);
}
@Override
public void onBackPressed() {
backAnimation();
}
/**
* 图片缩放
* @param targetBitmap 目标bitmap
* @param newWidth 目标宽度
* @param newHeight 目标高度
* @return
*/
private Bitmap zoomImage(Bitmap targetBitmap, double newWidth, double newHeight) {
float width = targetBitmap.getWidth();
float height = targetBitmap.getHeight();
Matrix matrix = new Matrix();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
matrix.postScale(scaleWidth, scaleHeight);
Bitmap bitmap = Bitmap.createBitmap(targetBitmap, 0, 0, (int) width, (int) height, matrix, true);
return bitmap;
}
/**
* View转Bitmap
* @param view 目标View
* @param bitmapWidth 宽度
* @param bitmapHeight 高度
* @return
*/
private Bitmap convertViewToBitmap(View view, int bitmapWidth, int bitmapHeight){
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
view.draw(new Canvas(bitmap));
return bitmap;
}
/**
* 高斯模糊
* @param sentBitmap 目标bitmap
* @param radius 高斯半径
* @param canReuseInBitmap 是否原图修改
* @return
*/
private Bitmap gaussBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
Bitmap bitmap;
if (canReuseInBitmap) {
bitmap = sentBitmap;
} else {
bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
}
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
@Override
public void onClick(View v) {
backAnimation();
}
}
然后布局代码:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/iv_gauss"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/iv_shadow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent_gray"/>
</FrameLayout>
以及style设置:
<style name="GaussDialog">
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:background">#00000000</item>
</style>
色值设置:
<color name="transparent_gray">#88000000</color>
嗯,使用方法很简单,只要两行代码:
imagePreviewDialog = new ImagePreviewDialog(this);
imagePreviewDialog.setTargetView(iv).show();
(第二行代码,需要在目标View显示后进行调用,否则获得的getmeasuredwidth以及高度会为0)
ok,这就完事了
其实这个版本很不完善,当时代码写的很急,后来也因为各种原因没在进行完善,功能上可以做不少的优化及扩展,以后有空再说吧
转帖请标明