Skip to content

Commit 8c91abc

Browse files
committed
Update
1 parent f1beb48 commit 8c91abc

3 files changed

Lines changed: 155 additions & 64 deletions

File tree

app/src/main/java/com/omarea/common/ui/BlurEngine.java

Lines changed: 150 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88
import android.content.Context;
99

1010
public final class BlurEngine {
11-
public static BlurController controller = new BlurController();
12-
public static volatile Bitmap blurBitmap;
11+
// Tỉ lệ thu nhỏ và bán kính mờ để cân bằng hiệu năng
12+
private static final float SCALE_FACTOR = 0.15f;
13+
private static final int BLUR_RADIUS = 8;
14+
1315
public static boolean isPaused = false;
14-
15-
public static float DEFAULT_CORNER_RADIUS = 30.0f;
16-
public float cornerRadius = DEFAULT_CORNER_RADIUS;
16+
public float cornerRadius = 30.0f;
1717

1818
private View targetView;
1919
private int[] location = new int[2];
20-
private int[] parentLocation = new int[2];
2120
private Rect srcRect = new Rect();
2221
private Bitmap cachedBitmap;
2322
private Canvas cachedCanvas;
@@ -38,47 +37,54 @@ public void setup() {
3837
targetView.getViewTreeObserver().addOnPreDrawListener(new BlurPreDrawListener(this, targetView));
3938
}
4039

40+
/**
41+
* CẬP NHẬT: Tự động chụp nội dung phía sau View hiện tại
42+
*/
4143
public Bitmap getUpdatedBlurBitmap() {
42-
if (isPaused || blurBitmap == null || blurBitmap.isRecycled() ||
43-
targetView.getWidth() <= 0 || targetView.getHeight() <= 0) {
44+
if (isPaused || targetView.getWidth() <= 0 || targetView.getHeight() <= 0) {
4445
return null;
4546
}
4647

47-
// Lấy RootView thực sự (thường là DecorView của Window)
4848
View rootView = targetView.getRootView();
4949
if (rootView == null) return null;
50-
targetView.getLocationOnScreen(location);
51-
float scaleX = (float) blurBitmap.getWidth() / rootView.getWidth();
52-
float scaleY = (float) blurBitmap.getHeight() / rootView.getHeight();
53-
54-
int w = (int) (targetView.getWidth() * scaleX);
55-
int h = (int) (targetView.getHeight() * scaleY);
56-
int x = (int) (location[0] * scaleX);
57-
int y = (int) (location[1] * scaleY);
58-
59-
// Chống tràn biên Bitmap
60-
x = Math.max(0, Math.min(x, blurBitmap.getWidth() - w));
61-
y = Math.max(0, Math.min(y, blurBitmap.getHeight() - h));
62-
63-
if (w > 0 && h > 0) {
64-
try {
65-
if (cachedBitmap == null || cachedBitmap.getWidth() != w || cachedBitmap.getHeight() != h) {
66-
if (cachedBitmap != null) cachedBitmap.recycle();
67-
cachedBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
68-
cachedCanvas = new Canvas(cachedBitmap);
69-
}
70-
71-
srcRect.set(x, y, x + w, y + h);
72-
cachedCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
73-
74-
cachedCanvas.drawBitmap(blurBitmap, srcRect, new Rect(0, 0, w, h), null);
75-
cachedCanvas.drawColor(getBlurTintColor());
76-
return cachedBitmap;
77-
} catch (Exception e) {
78-
return null;
50+
51+
// 1. Tính toán kích thước thu nhỏ
52+
int w = Math.round(targetView.getWidth() * SCALE_FACTOR);
53+
int h = Math.round(targetView.getHeight() * SCALE_FACTOR);
54+
55+
if (w <= 0 || h <= 0) return null;
56+
57+
try {
58+
// 2. Khởi tạo hoặc tái sử dụng Bitmap đệm
59+
if (cachedBitmap == null || cachedBitmap.getWidth() != w || cachedBitmap.getHeight() != h) {
60+
if (cachedBitmap != null) cachedBitmap.recycle();
61+
cachedBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
62+
cachedCanvas = new Canvas(cachedBitmap);
7963
}
64+
65+
// 3. Xác định vị trí của View trên màn hình
66+
targetView.getLocationOnScreen(location);
67+
68+
// 4. CHỤP NỘI DUNG PHÍA SAU
69+
cachedCanvas.save();
70+
cachedCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
71+
cachedCanvas.scale(SCALE_FACTOR, SCALE_FACTOR);
72+
cachedCanvas.translate(-location[0], -location[1]);
73+
74+
// Tạm ẩn chính nó để không bị hiệu ứng gương (chụp đè chính mình)
75+
targetView.setVisibility(View.INVISIBLE);
76+
rootView.draw(cachedCanvas);
77+
targetView.setVisibility(View.VISIBLE);
78+
cachedCanvas.restore();
79+
80+
// 5. LÀM MỜ & NHUỘM MÀU
81+
fastBlur(cachedBitmap, BLUR_RADIUS);
82+
cachedCanvas.drawColor(getBlurTintColor());
83+
84+
return cachedBitmap;
85+
} catch (Exception e) {
86+
return null;
8087
}
81-
return null;
8288
}
8389

8490
private int getBlurTintColor() {
@@ -99,6 +105,111 @@ public static Paint getStrokePaint(Context context) {
99105
return strokePaint;
100106
}
101107

108+
/**
109+
* Thuật toán StackBlur tích hợp trực tiếp để xử lý tại chỗ
110+
*/
111+
private void fastBlur(Bitmap bitmap, int radius) {
112+
if (radius < 1) return;
113+
int w = bitmap.getWidth();
114+
int h = bitmap.getHeight();
115+
int[] pix = new int[w * h];
116+
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
117+
118+
int wm = w - 1;
119+
int hm = h - 1;
120+
int wh = w * h;
121+
int div = radius + radius + 1;
122+
123+
int[] r = new int[wh];
124+
int[] g = new int[wh];
125+
int[] b = new int[wh];
126+
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
127+
int[] vmin = new int[Math.max(w, h)];
128+
129+
int divsum = (div + 1) >> 1;
130+
divsum *= divsum;
131+
int[] dv = new int[256 * divsum];
132+
for (i = 0; i < 256 * divsum; i++) dv[i] = (i / divsum);
133+
134+
yw = yi = 0;
135+
int[][] stack = new int[div][3];
136+
int stackpointer;
137+
int stackstart;
138+
int[] sir;
139+
int rbs;
140+
int r1 = radius + 1;
141+
int routsum, goutsum, boutsum;
142+
int rinsum, ginsum, binsum;
143+
144+
for (y = 0; y < h; y++) {
145+
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
146+
for (i = -radius; i <= radius; i++) {
147+
p = pix[yi + Math.min(wm, Math.max(i, 0))];
148+
sir = stack[i + radius];
149+
sir[0] = (p & 0xff0000) >> 16;
150+
sir[1] = (p & 0x00ff00) >> 8;
151+
sir[2] = (p & 0x0000ff);
152+
rbs = r1 - Math.abs(i);
153+
rsum += sir[0] * rbs; gsum += sir[1] * rbs; bsum += sir[2] * rbs;
154+
if (i > 0) { rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2]; }
155+
else { routsum += sir[0]; goutsum += sir[1]; boutsum += sir[2]; }
156+
}
157+
stackpointer = radius;
158+
for (x = 0; x < w; x++) {
159+
r[yi] = dv[rsum]; g[yi] = dv[gsum]; b[yi] = dv[bsum];
160+
rsum -= routsum; gsum -= goutsum; bsum -= boutsum;
161+
stackstart = stackpointer - radius + div;
162+
sir = stack[stackstart % div];
163+
routsum -= sir[0]; goutsum -= sir[1]; boutsum -= sir[2];
164+
if (y == 0) vmin[x] = Math.min(x + radius + 1, wm);
165+
p = pix[yw + vmin[x]];
166+
sir[0] = (p & 0xff0000) >> 16; sir[1] = (p & 0x00ff00) >> 8; sir[2] = (p & 0x0000ff);
167+
rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2];
168+
rsum += rinsum; gsum += ginsum; bsum += binsum;
169+
stackpointer = (stackpointer + 1) % div;
170+
sir = stack[stackpointer % div];
171+
routsum += sir[0]; goutsum += sir[1]; boutsum += sir[2];
172+
rinsum -= sir[0]; ginsum -= sir[1]; binsum -= sir[2];
173+
yi++;
174+
}
175+
yw += w;
176+
}
177+
for (x = 0; x < w; x++) {
178+
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
179+
yp = -radius * w;
180+
for (i = -radius; i <= radius; i++) {
181+
yi = Math.max(0, yp) + x;
182+
sir = stack[i + radius];
183+
sir[0] = r[yi]; sir[1] = g[yi]; sir[2] = b[yi];
184+
rbs = r1 - Math.abs(i);
185+
rsum += r[yi] * rbs; gsum += g[yi] * rbs; bsum += b[yi] * rbs;
186+
if (i > 0) { rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2]; }
187+
else { routsum += sir[0]; goutsum += sir[1]; boutsum += sir[2]; }
188+
if (i < hm) yp += w;
189+
}
190+
yi = x;
191+
stackpointer = radius;
192+
for (y = 0; y < h; y++) {
193+
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
194+
rsum -= routsum; gsum -= goutsum; bsum -= boutsum;
195+
stackstart = stackpointer - radius + div;
196+
sir = stack[stackstart % div];
197+
routsum -= sir[0]; goutsum -= sir[1]; boutsum -= sir[2];
198+
if (x == 0) vmin[y] = Math.min(y + r1, hm) * w;
199+
p = x + vmin[y];
200+
sir[0] = r[p]; sir[1] = g[p]; sir[2] = b[p];
201+
rinsum += sir[0]; ginsum += sir[1]; binsum += sir[2];
202+
rsum += rinsum; gsum += ginsum; bsum += binsum;
203+
stackpointer = (stackpointer + 1) % div;
204+
sir = stack[stackpointer];
205+
routsum += sir[0]; goutsum += sir[1]; boutsum += sir[2];
206+
rinsum -= sir[0]; ginsum -= sir[1]; binsum -= sir[2];
207+
yi += w;
208+
}
209+
}
210+
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
211+
}
212+
102213
public void destroy() {
103214
if (cachedBitmap != null && !cachedBitmap.isRecycled()) {
104215
cachedBitmap.recycle();

app/src/main/java/com/omarea/common/ui/BlurPreDrawListener.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public BlurPreDrawListener(BlurEngine engine, View view) {
1414

1515
@Override
1616
public boolean onPreDraw() {
17-
// Chỉ vẽ lại khi View đang hiển thị trên màn hình (tránh lãng phí tài nguyên cho các phần bị khuất trong ScrollView)
18-
if (targetView.isShown() && !BlurEngine.isPaused && BlurEngine.blurBitmap != null) {
17+
// CẬP NHẬT: Không còn phụ thuộc vào BlurEngine.blurBitmap tĩnh
18+
if (targetView.isShown() && !BlurEngine.isPaused) {
1919
targetView.invalidate();
2020
}
2121
return true;

app/src/main/java/com/omarea/common/ui/BlurViewLinearLayout.java

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,21 @@
44
import android.graphics.Bitmap;
55
import android.graphics.Canvas;
66
import android.graphics.Rect;
7-
import android.graphics.RectF;
87
import android.util.AttributeSet;
98
import android.widget.LinearLayout;
109

1110
public class BlurViewLinearLayout extends LinearLayout {
1211
protected BlurEngine engine;
13-
private RectF strokeRect = new RectF();
12+
private android.graphics.RectF strokeRect = new android.graphics.RectF();
1413
private Rect srcRect = new Rect();
1514
private Rect dstRect = new Rect();
1615

1716
public BlurViewLinearLayout(Context context, AttributeSet attrs) {
1817
super(context, attrs);
1918
this.engine = new BlurEngine(this);
20-
// Quan trọng: Phải set false để hệ thống gọi hàm onDraw của ViewGroup
2119
setWillNotDraw(false);
2220
}
2321

24-
public BlurEngine getEngine() {
25-
return engine;
26-
}
27-
2822
@Override
2923
protected void onAttachedToWindow() {
3024
super.onAttachedToWindow();
@@ -33,36 +27,25 @@ protected void onAttachedToWindow() {
3327

3428
@Override
3529
protected void onDraw(Canvas canvas) {
36-
// 1. Vẽ lớp kính mờ (Blur) ngay tại tọa độ hiện tại
3730
if (!BlurEngine.isPaused) {
38-
// Lấy bitmap đã được engine cắt và nhuộm màu sẵn
31+
// Lấy trực tiếp bitmap mờ từ Engine (đã bao gồm capture & blur)
3932
Bitmap blurFragment = engine.getUpdatedBlurBitmap();
4033

4134
if (blurFragment != null && !blurFragment.isRecycled()) {
42-
// Thiết lập vùng nguồn (toàn bộ bitmap đã cắt)
4335
srcRect.set(0, 0, blurFragment.getWidth(), blurFragment.getHeight());
44-
// Thiết lập vùng đích (khớp hoàn toàn với kích thước View hiện tại)
4536
dstRect.set(0, 0, getWidth(), getHeight());
46-
47-
// Vẽ trực tiếp lên canvas trước khi vẽ các View con
4837
canvas.drawBitmap(blurFragment, srcRect, dstRect, null);
4938
}
5039
}
5140

52-
// 2. Vẽ nội dung của View (super.onDraw sẽ vẽ TabLayout, chữ, icon...)
5341
super.onDraw(canvas);
54-
55-
// 3. Vẽ viền (Stroke) lên trên cùng
5642
drawStroke(canvas);
5743
}
5844

5945
protected void drawStroke(Canvas canvas) {
60-
// Sử dụng paint tĩnh từ engine để tối ưu
6146
android.graphics.Paint paint = BlurEngine.getStrokePaint(getContext());
6247
float radius = engine.cornerRadius;
6348
float strokeWidth = paint.getStrokeWidth();
64-
65-
// Inset nửa độ dày viền để nét vẽ nằm trọn bên trong biên View
6649
float inset = strokeWidth / 2f;
6750
strokeRect.set(inset, inset, getWidth() - inset, getHeight() - inset);
6851

@@ -77,9 +60,6 @@ protected void drawStroke(Canvas canvas) {
7760
@Override
7861
protected void onDetachedFromWindow() {
7962
super.onDetachedFromWindow();
80-
// Dọn dẹp bộ đệm bitmap để tránh rò rỉ bộ nhớ
81-
if (engine != null) {
82-
engine.destroy();
83-
}
63+
if (engine != null) engine.destroy();
8464
}
8565
}

0 commit comments

Comments
 (0)