355 lines
14 KiB
Java
355 lines
14 KiB
Java
/*
|
|
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
|
*
|
|
* This file is part of RoboSwag library.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
package ru.touchin.roboswag.components.utils;
|
|
|
|
import android.app.Activity;
|
|
import android.app.Application;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.res.Resources;
|
|
import android.os.Build;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.support.annotation.IdRes;
|
|
import android.support.annotation.LayoutRes;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.TypedValue;
|
|
import android.view.Display;
|
|
import android.view.KeyCharacterMap;
|
|
import android.view.KeyEvent;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewConfiguration;
|
|
import android.view.ViewGroup;
|
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
import io.reactivex.functions.Action;
|
|
import io.reactivex.functions.Consumer;
|
|
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
|
|
import ru.touchin.roboswag.core.log.Lc;
|
|
import ru.touchin.roboswag.core.log.LcGroup;
|
|
|
|
/**
|
|
* Created by Gavriil Sitnikov on 13/11/2015.
|
|
* General utilities related to UI (Inflation, Views, Metrics, Activities etc.).
|
|
*/
|
|
public final class UiUtils {
|
|
|
|
/**
|
|
* Logging group to log UI metrics (like inflation or layout time etc.).
|
|
*/
|
|
public static final LcGroup UI_METRICS_LC_GROUP = new LcGroup("UI_METRICS");
|
|
/**
|
|
* Logging group to log UI lifecycle (onCreate, onStart, onResume etc.).
|
|
*/
|
|
public static final LcGroup UI_LIFECYCLE_LC_GROUP = new LcGroup("UI_LIFECYCLE");
|
|
/**
|
|
* Delay to let user view ripple effect before screen changed.
|
|
*/
|
|
public static final long RIPPLE_EFFECT_DELAY = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 150 : 0;
|
|
|
|
private static final Handler RIPPLE_HANDLER = new Handler(Looper.getMainLooper());
|
|
|
|
/**
|
|
* Method to inflate view with right layout parameters based on container and add inflated view as a child to it.
|
|
*
|
|
* @param layoutId Id of layout resource;
|
|
* @param parent Container to rightly resolve layout parameters of view in XML;
|
|
* @return Inflated view.
|
|
*/
|
|
@NonNull
|
|
public static View inflateAndAdd(@LayoutRes final int layoutId, @NonNull final ViewGroup parent) {
|
|
LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, true);
|
|
return parent.getChildAt(parent.getChildCount() - 1);
|
|
}
|
|
|
|
/**
|
|
* Method to inflate view with right layout parameters based on container but not adding inflated view as a child to it.
|
|
*
|
|
* @param layoutId Id of layout resource;
|
|
* @param parent Container to rightly resolve layout parameters of view in XML;
|
|
* @return Inflated view.
|
|
*/
|
|
@NonNull
|
|
public static View inflate(@LayoutRes final int layoutId, @NonNull final ViewGroup parent) {
|
|
return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
|
|
}
|
|
|
|
/**
|
|
* Sets click listener to view. On click it will call something after delay.
|
|
*
|
|
* @param targetView View to set click listener to;
|
|
* @param onClickListener Click listener;
|
|
* @param delay Delay after which click listener will be called.
|
|
*/
|
|
public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action onClickListener, final long delay) {
|
|
setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, delay);
|
|
}
|
|
|
|
/**
|
|
* Sets click listener to view. On click it will call something with {@link #RIPPLE_EFFECT_DELAY}.
|
|
*
|
|
* @param targetView View to set click listener to;
|
|
* @param onClickListener Click listener.
|
|
*/
|
|
public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action onClickListener) {
|
|
setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, RIPPLE_EFFECT_DELAY);
|
|
}
|
|
|
|
/**
|
|
* Sets click listener to view. On click it will call something with {@link #RIPPLE_EFFECT_DELAY}.
|
|
*
|
|
* @param targetView View to set click listener to;
|
|
* @param onClickListener Click listener.
|
|
*/
|
|
public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Consumer<View> onClickListener) {
|
|
setOnRippleClickListener(targetView, onClickListener, RIPPLE_EFFECT_DELAY);
|
|
}
|
|
|
|
/**
|
|
* Sets click listener to view. On click it will call something after delay.
|
|
*
|
|
* @param targetView View to set click listener to;
|
|
* @param onClickListener Click listener;
|
|
* @param delay Delay after which click listener will be called.
|
|
*/
|
|
public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Consumer<View> onClickListener, final long delay) {
|
|
if (onClickListener == null) {
|
|
targetView.setOnClickListener(null);
|
|
return;
|
|
}
|
|
|
|
final Runnable runnable = () -> {
|
|
if (targetView.getWindowVisibility() != View.VISIBLE
|
|
|| !targetView.hasWindowFocus()
|
|
|| (targetView.getContext() instanceof BaseActivity && !((BaseActivity) targetView.getContext()).isActuallyResumed())) {
|
|
return;
|
|
}
|
|
try {
|
|
onClickListener.accept(targetView);
|
|
} catch (final Exception exception) {
|
|
Lc.assertion(exception);
|
|
}
|
|
};
|
|
|
|
targetView.setOnClickListener(v -> {
|
|
RIPPLE_HANDLER.removeCallbacksAndMessages(null);
|
|
RIPPLE_HANDLER.postDelayed(runnable, delay);
|
|
});
|
|
}
|
|
|
|
private UiUtils() {
|
|
}
|
|
|
|
/**
|
|
* Utilities methods related to metrics.
|
|
*/
|
|
public static class OfMetrics {
|
|
|
|
private static final int MAX_METRICS_TRIES_COUNT = 5;
|
|
|
|
/**
|
|
* Returns right metrics with non-zero height/width.
|
|
* It is common bug when metrics are calling at {@link Application#onCreate()} method and it returns metrics with zero height/width.
|
|
*
|
|
* @param context {@link Context} of metrics;
|
|
* @return {@link DisplayMetrics}.
|
|
*/
|
|
@SuppressWarnings("BusyWait")
|
|
@NonNull
|
|
public static DisplayMetrics getDisplayMetrics(@NonNull final Context context) {
|
|
DisplayMetrics result = context.getResources().getDisplayMetrics();
|
|
// it is needed to avoid bug with invalid metrics when user restore application from other application
|
|
int metricsTryNumber = 0;
|
|
while (metricsTryNumber < MAX_METRICS_TRIES_COUNT && (result.heightPixels <= 0 || result.widthPixels <= 0)) {
|
|
try {
|
|
Thread.sleep(500);
|
|
} catch (final InterruptedException ignored) {
|
|
return result;
|
|
}
|
|
result = context.getResources().getDisplayMetrics();
|
|
metricsTryNumber++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Simply converts DP to pixels.
|
|
*
|
|
* @param context {@link Context} of metrics;
|
|
* @param sizeInDp Size in DP;
|
|
* @return Size in pixels.
|
|
*/
|
|
public static float dpToPixels(@NonNull final Context context, final float sizeInDp) {
|
|
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sizeInDp, getDisplayMetrics(context));
|
|
}
|
|
|
|
public static int pixelsToDp(@NonNull final Context context, final int pixels) {
|
|
return (int) (pixels * getDisplayMetrics(context).density + 0.5f);
|
|
}
|
|
|
|
private OfMetrics() {
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Utilities methods related to activities and it'sintents.
|
|
*/
|
|
public static class OfActivities {
|
|
|
|
/**
|
|
* Returns action bar (on top like toolbar or appbar) common height (56dp).
|
|
*
|
|
* @param activity Activity of action bar;
|
|
* @return Height of action bar.
|
|
*/
|
|
public static int getActionBarHeight(@NonNull final Activity activity) {
|
|
return (int) OfMetrics.dpToPixels(activity, 56);
|
|
}
|
|
|
|
/**
|
|
* Returns status bar (on top where system info is) common height.
|
|
*
|
|
* @param activity Activity of status bar;
|
|
* @return Height of status bar.
|
|
*/
|
|
public static int getStatusBarHeight(@NonNull final Activity activity) {
|
|
final int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
|
|
return resourceId > 0 ? activity.getResources().getDimensionPixelSize(resourceId) : 0;
|
|
}
|
|
|
|
/**
|
|
* Returns navigation bar (on bottom where system buttons are) common height.
|
|
* Be aware that some devices have no software keyboard (check it by {@link #hasSoftKeys(Activity)}) but this method will return you
|
|
* size like they are showed.
|
|
*
|
|
* @param activity Activity of navigation bar;
|
|
* @return Height of navigation bar.
|
|
*/
|
|
public static int getNavigationBarHeight(@NonNull final Activity activity) {
|
|
if (hasSoftKeys(activity)) {
|
|
final int resourceId = activity.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
|
|
return resourceId > 0 ? activity.getResources().getDimensionPixelSize(resourceId) : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Returns if device has software keyboard at navigation bar or not.
|
|
*
|
|
* @param activity Activity of navigation bar;
|
|
* @return True if software keyboard is showing at navigation bar.
|
|
*/
|
|
//http://stackoverflow.com/questions/14853039/how-to-tell-whether-an-android-device-has-hard-keys/14871974#14871974
|
|
public static boolean hasSoftKeys(@NonNull final Activity activity) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
final Display display = activity.getWindowManager().getDefaultDisplay();
|
|
|
|
final DisplayMetrics realDisplayMetrics = new DisplayMetrics();
|
|
display.getRealMetrics(realDisplayMetrics);
|
|
|
|
final DisplayMetrics displayMetrics = new DisplayMetrics();
|
|
display.getMetrics(displayMetrics);
|
|
|
|
return (realDisplayMetrics.widthPixels - displayMetrics.widthPixels) > 0
|
|
|| (realDisplayMetrics.heightPixels - displayMetrics.heightPixels) > 0;
|
|
}
|
|
|
|
final boolean hasMenuKey = ViewConfiguration.get(activity).hasPermanentMenuKey();
|
|
final boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
|
|
return !hasMenuKey && !hasBackKey;
|
|
}
|
|
|
|
/**
|
|
* Returns if {@link Intent} could be handled by system.
|
|
*
|
|
* @param context {@link Context} of application;
|
|
* @param intent {@link Intent} to be handled;
|
|
* @return True if there are handlers for {@link Intent} (e.g. browser could handle URI intent).
|
|
*/
|
|
public static boolean isIntentAbleToHandle(@NonNull final Context context, @NonNull final Intent intent) {
|
|
return !context.getPackageManager().queryIntentActivities(intent, 0).isEmpty();
|
|
}
|
|
|
|
private OfActivities() {
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Utilities methods related to views.
|
|
*/
|
|
public static class OfViews {
|
|
|
|
private static final int GENERATED_ID_THRESHOLD = 0x00FFFFFF;
|
|
private static final AtomicInteger NEXT_GENERATED_ID = new AtomicInteger(1);
|
|
|
|
/**
|
|
* Generates unique ID for view. See android {@link View#generateViewId()}.
|
|
*
|
|
* @return Unique ID.
|
|
*/
|
|
@IdRes
|
|
public static int generateViewId() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
return View.generateViewId();
|
|
}
|
|
int result = 0;
|
|
boolean isGenerated = false;
|
|
while (!isGenerated) {
|
|
result = NEXT_GENERATED_ID.get();
|
|
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
|
|
int newValue = result + 1;
|
|
if (newValue > GENERATED_ID_THRESHOLD) {
|
|
newValue = 1; // Roll over to 1, not 0.
|
|
}
|
|
if (NEXT_GENERATED_ID.compareAndSet(result, newValue)) {
|
|
isGenerated = true;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns string representation of {@link View}'s ID.
|
|
*
|
|
* @param view {@link View} to get ID from;
|
|
* @return Readable ID.
|
|
*/
|
|
@NonNull
|
|
public static String getViewIdString(@NonNull final View view) {
|
|
try {
|
|
return view.getResources().getResourceName(view.getId());
|
|
} catch (final Resources.NotFoundException exception) {
|
|
return String.valueOf(view.getId());
|
|
}
|
|
}
|
|
|
|
private OfViews() {
|
|
}
|
|
|
|
}
|
|
|
|
}
|