BlurLinearLayout.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. package com.kongzue.dialogx.style.views;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.content.ContextWrapper;
  5. import android.content.res.TypedArray;
  6. import android.graphics.Bitmap;
  7. import android.graphics.BitmapShader;
  8. import android.graphics.Canvas;
  9. import android.graphics.Color;
  10. import android.graphics.Matrix;
  11. import android.graphics.Outline;
  12. import android.graphics.Paint;
  13. import android.graphics.Rect;
  14. import android.graphics.RectF;
  15. import android.graphics.Shader;
  16. import android.os.Build;
  17. import android.renderscript.Allocation;
  18. import android.renderscript.Element;
  19. import android.renderscript.RenderScript;
  20. import android.renderscript.ScriptIntrinsicBlur;
  21. import android.util.AttributeSet;
  22. import android.util.Log;
  23. import android.view.View;
  24. import android.view.ViewGroup;
  25. import android.view.ViewOutlineProvider;
  26. import android.view.ViewTreeObserver;
  27. import android.widget.LinearLayout;
  28. import com.kongzue.dialogx.DialogX;
  29. import com.kongzue.dialogx.interfaces.BaseDialog;
  30. import com.kongzue.dialogx.interfaces.BlurViewType;
  31. import com.kongzue.dialogx.iostheme.R;
  32. import com.kongzue.dialogx.util.views.MaxLinearLayout;
  33. public class BlurLinearLayout extends MaxLinearLayout implements BlurViewType {
  34. private float mDownSampleFactor = 4;
  35. private int mOverlayColor = Color.WHITE;
  36. private float mBlurRadius = 35;
  37. private boolean noAlpha = false;
  38. private boolean overrideOverlayColor = false;
  39. private float mRadius = 0;
  40. private boolean mDirty;
  41. private Bitmap mBitmapToBlur, mBlurredBitmap;
  42. private Canvas mBlurringCanvas;
  43. private RenderScript mRenderScript;
  44. private ScriptIntrinsicBlur mBlurScript;
  45. private Allocation mBlurInput, mBlurOutput;
  46. private boolean mIsRendering;
  47. private final Rect mRectSrc = new Rect(), mRectDst = new Rect();
  48. private View mDecorView;
  49. private boolean mDifferentRoot;
  50. private static int RENDERING_COUNT;
  51. private Paint mPaint;
  52. private RectF mRectF;
  53. public BlurLinearLayout(Context context) {
  54. super(context);
  55. init(context, null);
  56. }
  57. public BlurLinearLayout(Context context, AttributeSet attrs) {
  58. super(context, attrs);
  59. init(context, attrs);
  60. }
  61. public BlurLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  62. super(context, attrs, defStyleAttr);
  63. init(context, attrs);
  64. }
  65. private boolean isInit = false;
  66. private boolean darkMode = false;
  67. Paint cutPaint;
  68. Paint overlayPaint;
  69. private void init(Context context, AttributeSet attrs) {
  70. if (!isInit && context != null) {
  71. TypedArray a = context.obtainStyledAttributes(attrs, com.kongzue.dialogx.R.styleable.RealtimeBlurView);
  72. darkMode = a.getBoolean(com.kongzue.dialogx.R.styleable.RealtimeBlurView_dialogxDarkMode, false);
  73. mBlurRadius = a.getDimension(com.kongzue.dialogx.R.styleable.RealtimeBlurView_realtimeBlurRadius, dip2px(context, 35));
  74. mDownSampleFactor = a.getFloat(com.kongzue.dialogx.R.styleable.RealtimeBlurView_realtimeDownsampleFactor, 4);
  75. mOverlayColor = a.getColor(com.kongzue.dialogx.R.styleable.RealtimeBlurView_realtimeOverlayColor, getResources().getColor(darkMode ? R.color.dialogxIOSBkgDark : R.color.dialogxIOSBkgLight));
  76. mRadius = a.getDimension(com.kongzue.dialogx.R.styleable.RealtimeBlurView_realtimeRadius, dip2px(context, 15));
  77. noAlpha = a.getBoolean(com.kongzue.dialogx.R.styleable.RealtimeBlurView_dialogxOverlayColorNoAlpha, false);
  78. a.recycle();
  79. mPaint = new Paint();
  80. mPaint.setAntiAlias(true);
  81. mRectF = new RectF();
  82. cutPaint = new Paint();
  83. cutPaint.setAntiAlias(true);
  84. cutPaint.setColor(getOverlayColor());
  85. overlayPaint = new Paint();
  86. overlayPaint.setAntiAlias(true);
  87. isInit = true;
  88. if (!isCompatMode()) {
  89. setOutlineProvider(new ViewOutlineProvider() {
  90. @Override
  91. public void getOutline(View view, Outline outline) {
  92. outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mRadius);
  93. }
  94. });
  95. setClipToOutline(true);
  96. }
  97. }
  98. }
  99. private boolean isCompatMode() {
  100. return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
  101. }
  102. public void setBlurRadius(float radius) {
  103. if (mBlurRadius != radius) {
  104. mBlurRadius = radius;
  105. mDirty = true;
  106. invalidate();
  107. }
  108. }
  109. public void setRadiusPx(float r) {
  110. if (mRadius != r) {
  111. mRadius = r;
  112. mDirty = true;
  113. invalidate();
  114. if (!isCompatMode()) {
  115. setOutlineProvider(new ViewOutlineProvider() {
  116. @Override
  117. public void getOutline(View view, Outline outline) {
  118. outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mRadius);
  119. }
  120. });
  121. setClipToOutline(true);
  122. }
  123. }
  124. }
  125. @Override
  126. public void setRadiusPx(Float r) {
  127. if (r != null) {
  128. setRadiusPx((float) r);
  129. }
  130. }
  131. public void setDownsampleFactor(float factor) {
  132. if (factor <= 0) {
  133. throw new IllegalArgumentException("Downsample factor must be greater than 0.");
  134. }
  135. if (mDownSampleFactor != factor) {
  136. mDownSampleFactor = factor;
  137. mDirty = true; // may also change blur radius
  138. releaseBitmap();
  139. invalidate();
  140. }
  141. }
  142. public void setOverlayColor(int color) {
  143. if (mOverlayColor != color) {
  144. mOverlayColor = color;
  145. invalidate();
  146. }
  147. }
  148. @Override
  149. public void setOverlayColor(Integer color) {
  150. if (color != null) {
  151. setOverlayColor((int) color);
  152. }
  153. }
  154. private void releaseBitmap() {
  155. if (mBlurInput != null) {
  156. mBlurInput.destroy();
  157. mBlurInput = null;
  158. }
  159. if (mBlurOutput != null) {
  160. mBlurOutput.destroy();
  161. mBlurOutput = null;
  162. }
  163. if (mBitmapToBlur != null) {
  164. mBitmapToBlur.recycle();
  165. mBitmapToBlur = null;
  166. }
  167. if (mBlurredBitmap != null) {
  168. mBlurredBitmap.recycle();
  169. mBlurredBitmap = null;
  170. }
  171. }
  172. private void releaseScript() {
  173. if (mRenderScript != null) {
  174. mRenderScript.destroy();
  175. mRenderScript = null;
  176. }
  177. if (mBlurScript != null) {
  178. mBlurScript.destroy();
  179. mBlurScript = null;
  180. }
  181. }
  182. protected void release() {
  183. releaseBitmap();
  184. releaseScript();
  185. }
  186. protected boolean prepare() {
  187. if (mBlurRadius == 0 || !isAlive()) {
  188. release();
  189. return false;
  190. }
  191. float downsampleFactor = mDownSampleFactor;
  192. if (mDirty || mRenderScript == null) {
  193. if (supportRenderScript && useBlur) {
  194. if (mRenderScript == null) {
  195. try {
  196. mRenderScript = RenderScript.create(getContext());
  197. mBlurScript = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript));
  198. } catch (Exception e) {
  199. supportRenderScript = false;
  200. if (isDebug()) {
  201. e.printStackTrace();
  202. }
  203. return false;
  204. }
  205. }
  206. mDirty = false;
  207. float radius = mBlurRadius / downsampleFactor;
  208. if (radius > 25) {
  209. downsampleFactor = downsampleFactor * radius / 25;
  210. radius = 25;
  211. }
  212. if (mBlurScript != null) mBlurScript.setRadius(radius);
  213. }
  214. }
  215. final int width = getWidth();
  216. final int height = getHeight();
  217. int scaledWidth = Math.max(1, (int) (width / downsampleFactor));
  218. int scaledHeight = Math.max(1, (int) (height / downsampleFactor));
  219. if (mBlurringCanvas == null || mBlurredBitmap == null || mBlurredBitmap.getWidth() != scaledWidth || mBlurredBitmap.getHeight() != scaledHeight) {
  220. releaseBitmap();
  221. boolean r = false;
  222. try {
  223. mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
  224. if (mBitmapToBlur == null) {
  225. return false;
  226. }
  227. mBlurringCanvas = new Canvas(mBitmapToBlur);
  228. if (!supportRenderScript || !useBlur) {
  229. return true;
  230. }
  231. mBlurInput = Allocation.createFromBitmap(mRenderScript, mBitmapToBlur,
  232. Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT
  233. );
  234. mBlurOutput = Allocation.createTyped(mRenderScript, mBlurInput.getType());
  235. mBlurredBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
  236. if (mBlurredBitmap == null) {
  237. return false;
  238. }
  239. r = true;
  240. } catch (Exception e) {
  241. if (isDebug()) e.printStackTrace();
  242. } finally {
  243. if (!r) {
  244. releaseBitmap();
  245. return false;
  246. }
  247. }
  248. }
  249. return true;
  250. }
  251. private boolean isAlive() {
  252. Context context = getContext();
  253. if (context instanceof Activity) {
  254. return isAttachedToWindow() && !(((Activity) context).isDestroyed());
  255. }
  256. while (context instanceof ContextWrapper) {
  257. if (context instanceof Activity) {
  258. return isAttachedToWindow() && !(((Activity) context).isDestroyed());
  259. }
  260. context = ((ContextWrapper) context).getBaseContext();
  261. }
  262. return isAttachedToWindow() && getContext() != null;
  263. }
  264. protected void blur(Bitmap bitmapToBlur, Bitmap blurredBitmap) {
  265. mBlurInput.copyFrom(bitmapToBlur);
  266. mBlurScript.setInput(mBlurInput);
  267. mBlurScript.forEach(mBlurOutput);
  268. mBlurOutput.copyTo(blurredBitmap);
  269. }
  270. private final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
  271. @Override
  272. public boolean onPreDraw() {
  273. if (!isAlive()) {
  274. destroy();
  275. return false;
  276. }
  277. final int[] locations = new int[2];
  278. Bitmap oldBmp = mBlurredBitmap;
  279. View decor = mDecorView;
  280. if (decor != null && isShown() && prepare()) {
  281. boolean redrawBitmap = mBlurredBitmap != oldBmp;
  282. decor.getLocationOnScreen(locations);
  283. int x = -locations[0];
  284. int y = -locations[1];
  285. getLocationOnScreen(locations);
  286. x += locations[0];
  287. y += locations[1];
  288. // just erase transparent
  289. mBitmapToBlur.eraseColor(getOverlayColor() & 0xffffff);
  290. int rc = mBlurringCanvas.save();
  291. mIsRendering = true;
  292. RENDERING_COUNT++;
  293. try {
  294. mBlurringCanvas.scale(1.f * mBitmapToBlur.getWidth() / getWidth(), 1.f * mBitmapToBlur.getHeight() / getHeight());
  295. mBlurringCanvas.translate(-x, -y);
  296. if (decor.getBackground() != null) {
  297. decor.getBackground().draw(mBlurringCanvas);
  298. }
  299. decor.draw(mBlurringCanvas);
  300. } catch (Exception e) {
  301. if (isDebug()) e.printStackTrace();
  302. } finally {
  303. mIsRendering = false;
  304. RENDERING_COUNT--;
  305. mBlurringCanvas.restoreToCount(rc);
  306. }
  307. try {
  308. blur(mBitmapToBlur, mBlurredBitmap);
  309. } catch (Exception e) {
  310. if (isDebug()) e.printStackTrace();
  311. }
  312. if (redrawBitmap || mDifferentRoot) {
  313. invalidate();
  314. }
  315. }
  316. return true;
  317. }
  318. };
  319. @Override
  320. protected void onAttachedToWindow() {
  321. super.onAttachedToWindow();
  322. Activity activity;
  323. if (getContext() instanceof Activity) {
  324. activity = (Activity) getContext();
  325. } else {
  326. activity = BaseDialog.getTopActivity();
  327. }
  328. ViewGroup decorView = ((ViewGroup) activity.getWindow().getDecorView());
  329. if (decorView.getChildCount() >= 1) {
  330. mDecorView = decorView.getChildAt(0);
  331. }
  332. if (mDecorView != null) {
  333. log("mDecorView is ok.");
  334. mDecorView.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
  335. mDifferentRoot = mDecorView.getRootView() != getRootView();
  336. if (mDifferentRoot) {
  337. mDecorView.postInvalidate();
  338. }
  339. } else {
  340. log("mDecorView is NULL.");
  341. mDifferentRoot = false;
  342. }
  343. }
  344. @Override
  345. protected void onDetachedFromWindow() {
  346. destroy();
  347. super.onDetachedFromWindow();
  348. }
  349. private void destroy() {
  350. if (mDecorView != null) {
  351. mDecorView.getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
  352. }
  353. release();
  354. }
  355. @Override
  356. protected void dispatchDraw(Canvas canvas) {
  357. if (isCompatMode()) {
  358. drawBlurredBitmapCompat(canvas);
  359. } else {
  360. drawBlurredBitmap(canvas, mBlurredBitmap);
  361. }
  362. super.dispatchDraw(canvas);
  363. }
  364. @Override
  365. public void draw(Canvas canvas) {
  366. log("#draw");
  367. if (!useBlur || !supportRenderScript) {
  368. mRectF.right = getWidth();
  369. mRectF.bottom = getHeight();
  370. overlayPaint.setColor(getOverlayColor());
  371. canvas.drawRoundRect(mRectF, mRadius, mRadius, overlayPaint);
  372. } else {
  373. if (!mIsRendering && RENDERING_COUNT <= 0) {
  374. log("draw: ok");
  375. super.draw(canvas);
  376. }
  377. }
  378. }
  379. @Override
  380. protected void onDraw(Canvas canvas) {
  381. if (isCompatMode()) {
  382. drawBlurredBitmapCompat(canvas);
  383. } else {
  384. drawBlurredBitmap(canvas, mBlurredBitmap);
  385. }
  386. super.onDraw(canvas);
  387. }
  388. private void drawBlurredBitmapCompat(Canvas canvas) {
  389. if (getWidth() <= 0 || getHeight() <= 0) return;
  390. if (mBlurredBitmap != null) {
  391. mRectDst.right = getWidth();
  392. mRectDst.bottom = getHeight();
  393. if (getWidth() > 0 && getHeight() > 0) {
  394. Bitmap readyDrawBitmap = getRoundedCornerBitmap(resizeImage(mBlurredBitmap, getWidth(), getHeight()), mRectDst);
  395. if (readyDrawBitmap != null) {
  396. canvas.drawBitmap(readyDrawBitmap, 0, 0, null);
  397. } else {
  398. Bitmap overlyBitmap = drawOverlyColor(Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888));
  399. if (overlyBitmap != null) canvas.drawBitmap(overlyBitmap, 0, 0, null);
  400. }
  401. }
  402. } else {
  403. Bitmap overlyBitmap = drawOverlyColor(Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888));
  404. if (overlyBitmap != null) canvas.drawBitmap(overlyBitmap, 0, 0, null);
  405. }
  406. }
  407. protected void drawBlurredBitmap(Canvas canvas, Bitmap blurredBitmap) {
  408. if (getWidth() <= 0 || getHeight() <= 0) return;
  409. if (blurredBitmap != null) {
  410. mRectSrc.right = blurredBitmap.getWidth();
  411. mRectSrc.bottom = blurredBitmap.getHeight();
  412. mRectDst.right = getWidth();
  413. mRectDst.bottom = getHeight();
  414. canvas.drawBitmap(blurredBitmap, mRectSrc, mRectDst, null);
  415. canvas.drawColor(getOverlayColor());
  416. } else {
  417. Bitmap overlyBitmap = drawOverlyColor(Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888));
  418. if (overlyBitmap != null) canvas.drawBitmap(overlyBitmap, 0, 0, null);
  419. }
  420. }
  421. private Bitmap getRoundedCornerBitmap(Bitmap bitmap, Rect mRectDst) {
  422. bitmap = drawOverlyColor(resizeImage(bitmap, mRectDst.width(), mRectDst.height()));
  423. if (bitmap == null) return null;
  424. Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
  425. Canvas canvas = new Canvas(output);
  426. BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
  427. Paint paint = new Paint();
  428. paint.setAntiAlias(true);
  429. paint.setShader(bitmapShader);
  430. canvas.drawRoundRect(new RectF(mRectDst), mRadius, mRadius, paint);
  431. return output;
  432. }
  433. private Bitmap drawOverlyColor(Bitmap bitmap) {
  434. if (bitmap != null) {
  435. Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
  436. Canvas canvas = new Canvas(output);
  437. Rect originRect = new Rect();
  438. originRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
  439. canvas.drawBitmap(bitmap, originRect, originRect, overlayPaint);
  440. canvas.drawColor(getOverlayColor());
  441. return output;
  442. } else {
  443. return null;
  444. }
  445. }
  446. private Bitmap resizeImage(Bitmap bitmap, int newWidth, int newHeight) {
  447. if (bitmap != null) {
  448. int width = bitmap.getWidth();
  449. int height = bitmap.getHeight();
  450. float scaleWidth = ((float) newWidth) / width;
  451. float scaleHeight = ((float) newHeight) / height;
  452. Matrix matrix = new Matrix();
  453. matrix.postScale(scaleWidth, scaleHeight);
  454. return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
  455. } else {
  456. return null;
  457. }
  458. }
  459. private static boolean supportRenderScript = false;
  460. private boolean useBlur = true;
  461. public boolean isUseBlur() {
  462. return useBlur;
  463. }
  464. public BlurLinearLayout setUseBlur(boolean useBlur) {
  465. this.useBlur = useBlur;
  466. invalidate();
  467. return this;
  468. }
  469. private boolean needRemoveAlphaColor() {
  470. if (overrideOverlayColor) {
  471. return false;
  472. } else {
  473. return noAlpha || !(supportRenderScript && useBlur);
  474. }
  475. }
  476. private static int removeAlphaColor(int color) {
  477. int alpha = 255;
  478. int red = Color.red(color);
  479. int green = Color.green(color);
  480. int blue = Color.blue(color);
  481. return Color.argb(alpha, red, green, blue);
  482. }
  483. private static int replaceAlphaColor(int color, int alpha) {
  484. int red = Color.red(color);
  485. int green = Color.green(color);
  486. int blue = Color.blue(color);
  487. return Color.argb(alpha, red, green, blue);
  488. }
  489. static {
  490. /**
  491. * 之所以需要启动一个新线程检测RenderScript是否可用的原因是不清楚Android什么时候对loadClass做了变更,
  492. * 会直接抛出NoClassDefFoundError无法拦截,在主线程检测会导致程序直接闪退,因此改为异步检测。
  493. * 检测后会给定(boolean)supportRenderScript用于判断时光支持
  494. */
  495. new Thread() {
  496. @Override
  497. public void run() {
  498. try {
  499. BlurLinearLayout.class.getClassLoader().loadClass(RenderScript.class.getCanonicalName());
  500. supportRenderScript = true;
  501. } catch (Throwable e) {
  502. if (isDebug()) {
  503. e.printStackTrace();
  504. }
  505. supportRenderScript = false;
  506. }
  507. }
  508. }.start();
  509. }
  510. public static boolean DEBUGMODE = false;
  511. static boolean isDebug() {
  512. return DEBUGMODE && DialogX.DEBUGMODE;
  513. }
  514. private static void log(Object o) {
  515. if (isDebug()) Log.i(">>>", "DialogX.BlurView: " + o.toString());
  516. }
  517. public static void error(Object o) {
  518. if (isDebug()) Log.e(">>>", o.toString());
  519. }
  520. public BlurLinearLayout setOverrideOverlayColor(boolean overrideOverlayColor) {
  521. log("setOverrideOverlayColor: " + overrideOverlayColor);
  522. this.overrideOverlayColor = overrideOverlayColor;
  523. return this;
  524. }
  525. private int dip2px(Context context, float dpValue) {
  526. final float scale = context.getResources().getDisplayMetrics().density;
  527. return (int) (dpValue * scale + 0.5f);
  528. }
  529. private int getOverlayColor() {
  530. return needRemoveAlphaColor() ? removeAlphaColor(mOverlayColor) : mOverlayColor;
  531. }
  532. }