termux-app
247 строк · 11.3 Кб
1package com.termux.shared.view;
2
3import android.app.Activity;
4import android.content.Context;
5import android.content.ContextWrapper;
6import android.content.res.Configuration;
7import android.graphics.Point;
8import android.graphics.Rect;
9import android.os.Build;
10import android.util.TypedValue;
11import android.view.View;
12import android.view.ViewGroup;
13
14import androidx.annotation.NonNull;
15import androidx.annotation.Nullable;
16import androidx.appcompat.app.ActionBar;
17import androidx.appcompat.app.AppCompatActivity;
18
19import com.termux.shared.logger.Logger;
20
21public class ViewUtils {
22
23/** Log root view events. */
24public static boolean VIEW_UTILS_LOGGING_ENABLED = false;
25
26private static final String LOG_TAG = "ViewUtils";
27
28/**
29* Sets whether view utils logging is enabled or not.
30*
31* @param value The boolean value that defines the state.
32*/
33public static void setIsViewUtilsLoggingEnabled(boolean value) {
34VIEW_UTILS_LOGGING_ENABLED = value;
35}
36
37/**
38* Check if a {@link View} is fully visible and not hidden or partially covered by another view.
39*
40* https://stackoverflow.com/a/51078418/14686958
41*
42* @param view The {@link View} to check.
43* @param statusBarHeight The status bar height received by {@link View.OnApplyWindowInsetsListener}.
44* @return Returns {@code true} if view is fully visible.
45*/
46public static boolean isViewFullyVisible(View view, int statusBarHeight) {
47Rect[] windowAndViewRects = getWindowAndViewRects(view, statusBarHeight);
48if (windowAndViewRects == null)
49return false;
50return windowAndViewRects[0].contains(windowAndViewRects[1]);
51}
52
53/**
54* Get the {@link Rect} of a {@link View} and the {@link Rect} of the window inside which it
55* exists.
56*
57* https://stackoverflow.com/a/51078418/14686958
58*
59* @param view The {@link View} inside the window whose {@link Rect} to get.
60* @param statusBarHeight The status bar height received by {@link View.OnApplyWindowInsetsListener}.
61* @return Returns {@link Rect[]} if view is visible where Rect[0] will contain window
62* {@link Rect} and Rect[1] will contain view {@link Rect}. This will be {@code null}
63* if view is not visible.
64*/
65@Nullable
66public static Rect[] getWindowAndViewRects(View view, int statusBarHeight) {
67if (view == null || !view.isShown())
68return null;
69
70boolean view_utils_logging_enabled = VIEW_UTILS_LOGGING_ENABLED;
71
72// windowRect - will hold available area where content remain visible to users
73// Takes into account screen decorations (e.g. statusbar)
74Rect windowRect = new Rect();
75view.getWindowVisibleDisplayFrame(windowRect);
76
77// If there is actionbar, get his height
78int actionBarHeight = 0;
79boolean isInMultiWindowMode = false;
80Context context = view.getContext();
81if (context instanceof AppCompatActivity) {
82ActionBar actionBar = ((AppCompatActivity) context).getSupportActionBar();
83if (actionBar != null) actionBarHeight = actionBar.getHeight();
84isInMultiWindowMode = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) && ((AppCompatActivity) context).isInMultiWindowMode();
85} else if (context instanceof Activity) {
86android.app.ActionBar actionBar = ((Activity) context).getActionBar();
87if (actionBar != null) actionBarHeight = actionBar.getHeight();
88isInMultiWindowMode = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) && ((Activity) context).isInMultiWindowMode();
89}
90
91int displayOrientation = getDisplayOrientation(context);
92
93// windowAvailableRect - takes into account actionbar and statusbar height
94Rect windowAvailableRect;
95windowAvailableRect = new Rect(windowRect.left, windowRect.top + actionBarHeight, windowRect.right, windowRect.bottom);
96
97// viewRect - holds position of the view in window
98// (methods as getGlobalVisibleRect, getHitRect, getDrawingRect can return different result,
99// when partialy visible)
100Rect viewRect;
101final int[] viewsLocationInWindow = new int[2];
102view.getLocationInWindow(viewsLocationInWindow);
103int viewLeft = viewsLocationInWindow[0];
104int viewTop = viewsLocationInWindow[1];
105
106if (view_utils_logging_enabled) {
107Logger.logVerbose(LOG_TAG, "getWindowAndViewRects:");
108Logger.logVerbose(LOG_TAG, "windowRect: " + toRectString(windowRect) + ", windowAvailableRect: " + toRectString(windowAvailableRect));
109Logger.logVerbose(LOG_TAG, "viewsLocationInWindow: " + toPointString(new Point(viewLeft, viewTop)));
110Logger.logVerbose(LOG_TAG, "activitySize: " + toPointString(getDisplaySize(context, true)) +
111", displaySize: " + toPointString(getDisplaySize(context, false)) +
112", displayOrientation=" + displayOrientation);
113}
114
115if (isInMultiWindowMode) {
116if (displayOrientation == Configuration.ORIENTATION_PORTRAIT) {
117// The windowRect.top of the window at the of split screen mode should start right
118// below the status bar
119if (statusBarHeight != windowRect.top) {
120if (view_utils_logging_enabled)
121Logger.logVerbose(LOG_TAG, "Window top does not equal statusBarHeight " + statusBarHeight + " in multi-window portrait mode. Window is possibly bottom app in split screen mode. Adding windowRect.top " + windowRect.top + " to viewTop.");
122viewTop += windowRect.top;
123} else {
124if (view_utils_logging_enabled)
125Logger.logVerbose(LOG_TAG, "windowRect.top equals statusBarHeight " + statusBarHeight + " in multi-window portrait mode. Window is possibly top app in split screen mode.");
126}
127
128} else if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE) {
129// If window is on the right in landscape mode of split screen, the viewLeft actually
130// starts at windowRect.left instead of 0 returned by getLocationInWindow
131viewLeft += windowRect.left;
132}
133}
134
135int viewRight = viewLeft + view.getWidth();
136int viewBottom = viewTop + view.getHeight();
137viewRect = new Rect(viewLeft, viewTop, viewRight, viewBottom);
138
139if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE && viewRight > windowAvailableRect.right) {
140if (view_utils_logging_enabled)
141Logger.logVerbose(LOG_TAG, "viewRight " + viewRight + " is greater than windowAvailableRect.right " + windowAvailableRect.right + " in landscape mode. Setting windowAvailableRect.right to viewRight since it may not include navbar height.");
142windowAvailableRect.right = viewRight;
143}
144
145return new Rect[]{windowAvailableRect, viewRect};
146}
147
148/**
149* Check if {@link Rect} r2 is above r2. An empty rectangle never contains another rectangle.
150*
151* @param r1 The base rectangle.
152* @param r2 The rectangle being tested that should be above.
153* @return Returns {@code true} if r2 is above r1.
154*/
155public static boolean isRectAbove(@NonNull Rect r1, @NonNull Rect r2) {
156// check for empty first
157return r1.left < r1.right && r1.top < r1.bottom
158// now check if above
159&& r1.left <= r2.left && r1.bottom >= r2.bottom;
160}
161
162/**
163* Get device orientation.
164*
165* Related: https://stackoverflow.com/a/29392593/14686958
166*
167* @param context The {@link Context} to check with.
168* @return {@link Configuration#ORIENTATION_PORTRAIT} or {@link Configuration#ORIENTATION_LANDSCAPE}.
169*/
170public static int getDisplayOrientation(@NonNull Context context) {
171Point size = getDisplaySize(context, false);
172return (size.x < size.y) ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
173}
174
175/**
176* Get device display size.
177*
178* @param context The {@link Context} to check with. It must be {@link Activity} context, otherwise
179* android will throw:
180* `java.lang.IllegalArgumentException: Used non-visual Context to obtain an instance of WindowManager. Please use an Activity or a ContextWrapper around one instead.`
181* @param activitySize The set to {@link true}, then size returned will be that of the activity
182* and can be smaller than physical display size in multi-window mode.
183* @return Returns the display size as {@link Point}.
184*/
185public static Point getDisplaySize( @NonNull Context context, boolean activitySize) {
186// android.view.WindowManager.getDefaultDisplay() and Display.getSize() are deprecated in
187// API 30 and give wrong values in API 30 for activitySize=false in multi-window
188androidx.window.WindowManager windowManager = new androidx.window.WindowManager(context);
189androidx.window.WindowMetrics windowMetrics;
190if (activitySize)
191windowMetrics = windowManager.getCurrentWindowMetrics();
192else
193windowMetrics = windowManager.getMaximumWindowMetrics();
194return new Point(windowMetrics.getBounds().width(), windowMetrics.getBounds().height());
195}
196
197/** Convert {@link Rect} to {@link String}. */
198public static String toRectString(Rect rect) {
199if (rect == null) return "null";
200return "(" + rect.left + "," + rect.top + "), (" + rect.right + "," + rect.bottom + ")";
201}
202
203/** Convert {@link Point} to {@link String}. */
204public static String toPointString(Point point) {
205if (point == null) return "null";
206return "(" + point.x + "," + point.y + ")";
207}
208
209/** Get the {@link Activity} associated with the {@link Context} if available. */
210@Nullable
211public static Activity getActivity(Context context) {
212while (context instanceof ContextWrapper) {
213if (context instanceof Activity) {
214return (Activity)context;
215}
216context = ((ContextWrapper)context).getBaseContext();
217}
218return null;
219}
220
221
222/** Convert value in device independent pixels (dp) to pixels (px) units. */
223public static float dpToPx(Context context, float dp) {
224return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
225}
226
227/** Convert value in pixels (px) to device independent pixels (dp) units. */
228public static float pxToDp(Context context, float px) {
229return px / context.getResources().getDisplayMetrics().density;
230}
231
232
233public static void setLayoutMarginsInDp(@NonNull View view, int left, int top, int right, int bottom) {
234Context context = view.getContext();
235setLayoutMarginsInPixels(view, (int) dpToPx(context, left), (int) dpToPx(context, top),
236(int) dpToPx(context, right), (int) dpToPx(context, bottom));
237}
238
239public static void setLayoutMarginsInPixels(@NonNull View view, int left, int top, int right, int bottom) {
240if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
241ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
242params.setMargins(left, top, right, bottom);
243view.setLayoutParams(params);
244}
245}
246
247}
248