package fr.greweb.reactnativeviewshot; import android.annotation.TargetApi; import android.app.Activity; import android.content.res.Resources; import android.graphics.Matrix; import android.os.Build; import android.support.annotation.NonNull; import android.support.v4.util.Pair; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.Locale; import java.util.Stack; import javax.annotation.Nullable; import static android.view.View.GONE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; /** * @see Author of the Snippet */ @SuppressWarnings("WeakerAccess") public final class DebugViews { /** * Chunk of the long log line. */ public static final int LOG_MSG_LIMIT = 200; /** * Initial matrix without transformations. */ public static final Matrix EMPTY_MATRIX = new Matrix(); /** * Log long message by chunks * * @param message message to log. */ @SuppressWarnings("UnusedReturnValue") public static int longDebug(@NonNull final String tag, @NonNull final String message) { int counter = 0; String msg = message; while (msg.length() > 0) { final int endOfLine = msg.indexOf("\n"); // -1, 0, 1 final int breakPoint = Math.min(endOfLine < 0 ? LOG_MSG_LIMIT : endOfLine + 1, LOG_MSG_LIMIT); final int last = Math.min(msg.length(), breakPoint); final String out = String.format(Locale.US, "%02d: %s", counter, msg.substring(0, last)); Log.d(tag, out); msg = msg.substring(last); counter++; } return counter; } /** * Print into log activity views hierarchy. */ @NonNull public static String logViewHierarchy(@NonNull final Activity activity) { final View view = activity.findViewById(android.R.id.content); if (null == view) return "Activity [" + activity.getClass().getSimpleName() + "] is not initialized yet. "; return logViewHierarchy(view); } /** * Print into log view hierarchy. */ @NonNull private static String logViewHierarchy(@NonNull final View root) { final StringBuilder output = new StringBuilder(8192).append("\n"); final Resources r = root.getResources(); final Stack> stack = new Stack<>(); stack.push(Pair.create("", root)); while (!stack.empty()) { @NonNull final Pair p = stack.pop(); @NonNull final View v = p.second; @NonNull final String prefix = p.first; final boolean isLastOnLevel = stack.empty() || !prefix.equals(stack.peek().first); final String graphics = "" + prefix + (isLastOnLevel ? "└── " : "├── "); final String className = v.getClass().getSimpleName(); final String line = graphics + className + dumpProperties(r, v); output.append(line).append("\n"); if (v instanceof ViewGroup) { final ViewGroup vg = (ViewGroup) v; for (int i = vg.getChildCount() - 1; i >= 0; i--) { stack.push(Pair.create(prefix + (isLastOnLevel ? " " : "│ "), vg.getChildAt(i))); } } } return output.toString(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @NonNull private static String dumpProperties(@NonNull final Resources r, @NonNull final View v) { final StringBuilder sb = new StringBuilder(); sb.append(" ").append("id=").append(v.getId()).append(resolveIdToName(r, v)); switch (v.getVisibility()) { case VISIBLE: sb.append(", V--"); break; case INVISIBLE: sb.append(", -I-"); break; case GONE: sb.append(", --G"); break; default: sb.append(", ---"); break; } // transformation matrix exists, rotate/scale/skew/translate/ if (!v.getMatrix().equals(EMPTY_MATRIX)) { sb.append(", ").append("matrix=").append(v.getMatrix().toShortString()); if (0.0f != v.getRotation() || 0.0f != v.getRotationX() || 0.0f != v.getRotationY()) { sb.append(", rotate=[") .append(v.getRotation()).append(",") .append(v.getRotationX()).append(",") .append(v.getRotationY()) .append("]"); // print pivote only if its not default if (v.getWidth() / 2 != v.getPivotX() || v.getHeight() / 2 != v.getPivotY()) { sb.append(", pivot=[") .append(v.getPivotX()).append(",") .append(v.getPivotY()) .append("]"); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (0.0f != v.getTranslationX() || 0.0f != v.getTranslationY() || 0.0f != v.getTranslationZ()) { sb.append(", translate=[") .append(v.getTranslationX()).append(",") .append(v.getTranslationY()).append(",") .append(v.getTranslationZ()) .append("]"); } } if (1.0f != v.getScaleX() || 1.0f != v.getScaleY()) { sb.append(", scale=[") .append(v.getScaleX()).append(",") .append(v.getScaleY()) .append("]"); } } // padding's if (0 != v.getPaddingStart() || 0 != v.getPaddingTop() || 0 != v.getPaddingEnd() || 0 != v.getPaddingBottom()) { sb.append(", ") .append("padding=[") .append(v.getPaddingStart()).append(",") .append(v.getPaddingTop()).append(",") .append(v.getPaddingEnd()).append(",") .append(v.getPaddingBottom()) .append("]"); } // margin's final ViewGroup.LayoutParams lp = v.getLayoutParams(); if (lp instanceof ViewGroup.MarginLayoutParams) { final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; if (0 != mlp.leftMargin || 0 != mlp.topMargin || 0 != mlp.rightMargin || 0 != mlp.bottomMargin) { sb.append(", ").append("margin=[") .append(mlp.leftMargin).append(",") .append(mlp.topMargin).append(",") .append(mlp.rightMargin).append(",") .append(mlp.bottomMargin) .append("]"); } } // width, height, size sb.append(", position=[").append(v.getLeft()).append(",").append(v.getTop()).append("]"); sb.append(", size=[").append(v.getWidth()).append(",").append(v.getHeight()).append("]"); // texts if (v instanceof TextView) { final TextView tv = (TextView) v; sb.append(", text=\"").append(tv.getText()).append("\""); } return sb.toString(); } /** * @see Lookup resource name */ @NonNull private static String resolveIdToName(@Nullable final Resources r, @NonNull final View v) { if (null == r) return ""; try { return " / " + r.getResourceEntryName(v.getId()); } catch (Throwable ignored) { return ""; } } }