Skip to content

Commit f24f100

Browse files
authored
Merge pull request #178 from OleksandrKucherenko/android-performance-improvements
fixed bug with child views transformation loss
2 parents d626c3b + 2a865fa commit f24f100

File tree

5 files changed

+336
-55
lines changed

5 files changed

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

android/src/main/java/fr/greweb/reactnativeviewshot/RNViewShotModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ public void captureRef(int tag, ReadableMap options, Promise promise) {
100100
scaleWidth, scaleHeight, outputFile, resultStreamFormat,
101101
snapshotContentContainer, reactContext, activity, promise)
102102
);
103-
} catch (final Throwable ignored) {
103+
} catch (final Throwable ex) {
104+
Log.e(RNVIEW_SHOT, "Failed to snapshot view tag " + tag, ex);
104105
promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag " + tag);
105106
}
106107
}

0 commit comments

Comments
 (0)