DebugViews.java 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package fr.greweb.reactnativeviewshot;
  2. import android.annotation.TargetApi;
  3. import android.app.Activity;
  4. import android.content.res.Resources;
  5. import android.graphics.Matrix;
  6. import android.os.Build;
  7. import android.support.annotation.NonNull;
  8. import android.support.v4.util.Pair;
  9. import android.util.Log;
  10. import android.view.View;
  11. import android.view.ViewGroup;
  12. import android.widget.TextView;
  13. import java.util.Locale;
  14. import java.util.Stack;
  15. import javax.annotation.Nullable;
  16. import static android.view.View.GONE;
  17. import static android.view.View.INVISIBLE;
  18. import static android.view.View.VISIBLE;
  19. /**
  20. * @see <a href="https://gist.github.com/OleksandrKucherenko/054b0331ec791edb39db76bd690ecdb2">Author of the Snippet</a>
  21. */
  22. @SuppressWarnings("WeakerAccess")
  23. public final class DebugViews {
  24. /**
  25. * Chunk of the long log line.
  26. */
  27. public static final int LOG_MSG_LIMIT = 200;
  28. /**
  29. * Initial matrix without transformations.
  30. */
  31. public static final Matrix EMPTY_MATRIX = new Matrix();
  32. /**
  33. * Log long message by chunks
  34. *
  35. * @param message message to log.
  36. */
  37. @SuppressWarnings("UnusedReturnValue")
  38. public static int longDebug(@NonNull final String tag, @NonNull final String message) {
  39. int counter = 0;
  40. String msg = message;
  41. while (msg.length() > 0) {
  42. final int endOfLine = msg.indexOf("\n"); // -1, 0, 1
  43. final int breakPoint = Math.min(endOfLine < 0 ? LOG_MSG_LIMIT : endOfLine + 1, LOG_MSG_LIMIT);
  44. final int last = Math.min(msg.length(), breakPoint);
  45. final String out = String.format(Locale.US, "%02d: %s", counter, msg.substring(0, last));
  46. Log.d(tag, out);
  47. msg = msg.substring(last);
  48. counter++;
  49. }
  50. return counter;
  51. }
  52. /**
  53. * Print into log activity views hierarchy.
  54. */
  55. @NonNull
  56. public static String logViewHierarchy(@NonNull final Activity activity) {
  57. final View view = activity.findViewById(android.R.id.content);
  58. if (null == view)
  59. return "Activity [" + activity.getClass().getSimpleName() + "] is not initialized yet. ";
  60. return logViewHierarchy(view);
  61. }
  62. /**
  63. * Print into log view hierarchy.
  64. */
  65. @NonNull
  66. private static String logViewHierarchy(@NonNull final View root) {
  67. final StringBuilder output = new StringBuilder(8192).append("\n");
  68. final Resources r = root.getResources();
  69. final Stack<Pair<String, View>> stack = new Stack<>();
  70. stack.push(Pair.create("", root));
  71. while (!stack.empty()) {
  72. @NonNull final Pair<String, View> p = stack.pop();
  73. @NonNull final View v = p.second;
  74. @NonNull final String prefix = p.first;
  75. final boolean isLastOnLevel = stack.empty() || !prefix.equals(stack.peek().first);
  76. final String graphics = "" + prefix + (isLastOnLevel ? "└── " : "├── ");
  77. final String className = v.getClass().getSimpleName();
  78. final String line = graphics + className + dumpProperties(r, v);
  79. output.append(line).append("\n");
  80. if (v instanceof ViewGroup) {
  81. final ViewGroup vg = (ViewGroup) v;
  82. for (int i = vg.getChildCount() - 1; i >= 0; i--) {
  83. stack.push(Pair.create(prefix + (isLastOnLevel ? " " : "│ "), vg.getChildAt(i)));
  84. }
  85. }
  86. }
  87. return output.toString();
  88. }
  89. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  90. @NonNull
  91. private static String dumpProperties(@NonNull final Resources r, @NonNull final View v) {
  92. final StringBuilder sb = new StringBuilder();
  93. sb.append(" ").append("id=").append(v.getId()).append(resolveIdToName(r, v));
  94. switch (v.getVisibility()) {
  95. case VISIBLE:
  96. sb.append(", V--");
  97. break;
  98. case INVISIBLE:
  99. sb.append(", -I-");
  100. break;
  101. case GONE:
  102. sb.append(", --G");
  103. break;
  104. default:
  105. sb.append(", ---");
  106. break;
  107. }
  108. // transformation matrix exists, rotate/scale/skew/translate/
  109. if (!v.getMatrix().equals(EMPTY_MATRIX)) {
  110. sb.append(", ").append("matrix=").append(v.getMatrix().toShortString());
  111. if (0.0f != v.getRotation() || 0.0f != v.getRotationX() || 0.0f != v.getRotationY()) {
  112. sb.append(", rotate=[")
  113. .append(v.getRotation()).append(",")
  114. .append(v.getRotationX()).append(",")
  115. .append(v.getRotationY())
  116. .append("]");
  117. // print pivote only if its not default
  118. if (v.getWidth() / 2 != v.getPivotX() || v.getHeight() / 2 != v.getPivotY()) {
  119. sb.append(", pivot=[")
  120. .append(v.getPivotX()).append(",")
  121. .append(v.getPivotY())
  122. .append("]");
  123. }
  124. }
  125. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  126. if (0.0f != v.getTranslationX() || 0.0f != v.getTranslationY() || 0.0f != v.getTranslationZ()) {
  127. sb.append(", translate=[")
  128. .append(v.getTranslationX()).append(",")
  129. .append(v.getTranslationY()).append(",")
  130. .append(v.getTranslationZ())
  131. .append("]");
  132. }
  133. }
  134. if (1.0f != v.getScaleX() || 1.0f != v.getScaleY()) {
  135. sb.append(", scale=[")
  136. .append(v.getScaleX()).append(",")
  137. .append(v.getScaleY())
  138. .append("]");
  139. }
  140. }
  141. // padding's
  142. if (0 != v.getPaddingStart() || 0 != v.getPaddingTop() ||
  143. 0 != v.getPaddingEnd() || 0 != v.getPaddingBottom()) {
  144. sb.append(", ")
  145. .append("padding=[")
  146. .append(v.getPaddingStart()).append(",")
  147. .append(v.getPaddingTop()).append(",")
  148. .append(v.getPaddingEnd()).append(",")
  149. .append(v.getPaddingBottom())
  150. .append("]");
  151. }
  152. // margin's
  153. final ViewGroup.LayoutParams lp = v.getLayoutParams();
  154. if (lp instanceof ViewGroup.MarginLayoutParams) {
  155. final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
  156. if (0 != mlp.leftMargin || 0 != mlp.topMargin ||
  157. 0 != mlp.rightMargin || 0 != mlp.bottomMargin) {
  158. sb.append(", ").append("margin=[")
  159. .append(mlp.leftMargin).append(",")
  160. .append(mlp.topMargin).append(",")
  161. .append(mlp.rightMargin).append(",")
  162. .append(mlp.bottomMargin)
  163. .append("]");
  164. }
  165. }
  166. // width, height, size
  167. sb.append(", position=[").append(v.getLeft()).append(",").append(v.getTop()).append("]");
  168. sb.append(", size=[").append(v.getWidth()).append(",").append(v.getHeight()).append("]");
  169. // texts
  170. if (v instanceof TextView) {
  171. final TextView tv = (TextView) v;
  172. sb.append(", text=\"").append(tv.getText()).append("\"");
  173. }
  174. return sb.toString();
  175. }
  176. /**
  177. * @see <a href="https://stackoverflow.com/questions/10137692/how-to-get-resource-name-from-resource-id">Lookup resource name</a>
  178. */
  179. @NonNull
  180. private static String resolveIdToName(@Nullable final Resources r, @NonNull final View v) {
  181. if (null == r) return "";
  182. try {
  183. return " / " + r.getResourceEntryName(v.getId());
  184. } catch (Throwable ignored) {
  185. return "";
  186. }
  187. }
  188. }