diff --git a/app/src/main/java/com/hippo/ehviewer/widget/GalleryHeader.java b/app/src/main/java/com/hippo/ehviewer/widget/GalleryHeader.java index 16906ffbe..d8a855307 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/GalleryHeader.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/GalleryHeader.java @@ -20,21 +20,30 @@ import android.content.Context; import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; -import android.util.SparseIntArray; import android.view.DisplayCutout; import android.view.View; -import android.widget.FrameLayout; -import androidx.annotation.NonNull; +import android.view.ViewGroup; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import com.hippo.ehviewer.R; +import com.hippo.yorozuya.ObjectUtils; -// Offset widgets to avoid notch -public class GalleryHeader extends FrameLayout { +public class GalleryHeader extends ViewGroup { + + private DisplayCutout displayCutout; + + private View battery; + private View progress; + private View clock; + + private Rect batteryRect = new Rect(); + private Rect progressRect = new Rect(); + private Rect clockRect = new Rect(); - private SparseIntArray originalTopMargins = new SparseIntArray(); - private DisplayCutout displayCutout = null; private int[] location = new int[2]; - private Rect rect = new Rect(); + + private int lastX = 0; + private int lastY = 0; public GalleryHeader(Context context, AttributeSet attrs) { super(context, attrs); @@ -42,67 +51,157 @@ public class GalleryHeader extends FrameLayout { @RequiresApi(api = Build.VERSION_CODES.P) public void setDisplayCutout(@Nullable DisplayCutout displayCutout) { - // Backup top margins - if (originalTopMargins.size() == 0) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - originalTopMargins.append(child.getId(), ((MarginLayoutParams) child.getLayoutParams()).topMargin); - } - } - - if (displayCutout != null) { - // Update top margins - if (getWidth() == 0 && getHeight() == 0) { - this.displayCutout = displayCutout; - } else { - updateTopMargins(displayCutout); - } - } else if (originalTopMargins.size() != 0) { - // Reset - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - ((MarginLayoutParams) child.getLayoutParams()).topMargin = originalTopMargins.get(child.getId()); - } - // Layout + if (!ObjectUtils.equal(this.displayCutout, displayCutout)) { + this.displayCutout = displayCutout; requestLayout(); } } @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); + protected void onFinishInflate() { + super.onFinishInflate(); - if (displayCutout != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - updateTopMargins(displayCutout); - displayCutout = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + battery = findViewById(R.id.battery); + progress = findViewById(R.id.progress); + clock = findViewById(R.id.clock); + } + } + + private void measureChild(Rect rect, View view, int width, int paddingLeft, int paddingRight) { + int left; + MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams(); + if (view == battery) { + left = paddingLeft + lp.leftMargin; + } else if (view == progress) { + left = paddingLeft + (width - paddingLeft - paddingRight) / 2 - view.getMeasuredWidth() / 2; + } else { + left = width - paddingRight - lp.rightMargin - view.getMeasuredWidth(); + } + rect.set(left, lp.topMargin, left + view.getMeasuredWidth(), lp.topMargin + view.getMeasuredHeight()); + } + + @RequiresApi(api = Build.VERSION_CODES.P) + private boolean offsetVertically(Rect rect, View view, int width) { + int offset = 0; + + measureChild(rect, view, width, 0, 0); + rect.offset(lastX, lastY); + + for (Rect notch : displayCutout.getBoundingRects()) { + if (Rect.intersects(notch, rect)) { + offset = Math.max(offset, notch.bottom - lastY); + } + } + + if (offset != 0) { + rect.offset(-lastX, -lastY); + rect.offset(0, offset); + return true; + } else { + return false; } } @RequiresApi(api = Build.VERSION_CODES.P) - private void updateTopMargins(@NonNull DisplayCutout displayCutout) { - getLocationOnScreen(location); - int y = location[1]; + private int getOffsetLeft(Rect rect, View view, int width) { + int offset = 0; + measureChild(rect, view, width, 0, 0); + rect.offset(lastX, lastY); + + for (Rect notch : displayCutout.getBoundingRects()) { + if (Rect.intersects(notch, rect)) { + offset = Math.max(offset, notch.right - lastX); + } + } + + return offset; + } + + @RequiresApi(api = Build.VERSION_CODES.P) + private int getOffsetRight(Rect rect, View view, int width) { + int offset = 0; + + measureChild(rect, view, width, 0, 0); + rect.offset(lastX, lastY); + + for (Rect notch : displayCutout.getBoundingRects()) { + if (Rect.intersects(notch, rect)) { + offset = Math.max(offset, lastX + width - notch.left); + } + } + + return offset; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { + throw new IllegalStateException(); + } + int width = MeasureSpec.getSize(widthMeasureSpec); + + int height = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); - child.getLocationOnScreen(location); + measureChild(child, widthMeasureSpec, heightMeasureSpec); + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + height = Math.max(height, child.getMeasuredHeight() + lp.topMargin); + } - // Fix top margin - boolean intersected = false; - for (Rect notch : displayCutout.getBoundingRects()) { - rect.set(location[0], location[1], location[0] + child.getWidth(), location[1] + child.getHeight()); - if (Rect.intersects(notch, rect)) { - intersected = true; - ((MarginLayoutParams) child.getLayoutParams()).topMargin = originalTopMargins.get(child.getId()) + (notch.bottom - y); - break; - } - } - if (!intersected) { - ((MarginLayoutParams) child.getLayoutParams()).topMargin = originalTopMargins.get(child.getId()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && displayCutout != null) { + // Check progress covered + if (offsetVertically(progressRect, progress, width)) { + offsetVertically(batteryRect, battery, width); + offsetVertically(clockRect, clock, width); + height = Math.max(progressRect.bottom, Math.max(batteryRect.bottom, clockRect.bottom)); + } else { + // Clamp left and right + int paddingLeft = getOffsetLeft(batteryRect, battery, width); + int paddingRight = getOffsetRight(clockRect, clock, width); + measureChild(batteryRect, battery, width, paddingLeft, paddingRight); + measureChild(progressRect, progress, width, paddingLeft, paddingRight); + measureChild(clockRect, clock, width, paddingLeft, paddingRight); } + } else { + measureChild(batteryRect, battery, width, 0, 0); + measureChild(progressRect, progress, width, 0, 0); + measureChild(clockRect, clock, width, 0, 0); + } - // Layout + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + battery.layout(batteryRect.left, batteryRect.top, batteryRect.right, batteryRect.bottom); + progress.layout(progressRect.left, progressRect.top, progressRect.right, progressRect.bottom); + clock.layout(clockRect.left, clockRect.top, clockRect.right, clockRect.bottom); + + getLocationOnScreen(location); + if (lastX != location[0] || lastY != location[1]) { + lastX = location[0]; + lastY = location[1]; requestLayout(); } } + + @Override + public MarginLayoutParams generateLayoutParams(AttributeSet attrs) { + return new MarginLayoutParams(getContext(), attrs); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof MarginLayoutParams; + } + + @Override + protected MarginLayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { + if (lp instanceof MarginLayoutParams) { + return new MarginLayoutParams((MarginLayoutParams) lp); + } + return new MarginLayoutParams(lp); + } }