摘要:
毛玻璃实现方法。
代码说明:
我自己做了一个Android的输入法去参加学校的比赛,然后我做到最后,尝试美化UI,美化UI的过程中,我想起IOS上的高斯模糊的效果(也就是毛玻璃效果),所以我决定对安卓系统也进行尝试。
我在网上查找了很多方法,起初的打算是先把输入法的界面设置为透明,然后用view.getDrawingCache();方法来获取输入法界面的截图,然后获得bitmap之后,对bitmap进行高斯模糊处理,获得高斯模糊之后的bitmap,对我的输入法使用keyboardview.setbackground();把处理后的图片做为键盘的背景。我要求在键盘弹出的瞬间,进行处理并改变背景,所以我在onwindowshown()方法里写了刚才提到的解决方案的代码。
但是,得到的结果却是view.getDrawingCache();报错,因为得到的bitmap是空的,第一次尝试就这样失败了。后来仔细想想为什么会得到空值,我觉得可能的原因有两个,1.我在xml里把背景设置成透明的,因此无法获得图片。2.onwindowshown()会在键盘弹出的时候执行,可能那时候整个键盘都没有渲染好,更别说获取到bitmap了。后来,我认为就算获得了bitmap,也是无法做成功的,因为keyboardview是属于view类的一种,就算能获得截图,也是应用内的截图,无法获得当前应用外的内容,也就是说获得的图片很可能是无法获取躲在键盘后面的内容的。
于是我进行了另一种尝试,安卓5.0以后可以使用MediaProjectionManager,使用系统自带的方法来进行对全屏的截图。
我把我的思路总结为:
1.获得全屏幕的截图(这个是需要权限的)。
2.获得view的像素高度和宽度(在我的项目里是keyboardview)。
3.获得屏幕的像素高宽。
4.判断是否有虚拟按键,如果有,获得虚拟按键的高度。
5.对获得的截图,通过刚刚获得的高宽,对bitmap进行裁切。
6.将裁切好的bitmap进行高斯模糊处理,return处理好的bitmap。
7.将处理好的bitmap设置为keyboardview的背景。
步骤:
1.获得全屏幕的截图。
我在网上看到,全屏幕的截图都是在activity里进行的,而我的输入法是service,如果启动一个一般的activity必然会剥夺键盘的焦点,弹出activity界面才能截图,那这样的话,只要一点输入框,就会弹出activity的界面,使得键盘又会弹回去,这样以此往复,输入法根本无法使用,更不用说制造毛玻璃的背景了。
因此,我的目的是要创造一个没有界面的,不会影响当前焦点的,截图完成立马滚蛋的activity。
在网上可以找到很多的获得截图的教程。
但是要达到一个没有界面的,不会影响当前焦点的,截图完成立马滚蛋的activity,需要以下条件
1.manifest.xml里,android:theme="@android:style/Theme.NoDisplay">
2.java代码里,要覆盖onResume() ,并把需要做的事情放在onResume()里而不是 onCreate()。
3.这里的代码和其他的模块有耦合,单纯拷贝是无法运行的,先读好代码,这里的代码并不复杂。
ScreenAct.java
package com.example.lenovo.imputmethod;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import java.nio.ByteBuffer;
import static android.graphics.PixelFormat.RGBA_8888;
public class ScreenAct extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
time=5;
}
static boolean isRealFinish=false;
@Override
protected void onResume() { //要干的活写在这里。
super.onResume();
checkScreenShotPermission(); //截图的动作从这里开始。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finish();
}
MediaProjectionManager projectionManager;
MediaProjection mediaProjection;
DisplayMetrics metrics;
Display display;
ImageReader imageReader;
VirtualDisplay virtualDisplay;
int width;
int height;
private static final int SCREEN_CAPTURE_PERMISSION = 0x119;
Handler handler;
protected void checkScreenShotPermission() {
projectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
handler=new Handler();
startActivityForResult(projectionManager.createScreenCaptureIntent(), SCREEN_CAPTURE_PERMISSION);
}
/**
* 初始化截屏相关设置
* MediaProjectionManager -> MediaProjection -> VirtualDisplay
* */
protected void screenShotPrepare() {
if(mediaProjection==null)
return;
display = ((WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
metrics = new DisplayMetrics();
display.getRealMetrics(metrics);
Point point = new Point();
display.getRealSize(point);
width = point.x;
height = point.y;
//将屏幕画面放入ImageReader关联的Surface中
imageReader = ImageReader.newInstance(width, height, RGBA_8888, 1);
virtualDisplay = mediaProjection.createVirtualDisplay("ScreenShotDemo",
width, height, metrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
imageReader.getSurface(), null/*Callbacks*/, null/*Handler*/);
handler.post(runnableui);
}
public static Bitmap bitmap;
Runnable runnableui=new Runnable() {
@Override
public void run() {
TimeThread thread=new TimeThread();
thread.start();
}
// handler.post(runnableui);
//imageView.setImageBitmap( bitmap);
// setContentView(R.layout.activity_main);
};
protected Bitmap screenShot()
{
Image image = imageReader.acquireLatestImage(); //获取缓冲区中的图像。
if(image==null)
return null;
//Image -> Bitmap
final Image.Plane[] planes = image.getPlanes();
final ByteBuffer buffer = planes[0].getBuffer();
int rowStride = planes[0].getRowStride(); //Image中的行宽,大于Bitmap所设的真实行宽
byte[] oldBuffer = new byte[rowStride*height];
buffer.get(oldBuffer);
byte[] newBuffer = new byte[width*4*height];
Bitmap bitmap = Bitmap.createBitmap(metrics, width, height, Bitmap.Config.ARGB_8888);
for (int i = 0; i < height; ++i) {
System.arraycopy(oldBuffer,i*rowStride,newBuffer,i*width*4,width*4);
}
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(newBuffer));
image.close();
return bitmap;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case SCREEN_CAPTURE_PERMISSION:
mediaProjection = projectionManager.getMediaProjection(resultCode, data);
screenShotPrepare();
return;
}
}
public static Bitmap bitmapcut(int height,int width,int hstart,int scheight) //对获得的截图进行裁切
{
Log.e("sys", "bitmapcut: "+(scheight-(scheight-(scheight-(height+hstart)))) );
Bitmap bitmaptemp=Bitmap.createBitmap(bitmap, 0,(scheight-(scheight-(scheight-(height+hstart))))+hstart, width, height, null, false);
bitmaptemp = Bitmap.createScaledBitmap(bitmaptemp,bitmaptemp.getWidth()/3,bitmaptemp.getHeight()/3, true);
bitmaptemp=brightness(bitmaptemp,(float)(0.6));
return bitmaptemp;
}
private static Bitmap brightness(Bitmap bitmap,float ld)
{
//Bitmap bmp = bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
ColorMatrix ldMatrix = new ColorMatrix();// 设置亮度
ldMatrix.setScale(ld, ld, ld, 1);
paint.setColorFilter(new ColorMatrixColorFilter(ldMatrix));
canvas.drawBitmap(bitmap, 0, 0, paint);// 重新画图
return bitmap;
}
int time;
public class TimeThread extends Thread {
@Override
public void run () {
do {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(AndroidXXIME.needtoshot) {
bitmap = screenShot(); //截图是个耗时的操作,因此在新的线程里进行,并且需要设置时限,否则截图失败就会一直循环引起手机发烫。
if(bitmap!=null) {
Log.e("sc", "run: sc ok");
AndroidXXIME.needtoshot=false;
}
else
{
Log.e("sc", "run: sc failed" );
time--;
if(time==0)
break;
}
}
} while(true);
}
}
}
在manifest.xml注册activity
<activity
android:name=".ScreenAct"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
2.获得view的高宽(在这里view是keyboardview,只要是view类就有这个方法)。
int keyHeight ;
int keyWidth;
keyWidth = keyboardView.getWidth(); // 获取宽度
keyHeight = keyboardView.getHeight(); // 获取高度
注意,必须要等到渲染完成之后在获取,否则会获得null。
3.获得屏幕的高宽。
int screenWidth;
int screenHeight;
DisplayMetrics dm = getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
4.获得虚拟按键的高度.
int vHeight=0;
vHeight=ScreenUtils.getNavigationBarHeight(this); //如果没有虚拟按键,则vHeight=0。
这里,this为context类。
5.对图片进行切割。
public static Bitmap bitmapcut(int height,int width,int hstart,int scheight) //对获得的截图进行裁切
{
Log.e("sys", "bitmapcut: "+(scheight-(scheight-(scheight-(height+hstart)))) );
Bitmap bitmaptemp=Bitmap.createBitmap(bitmap, 0,(scheight-(scheight-(scheight-(height+hstart))))+hstart, width, height, null, false);
bitmaptemp = Bitmap.createScaledBitmap(bitmaptemp,bitmaptemp.getWidth()/3,bitmaptemp.getHeight()/3, true);
bitmaptemp=brightness(bitmaptemp,(float)(0.6));
return bitmaptemp;
}
private static Bitmap brightness(Bitmap bitmap,float ld) //将图片变暗,这样效果会好一点。
{
//Bitmap bmp = bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
ColorMatrix ldMatrix = new ColorMatrix();// 设置亮度
ldMatrix.setScale(ld, ld, ld, 1);
paint.setColorFilter(new ColorMatrixColorFilter(ldMatrix));
canvas.drawBitmap(bitmap, 0, 0, paint);// 重新画图
return bitmap;
}
6.对图片进行高斯模糊处理(快速高斯模糊,高斯模糊部分是我从网上抄来的,出处在注释里)
FastBlur.java
package com.example.lenovo.imputmethod;
import android.graphics.Bitmap;
/**
* Created by paveld on 3/6/14.
*/
public class FastBlur {
public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
// Stack Blur v1.0 from
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
//
// Java Author: Mario Klingemann <mario at quasimondo.com>
// http://incubator.quasimondo.com
// created Feburary 29, 2004
// Android port : Yahel Bouaziz <yahel at kayenko.com>
// http://www.kayenko.com
// ported april 5th, 2012
// This is a compromise between Gaussian Blur and Box blur
// It creates much better looking blurs than Box Blur, but is
// 7x faster than my Gaussian Blur implementation.
//
// I called it Stack Blur because this describes best how this
// filter works internally: it creates a kind of moving stack
// of colors whilst scanning through the image. Thereby it
// just has to add one new block of color to the right side
// of the stack and remove the leftmost color. The remaining
// colors on the topmost layer of the stack are either added on
// or reduced by one, depending on if they are on the right or
// on the left side of the stack.
//
// If you are using this algorithm in your code please add
// the following line:
//
// Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
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++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
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);
}
}
将bitmap转为drawable类。
if (bitmap != null) {
drawable1 = new BitmapDrawable(FastBlur.doBlur(bitmap, 25, true)); //高斯模糊处理
handler.post(runnableui);
break;
}
7.将处理好的图片设置为view的背景(因为图片处理是耗时的操作,所以开启新线程里处理)
Runnable runnableui=new Runnable() {
@Override
public void run() {
if(backtime==0) //如果在限定时间内没有获得截图。
{
keyboardView.setBackgroundColor(getResources().getColor(R.color.ksb));
keyboardView.setAlpha(0.6f);
}
else {
keyboardView.setBackground(drawable1); 设置背景
}
ScreenAct.bitmap=null;
}
};
调用handler.post(runnableui);设置view的背景,至此,代码的编写就完成了。
效果:
问题:
目前有一个问题无法解决,就是在应用启动的时候会弹出需要权限,点了“不再提示”的就不会再弹出,如果不点的话,每次都会弹出,这可能就是逆潮流的代价吧。
或者解决办法就是弄一个设置向导,哄骗一下用户让他点不在提示,我就是用这样的办法的。
总结:
安卓是原生不支持毛玻璃的,那些国产的系统的自带的launcher或者通知栏能达到毛玻璃的效果的原因我认为是修改了系统的底层代码,使得系统可以实现毛玻璃效果,不过这个效果就仅限于系统自带的应用了。我这个就不需要修改底层代码,在原生安卓就可以实现毛玻璃效果。但是值得注意的是,我这个毛玻璃是静态的,不会随着下边内容的改变而改变,只有再键盘弹出的瞬间会变更bitmap,实现的其实是一个假的毛玻璃效果,所以ios就是ios,总有安卓无法超越的东西,因为安卓硬件的杂乱和性能参差不齐,原生支持毛玻璃可能会出现兼容性或者性能问题,但是如果像国产系统那样修改底层就失去了普适性。
这个方法不仅仅是输入法可以用,其实对话框等等控件也是可以使用的,我认为这对美化UI以及足够了,至少在比赛中项目的演示是可以达到不错的效果的。如果有什么不对的地方欢迎指正,因为我也在继续学习。