Skip to content

Instantly share code, notes, and snippets.

@jaredsburrows
Created January 2, 2016 04:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaredsburrows/8611f160db41b2336ee9 to your computer and use it in GitHub Desktop.
Save jaredsburrows/8611f160db41b2336ee9 to your computer and use it in GitHub Desktop.
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
/**
* Utility functions for common Android UI tasks.
* This class is not supposed to be instantiated.
*/
public class UiUtils {
private static final String TAG = "UiUtils";
/**
* Guards this class from being instantiated.
*/
private UiUtils() {
}
/** The minimum size of the bottom margin below the app to detect a keyboard. */
private static final float KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP = 100;
/** A delegate that allows disabling keyboard visibility detection. */
private static KeyboardShowingDelegate sKeyboardShowingDelegate;
/**
* A delegate that can be implemented to override whether or not keyboard detection will be
* used.
*/
public interface KeyboardShowingDelegate {
/**
* Will be called to determine whether or not to detect if the keyboard is visible.
* @param context A {@link Context} instance.
* @param view A {@link View}.
* @return Whether or not the keyboard check should be disabled.
*/
boolean disableKeyboardCheck(Context context, View view);
}
/**
* Allows setting a delegate to override the default software keyboard visibility detection.
* @param delegate A {@link KeyboardShowingDelegate} instance.
*/
public static void setKeyboardShowingDelegate(KeyboardShowingDelegate delegate) {
sKeyboardShowingDelegate = delegate;
}
/**
* Shows the software keyboard if necessary.
* @param view The currently focused {@link View}, which would receive soft keyboard input.
*/
public static void showKeyboard(View view) {
InputMethodManager imm =
(InputMethodManager) view.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
// Only shows soft keyboard if there isn't an open physical keyboard.
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
/**
* Hides the keyboard.
* @param view The {@link View} that is currently accepting input.
* @return Whether the keyboard was visible before.
*/
public static boolean hideKeyboard(View view) {
InputMethodManager imm =
(InputMethodManager) view.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
return imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
/**
* Detects whether or not the keyboard is showing. This is a best guess as there is no
* standardized/foolproof way to do this.
* @param context A {@link Context} instance.
* @param view A {@link View}.
* @return Whether or not the software keyboard is visible and taking up screen space.
*/
public static boolean isKeyboardShowing(Context context, View view) {
if (sKeyboardShowingDelegate != null
&& sKeyboardShowingDelegate.disableKeyboardCheck(context, view)) {
return false;
}
View rootView = view.getRootView();
if (rootView == null) return false;
Rect appRect = new Rect();
rootView.getWindowVisibleDisplayFrame(appRect);
final float density = context.getResources().getDisplayMetrics().density;
final float bottomMarginDp = Math.abs(rootView.getHeight() - appRect.height()) / density;
return bottomMarginDp > KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP;
}
/**
* Inserts a {@link View} into a {@link ViewGroup} after directly before a given {@View}.
* @param container The {@link View} to add newView to.
* @param newView The new {@link View} to add.
* @param existingView The {@link View} to insert the newView before.
* @return The index where newView was inserted, or -1 if it was not inserted.
*/
public static int insertBefore(ViewGroup container, View newView, View existingView) {
return insertView(container, newView, existingView, false);
}
/**
* Inserts a {@link View} into a {@link ViewGroup} after directly after a given {@View}.
* @param container The {@link View} to add newView to.
* @param newView The new {@link View} to add.
* @param existingView The {@link View} to insert the newView after.
* @return The index where newView was inserted, or -1 if it was not inserted.
*/
public static int insertAfter(ViewGroup container, View newView, View existingView) {
return insertView(container, newView, existingView, true);
}
private static int insertView(
ViewGroup container, View newView, View existingView, boolean after) {
// See if the view has already been added.
int index = container.indexOfChild(newView);
if (index >= 0) return index;
// Find the location of the existing view.
index = container.indexOfChild(existingView);
if (index < 0) return -1;
// Add the view.
if (after) index++;
container.addView(newView, index);
return index;
}
/**
* Generates a scaled screenshot of the given view. The maximum size of the screenshot is
* determined by maximumDimension.
*
* @param currentView The view to generate a screenshot of.
* @param maximumDimension The maximum width or height of the generated screenshot. The bitmap
* will be scaled to ensure the maximum width or height is equal to or
* less than this. Any value <= 0, will result in no scaling.
* @param bitmapConfig Bitmap config for the generated screenshot (ARGB_8888 or RGB_565).
* @return The screen bitmap of the view or null if a problem was encountered.
*/
public static Bitmap generateScaledScreenshot(
View currentView, int maximumDimension, Bitmap.Config bitmapConfig) {
Bitmap screenshot = null;
boolean drawingCacheEnabled = currentView.isDrawingCacheEnabled();
try {
prepareViewHierarchyForScreenshot(currentView, true);
if (!drawingCacheEnabled) currentView.setDrawingCacheEnabled(true);
// Android has a maximum drawing cache size and if the drawing cache is bigger
// than that, getDrawingCache() returns null.
Bitmap originalBitmap = currentView.getDrawingCache();
if (originalBitmap != null) {
double originalHeight = originalBitmap.getHeight();
double originalWidth = originalBitmap.getWidth();
int newWidth = (int) originalWidth;
int newHeight = (int) originalHeight;
if (maximumDimension > 0) {
double scale = maximumDimension / Math.max(originalWidth, originalHeight);
newWidth = (int) Math.round(originalWidth * scale);
newHeight = (int) Math.round(originalHeight * scale);
}
Bitmap scaledScreenshot =
Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true);
if (scaledScreenshot.getConfig() != bitmapConfig) {
screenshot = scaledScreenshot.copy(bitmapConfig, false);
scaledScreenshot.recycle();
scaledScreenshot = null;
} else {
screenshot = scaledScreenshot;
}
} else if (currentView.getMeasuredHeight() > 0 && currentView.getMeasuredWidth() > 0) {
double originalHeight = currentView.getMeasuredHeight();
double originalWidth = currentView.getMeasuredWidth();
int newWidth = (int) originalWidth;
int newHeight = (int) originalHeight;
if (maximumDimension > 0) {
double scale = maximumDimension / Math.max(originalWidth, originalHeight);
newWidth = (int) Math.round(originalWidth * scale);
newHeight = (int) Math.round(originalHeight * scale);
}
Bitmap bitmap = Bitmap.createBitmap(newWidth, newHeight, bitmapConfig);
Canvas canvas = new Canvas(bitmap);
canvas.scale((float) (newWidth / originalWidth),
(float) (newHeight / originalHeight));
currentView.draw(canvas);
screenshot = bitmap;
}
} catch (OutOfMemoryError e) {
Log.d(TAG, "Unable to capture screenshot and scale it down." + e.getMessage());
} finally {
if (!drawingCacheEnabled) currentView.setDrawingCacheEnabled(false);
prepareViewHierarchyForScreenshot(currentView, false);
}
return screenshot;
}
private static void prepareViewHierarchyForScreenshot(View view, boolean takingScreenshot) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
prepareViewHierarchyForScreenshot(viewGroup.getChildAt(i), takingScreenshot);
}
} else if (view instanceof SurfaceView) {
view.setWillNotDraw(!takingScreenshot);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment