sharedElementSnapshots) {
48 | ReflowText.setupReflow(new ReflowText.ReflowableTextView(textView));
49 | }
50 | });
51 | }
52 |
53 | @Override
54 | protected void attachBaseContext(Context newBase) {
55 | super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/alimuzaffar/demo/textreflowexample/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.alimuzaffar.demo.textreflowexample.activity;
2 |
3 | import android.app.ActivityOptions;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.view.View;
9 | import android.widget.TextView;
10 |
11 | import com.alimuzaffar.demo.textreflowexample.R;
12 |
13 | import io.plaidapp.ui.transitions.ReflowText;
14 | import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
15 |
16 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_main);
22 | TextView textView = (TextView) findViewById(R.id.txt_main);
23 | textView.setOnClickListener(this);
24 | }
25 |
26 | @Override
27 | public void onClick(View view) {
28 | Intent intent = new Intent(this, Main2Activity.class);
29 | ReflowText.addExtras(intent, new ReflowText.ReflowableTextView((TextView) view));
30 | ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, view, "hello_world");
31 | startActivity(intent, options.toBundle());
32 | }
33 |
34 | @Override
35 | protected void attachBaseContext(Context newBase) {
36 | super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/io/plaidapp/ui/transitions/ReflowText.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.plaidapp.ui.transitions;
18 |
19 | import android.animation.Animator;
20 | import android.animation.AnimatorListenerAdapter;
21 | import android.animation.AnimatorSet;
22 | import android.animation.ObjectAnimator;
23 | import android.animation.PropertyValuesHolder;
24 | import android.content.Context;
25 | import android.content.Intent;
26 | import android.content.res.TypedArray;
27 | import android.graphics.Bitmap;
28 | import android.graphics.Canvas;
29 | import android.graphics.ColorFilter;
30 | import android.graphics.Paint;
31 | import android.graphics.PixelFormat;
32 | import android.graphics.Point;
33 | import android.graphics.PointF;
34 | import android.graphics.Rect;
35 | import android.graphics.drawable.Drawable;
36 | import android.os.Build;
37 | import android.os.Parcel;
38 | import android.os.Parcelable;
39 | import android.support.annotation.ColorInt;
40 | import android.support.annotation.NonNull;
41 | import android.support.annotation.Nullable;
42 | import android.text.Layout;
43 | import android.text.StaticLayout;
44 | import android.text.TextPaint;
45 | import android.text.TextUtils;
46 | import android.transition.Transition;
47 | import android.transition.TransitionValues;
48 | import android.util.AttributeSet;
49 | import android.util.Property;
50 | import android.view.View;
51 | import android.view.ViewGroup;
52 | import android.view.animation.LinearInterpolator;
53 | import android.widget.TextView;
54 |
55 | import com.alimuzaffar.demo.textreflowexample.R;
56 |
57 | import java.util.ArrayList;
58 | import java.util.List;
59 |
60 | import io.plaidapp.util.FontUtil;
61 |
62 | /**
63 | * A transition for repositioning text. This will animate changes in text size and position,
64 | * re-flowing line breaks as necessary.
65 | *
66 | * Strongly recommended to use a curved {@code pathMotion} for a more natural transition.
67 | */
68 | public class ReflowText extends Transition {
69 |
70 | private static final String EXTRA_REFLOW_DATA = "EXTRA_REFLOW_DATA";
71 | private static final String PROPNAME_DATA = "plaid:reflowtext:data";
72 | private static final String PROPNAME_TEXT_SIZE = "plaid:reflowtext:textsize";
73 | private static final String PROPNAME_BOUNDS = "plaid:reflowtext:bounds";
74 | private static final String[] PROPERTIES = {PROPNAME_TEXT_SIZE, PROPNAME_BOUNDS};
75 | private static final int TRANSPARENT = 0;
76 | private static final int OPAQUE = 255;
77 | private static final int OPACITY_MID_TRANSITION = (int) (0.8f * OPAQUE);
78 | private static final float STAGGER_DECAY = 0.8f;
79 |
80 | private int velocity = 700; // pixels per second
81 | private long minDuration = 200; // ms
82 | private long maxDuration = 400; // ms
83 | private long staggerDelay = 40; // ms
84 | private long duration;
85 | // this is hack for preventing view from drawing briefly at the end of the transition :(
86 | private final boolean freezeFrame;
87 |
88 | public ReflowText(Context context, AttributeSet attrs) {
89 | super(context, attrs);
90 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ReflowText);
91 | velocity = a.getDimensionPixelSize(R.styleable.ReflowText_velocity, velocity);
92 | minDuration = a.getInt(R.styleable.ReflowText_minDuration, (int) minDuration);
93 | maxDuration = a.getInt(R.styleable.ReflowText_maxDuration, (int) maxDuration);
94 | staggerDelay = a.getInt(R.styleable.ReflowText_staggerDelay, (int) staggerDelay);
95 | freezeFrame = a.getBoolean(R.styleable.ReflowText_freezeFrame, false);
96 | a.recycle();
97 | }
98 |
99 | /**
100 | * Store data about the view which will participate in a reflow transition in {@code intent}.
101 | */
102 | public static void addExtras(@NonNull Intent intent, @NonNull Reflowable reflowableView) {
103 | intent.putExtra(EXTRA_REFLOW_DATA, new ReflowData(reflowableView));
104 | }
105 |
106 | /**
107 | * Retrieve data about the reflow from {@code intent} and store it for later use.
108 | */
109 | public static void setupReflow(@NonNull Intent intent, @NonNull View view) {
110 | view.setTag(R.id.tag_reflow_data, intent.getParcelableExtra(EXTRA_REFLOW_DATA));
111 | }
112 |
113 | /**
114 | * Create data about the reflow from {@code reflowableView} and store it for later use.
115 | */
116 | public static void setupReflow(@NonNull Reflowable reflowableView) {
117 | reflowableView.getView().setTag(R.id.tag_reflow_data, new ReflowData(reflowableView));
118 | }
119 |
120 | @Override
121 | public void captureStartValues(TransitionValues transitionValues) {
122 | captureValues(transitionValues);
123 | }
124 |
125 | @Override
126 | public void captureEndValues(TransitionValues transitionValues) {
127 | captureValues(transitionValues);
128 | }
129 |
130 | @Override
131 | public String[] getTransitionProperties() {
132 | return PROPERTIES;
133 | }
134 |
135 | @Override
136 | public Animator createAnimator(
137 | ViewGroup sceneRoot,
138 | TransitionValues startValues,
139 | TransitionValues endValues) {
140 |
141 | if (startValues == null || endValues == null) return null;
142 |
143 | final View view = endValues.view;
144 | AnimatorSet transition = new AnimatorSet();
145 | ReflowData startData = (ReflowData) startValues.values.get(PROPNAME_DATA);
146 | ReflowData endData = (ReflowData) endValues.values.get(PROPNAME_DATA);
147 | duration = calculateDuration(startData.bounds, endData.bounds);
148 |
149 | // create layouts & capture a bitmaps of the text in both states
150 | // (with max lines variants where needed)
151 | Layout startLayout = createLayout(startData, sceneRoot.getContext(), false);
152 | Layout endLayout = createLayout(endData, sceneRoot.getContext(), false);
153 | Layout startLayoutMaxLines = null;
154 | Layout endLayoutMaxLines = null;
155 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // StaticLayout maxLines support
156 | if (startData.maxLines != -1) {
157 | startLayoutMaxLines = createLayout(startData, sceneRoot.getContext(), true);
158 | }
159 | if (endData.maxLines != -1) {
160 | endLayoutMaxLines = createLayout(endData, sceneRoot.getContext(), true);
161 | }
162 | }
163 | final Bitmap startText = createBitmap(startData,
164 | startLayoutMaxLines != null ? startLayoutMaxLines : startLayout);
165 | final Bitmap endText = createBitmap(endData,
166 | endLayoutMaxLines != null ? endLayoutMaxLines : endLayout);
167 |
168 | // temporarily turn off clipping so we can draw outside of our bounds don't draw
169 | view.setWillNotDraw(true);
170 | ((ViewGroup) view.getParent()).setClipChildren(false);
171 |
172 | // calculate the runs of text to move together
173 | List runs = getRuns(startData, startLayout, startLayoutMaxLines,
174 | endData, endLayout, endLayoutMaxLines);
175 |
176 | // create animators for moving, scaling and fading each run of text
177 | transition.playTogether(
178 | createRunAnimators(view, startData, endData, startText, endText, runs));
179 |
180 | if (!freezeFrame) {
181 | transition.addListener(new AnimatorListenerAdapter() {
182 | @Override
183 | public void onAnimationEnd(Animator animation) {
184 | // clean up
185 | view.setWillNotDraw(false);
186 | view.getOverlay().clear();
187 | ((ViewGroup) view.getParent()).setClipChildren(true);
188 | startText.recycle();
189 | endText.recycle();
190 | }
191 | });
192 | }
193 | return transition;
194 | }
195 |
196 | @Override
197 | public Transition setDuration(long duration) {
198 | /* don't call super as we want to handle duration ourselves */
199 | return this;
200 | }
201 |
202 | private void captureValues(TransitionValues transitionValues) {
203 | ReflowData reflowData = getReflowData(transitionValues.view);
204 | transitionValues.values.put(PROPNAME_DATA, reflowData);
205 | if (reflowData != null) {
206 | // add these props to the map separately (even though they are captured in the reflow
207 | // data) to use only them to determine whether to create an animation i.e. only
208 | // animate if text size or bounds have changed (see #getTransitionProperties())
209 | transitionValues.values.put(PROPNAME_TEXT_SIZE, reflowData.textSize);
210 | transitionValues.values.put(PROPNAME_BOUNDS, reflowData.bounds);
211 | }
212 | }
213 |
214 | private ReflowData getReflowData(@NonNull View view) {
215 | ReflowData reflowData = (ReflowData) view.getTag(R.id.tag_reflow_data);
216 | if (reflowData != null) {
217 | view.setTag(R.id.tag_reflow_data, null);
218 | return reflowData;
219 | }
220 | return null;
221 | }
222 |
223 | /**
224 | * Calculate the {@linkplain Run}s i.e. diff the start and end states, see where text changes
225 | * line and track the bounds of sections of text that can move together.
226 | *
227 | * If a text block has a max number of lines, consider both with and without this limit applied.
228 | * This allows simulating the correct line breaking as well as calculating the position that
229 | * overflowing text would have been laid out, so that it can animate from/to that position.
230 | */
231 | private List getRuns(@NonNull ReflowData startData, @NonNull Layout startLayout,
232 | @Nullable Layout startLayoutMaxLines, @NonNull ReflowData endData,
233 | @NonNull Layout endLayout, @Nullable Layout endLayoutMaxLines) {
234 | int textLength = endLayout.getText().length();
235 | int currentStartLine = 0;
236 | int currentStartRunLeft = 0;
237 | int currentStartRunTop = 0;
238 | int currentEndLine = 0;
239 | int currentEndRunLeft = 0;
240 | int currentEndRunTop = 0;
241 | List runs = new ArrayList<>(endLayout.getLineCount());
242 |
243 | for (int i = 0; i < textLength; i++) {
244 | // work out which line this letter is on in the start state
245 | int startLine = -1;
246 | boolean startMax = false;
247 | boolean startMaxEllipsis = false;
248 | if (startLayoutMaxLines != null) {
249 | char letter = startLayoutMaxLines.getText().charAt(i);
250 | startMaxEllipsis = letter == '…';
251 | if (letter != '\uFEFF' // beyond max lines
252 | && !startMaxEllipsis) { // ellipsize inserted into layout
253 | startLine = startLayoutMaxLines.getLineForOffset(i);
254 | startMax = true;
255 | }
256 | }
257 | if (!startMax) {
258 | startLine = startLayout.getLineForOffset(i);
259 | }
260 |
261 | // work out which line this letter is on in the end state
262 | int endLine = -1;
263 | boolean endMax = false;
264 | boolean endMaxEllipsis = false;
265 | if (endLayoutMaxLines != null) {
266 | char letter = endLayoutMaxLines.getText().charAt(i);
267 | endMaxEllipsis = letter == '…';
268 | if (letter != '\uFEFF' // beyond max lines
269 | && !endMaxEllipsis) { // ellipsize inserted into layout
270 | endLine = endLayoutMaxLines.getLineForOffset(i);
271 | endMax = true;
272 | }
273 | }
274 | if (!endMax) {
275 | endLine = endLayout.getLineForOffset(i);
276 | }
277 | boolean lastChar = i == textLength - 1;
278 |
279 | if (startLine != currentStartLine
280 | || endLine != currentEndLine
281 | || lastChar) {
282 | // at a run boundary, store bounds in both states
283 | int startRunRight = getRunRight(startLayout, startLayoutMaxLines,
284 | currentStartLine, i, startLine, startMax, startMaxEllipsis, lastChar);
285 | int startRunBottom = startLayout.getLineBottom(currentStartLine);
286 | int endRunRight = getRunRight(endLayout, endLayoutMaxLines, currentEndLine, i,
287 | endLine, endMax, endMaxEllipsis, lastChar);
288 | int endRunBottom = endLayout.getLineBottom(currentEndLine);
289 |
290 | Rect startBound = new Rect(
291 | currentStartRunLeft, currentStartRunTop, startRunRight, startRunBottom);
292 | startBound.offset(startData.textPosition.x, startData.textPosition.y);
293 | Rect endBound = new Rect(
294 | currentEndRunLeft, currentEndRunTop, endRunRight, endRunBottom);
295 | endBound.offset(endData.textPosition.x, endData.textPosition.y);
296 | runs.add(new Run(
297 | startBound,
298 | startMax || startRunBottom <= startData.textHeight,
299 | endBound,
300 | endMax || endRunBottom <= endData.textHeight));
301 | currentStartLine = startLine;
302 | currentStartRunLeft = (int) (startMax ? startLayoutMaxLines
303 | .getPrimaryHorizontal(i) : startLayout.getPrimaryHorizontal(i));
304 | currentStartRunTop = startLayout.getLineTop(startLine);
305 | currentEndLine = endLine;
306 | currentEndRunLeft = (int) (endMax ? endLayoutMaxLines
307 | .getPrimaryHorizontal(i) : endLayout.getPrimaryHorizontal(i));
308 | currentEndRunTop = endLayout.getLineTop(endLine);
309 | }
310 | }
311 | return runs;
312 | }
313 |
314 | /**
315 | * Calculate the right boundary for this run (harder than it sounds). As we're a letter ahead,
316 | * need to grab either current letter start or the end of the previous line. Also need to
317 | * consider maxLines case, which inserts ellipses at the overflow point – don't include these.
318 | */
319 | private int getRunRight(
320 | Layout unrestrictedLayout, Layout maxLinesLayout, int currentLine, int index,
321 | int line, boolean withinMax, boolean isMaxEllipsis, boolean isLastChar) {
322 | int runRight;
323 | if (line != currentLine || isLastChar) {
324 | if (isMaxEllipsis) {
325 | runRight = (int) maxLinesLayout.getPrimaryHorizontal(index);
326 | } else {
327 | runRight = (int) unrestrictedLayout.getLineMax(currentLine);
328 | }
329 | } else {
330 | if (withinMax) {
331 | runRight = (int) maxLinesLayout.getPrimaryHorizontal(index);
332 | } else {
333 | runRight = (int) unrestrictedLayout.getPrimaryHorizontal(index);
334 | }
335 | }
336 | return runRight;
337 | }
338 |
339 | /**
340 | * Create Animators to transition each run of text from start to end position and size.
341 | */
342 | @NonNull
343 | private List createRunAnimators(
344 | View view,
345 | ReflowData startData,
346 | ReflowData endData,
347 | Bitmap startText,
348 | Bitmap endText,
349 | List runs) {
350 | List animators = new ArrayList<>(runs.size());
351 | int dx = startData.bounds.left - endData.bounds.left;
352 | int dy = startData.bounds.top - endData.bounds.top;
353 | long startDelay = 0L;
354 | // move text closest to the destination first i.e. loop forward or backward over the runs
355 | boolean upward = startData.bounds.centerY() > endData.bounds.centerY();
356 | boolean first = true;
357 | boolean lastRightward = true;
358 | LinearInterpolator linearInterpolator = new LinearInterpolator();
359 |
360 | for (int i = upward ? 0 : runs.size() - 1;
361 | ((upward && i < runs.size()) || (!upward && i >= 0));
362 | i += (upward ? 1 : -1)) {
363 | Run run = runs.get(i);
364 |
365 | // skip text runs which aren't visible in either state
366 | if (!run.startVisible && !run.endVisible) continue;
367 |
368 | // create & position the drawable which displays the run; add it to the overlay.
369 | SwitchDrawable drawable = new SwitchDrawable(
370 | startText, run.start, startData.textSize,
371 | endText, run.end, endData.textSize);
372 | drawable.setBounds(
373 | run.start.left + dx,
374 | run.start.top + dy,
375 | run.start.right + dx,
376 | run.start.bottom + dy);
377 | view.getOverlay().add(drawable);
378 |
379 | PropertyValuesHolder topLeft =
380 | PropertyValuesHolder.ofObject(SwitchDrawable.TOP_LEFT, null,
381 | getPathMotion().getPath(
382 | run.start.left + dx,
383 | run.start.top + dy,
384 | run.end.left,
385 | run.end.top));
386 | PropertyValuesHolder width = PropertyValuesHolder.ofInt(
387 | SwitchDrawable.WIDTH, run.start.width(), run.end.width());
388 | PropertyValuesHolder height = PropertyValuesHolder.ofInt(
389 | SwitchDrawable.HEIGHT, run.start.height(), run.end.height());
390 | // the progress property drives the switching behaviour
391 | PropertyValuesHolder progress = PropertyValuesHolder.ofFloat(
392 | SwitchDrawable.PROGRESS, 0f, 1f);
393 | Animator runAnim = ObjectAnimator.ofPropertyValuesHolder(
394 | drawable, topLeft, width, height, progress);
395 |
396 | boolean rightward = run.start.centerX() + dx < run.end.centerX();
397 | if ((run.startVisible && run.endVisible)
398 | && !first && rightward != lastRightward) {
399 | // increase the start delay (by a decreasing amount) for the next run
400 | // (if it's visible throughout) to stagger the movement and try to minimize overlaps
401 | startDelay += staggerDelay;
402 | staggerDelay *= STAGGER_DECAY;
403 | }
404 | lastRightward = rightward;
405 | first = false;
406 |
407 | runAnim.setStartDelay(startDelay);
408 | long animDuration = Math.max(minDuration, duration - (startDelay / 2));
409 | runAnim.setDuration(animDuration);
410 | animators.add(runAnim);
411 |
412 | if (run.startVisible != run.endVisible) {
413 | // if run is appearing/disappearing then fade it in/out
414 | ObjectAnimator fade = ObjectAnimator.ofInt(
415 | drawable,
416 | SwitchDrawable.ALPHA,
417 | run.startVisible ? OPAQUE : TRANSPARENT,
418 | run.endVisible ? OPAQUE : TRANSPARENT);
419 | fade.setDuration((duration + startDelay) / 2);
420 | if (!run.startVisible) {
421 | drawable.setAlpha(TRANSPARENT);
422 | fade.setStartDelay((duration + startDelay) / 2);
423 | } else {
424 | fade.setStartDelay(startDelay);
425 | }
426 | animators.add(fade);
427 | } else {
428 | // slightly fade during transition to minimize movement
429 | ObjectAnimator fade = ObjectAnimator.ofInt(
430 | drawable,
431 | SwitchDrawable.ALPHA,
432 | OPAQUE, OPACITY_MID_TRANSITION, OPAQUE);
433 | fade.setStartDelay(startDelay);
434 | fade.setDuration(duration + startDelay);
435 | fade.setInterpolator(linearInterpolator);
436 | animators.add(fade);
437 | }
438 | }
439 | return animators;
440 | }
441 |
442 | private Layout createLayout(ReflowData data, Context context, boolean enforceMaxLines) {
443 | TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
444 | paint.setTextSize(data.textSize);
445 | paint.setColor(data.textColor);
446 | paint.setLetterSpacing(data.letterSpacing);
447 | if (data.fontName != null) {
448 | paint.setTypeface(FontUtil.get(context, data.fontName));
449 | }
450 |
451 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
452 | StaticLayout.Builder builder = StaticLayout.Builder.obtain(
453 | data.text, 0, data.text.length(), paint, data.textWidth)
454 | .setLineSpacing(data.lineSpacingAdd, data.lineSpacingMult)
455 | .setBreakStrategy(data.breakStrategy);
456 | if (enforceMaxLines && data.maxLines != -1) {
457 | builder.setMaxLines(data.maxLines);
458 | builder.setEllipsize(TextUtils.TruncateAt.END);
459 | }
460 | return builder.build();
461 | } else {
462 | return new StaticLayout(
463 | data.text,
464 | paint,
465 | data.textWidth,
466 | Layout.Alignment.ALIGN_NORMAL,
467 | data.lineSpacingMult,
468 | data.lineSpacingAdd,
469 | true);
470 | }
471 | }
472 |
473 | private Bitmap createBitmap(@NonNull ReflowData data, @NonNull Layout layout) {
474 | Bitmap bitmap = Bitmap.createBitmap(
475 | data.bounds.width(), data.bounds.height(), Bitmap.Config.ARGB_8888);
476 | Canvas canvas = new Canvas(bitmap);
477 | canvas.translate(data.textPosition.x, data.textPosition.y);
478 | layout.draw(canvas);
479 | return bitmap;
480 | }
481 |
482 | /**
483 | * Calculate the duration for the transition depending upon how far the text has to move.
484 | */
485 | private long calculateDuration(@NonNull Rect startPosition, @NonNull Rect endPosition) {
486 | float distance = (float) Math.hypot(
487 | startPosition.exactCenterX() - endPosition.exactCenterX(),
488 | startPosition.exactCenterY() - endPosition.exactCenterY());
489 | long duration = (long) (1000 * (distance / velocity));
490 | return Math.max(minDuration, Math.min(maxDuration, duration));
491 | }
492 |
493 | /**
494 | * Holds all data needed to describe a block of text i.e. to be able to re-create the
495 | * {@link Layout}.
496 | */
497 | private static class ReflowData implements Parcelable {
498 |
499 | final String text;
500 | final float textSize;
501 | final
502 | @ColorInt
503 | int textColor;
504 | final Rect bounds;
505 | final
506 | @Nullable
507 | String fontName;
508 | final float lineSpacingAdd;
509 | final float lineSpacingMult;
510 | final Point textPosition;
511 | final int textHeight;
512 | final int textWidth;
513 | final int breakStrategy;
514 | final float letterSpacing;
515 | final int maxLines;
516 |
517 | ReflowData(@NonNull Reflowable reflowable) {
518 | text = reflowable.getText();
519 | textSize = reflowable.getTextSize();
520 | textColor = reflowable.getTextColor();
521 | fontName = reflowable.getFontName();
522 | final View view = reflowable.getView();
523 | int[] loc = new int[2];
524 | view.getLocationInWindow(loc);
525 | bounds = new Rect(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
526 | textPosition = reflowable.getTextPosition();
527 | textHeight = reflowable.getTextHeight();
528 | lineSpacingAdd = reflowable.getLineSpacingAdd();
529 | lineSpacingMult = reflowable.getLineSpacingMult();
530 | textWidth = reflowable.getTextWidth();
531 | breakStrategy = reflowable.getBreakStrategy();
532 | letterSpacing = reflowable.getLetterSpacing();
533 | maxLines = reflowable.getMaxLines();
534 | }
535 |
536 | ReflowData(Parcel in) {
537 | text = in.readString();
538 | textSize = in.readFloat();
539 | textColor = in.readInt();
540 | bounds = (Rect) in.readValue(Rect.class.getClassLoader());
541 | fontName = in.readString();
542 | lineSpacingAdd = in.readFloat();
543 | lineSpacingMult = in.readFloat();
544 | textPosition = (Point) in.readValue(Point.class.getClassLoader());
545 | textHeight = in.readInt();
546 | textWidth = in.readInt();
547 | breakStrategy = in.readInt();
548 | letterSpacing = in.readFloat();
549 | maxLines = in.readInt();
550 | }
551 |
552 | @Override
553 | public int describeContents() {
554 | return 0;
555 | }
556 |
557 | @Override
558 | public void writeToParcel(Parcel dest, int flags) {
559 | dest.writeString(text);
560 | dest.writeFloat(textSize);
561 | dest.writeInt(textColor);
562 | dest.writeValue(bounds);
563 | dest.writeString(fontName);
564 | dest.writeFloat(lineSpacingAdd);
565 | dest.writeFloat(lineSpacingMult);
566 | dest.writeValue(textPosition);
567 | dest.writeInt(textHeight);
568 | dest.writeInt(textWidth);
569 | dest.writeInt(breakStrategy);
570 | dest.writeFloat(letterSpacing);
571 | dest.writeInt(maxLines);
572 | }
573 |
574 | @SuppressWarnings("unused")
575 | public static final Parcelable.Creator CREATOR
576 | = new Parcelable.Creator() {
577 | @Override
578 | public ReflowData createFromParcel(Parcel in) {
579 | return new ReflowData(in);
580 | }
581 |
582 | @Override
583 | public ReflowData[] newArray(int size) {
584 | return new ReflowData[size];
585 | }
586 | };
587 |
588 | }
589 |
590 | /**
591 | * Models the location of a run of text in both start and end states.
592 | */
593 | private static class Run {
594 |
595 | final Rect start;
596 | final boolean startVisible;
597 | final Rect end;
598 | final boolean endVisible;
599 |
600 | Run(Rect start, boolean startVisible, Rect end, boolean endVisible) {
601 | this.start = start;
602 | this.startVisible = startVisible;
603 | this.end = end;
604 | this.endVisible = endVisible;
605 | }
606 | }
607 |
608 | /**
609 | * A drawable which shows (a portion of) one of two given bitmaps, switching between them once
610 | * a progress property passes a threshold.
611 | *
612 | * This is helpful when animating text size change as small text scaled up is blurry but larger
613 | * text scaled down has different kerning. Instead we use images of both states and switch
614 | * during the transition. We use images as animating text size thrashes the font cache.
615 | */
616 | private static class SwitchDrawable extends Drawable {
617 |
618 | static final Property TOP_LEFT =
619 | new Property(PointF.class, "topLeft") {
620 | @Override
621 | public void set(SwitchDrawable drawable, PointF topLeft) {
622 | drawable.setTopLeft(topLeft);
623 | }
624 |
625 | @Override
626 | public PointF get(SwitchDrawable drawable) {
627 | return drawable.getTopLeft();
628 | }
629 | };
630 |
631 | static final Property WIDTH =
632 | new Property(Integer.class, "width") {
633 | @Override
634 | public void set(SwitchDrawable drawable, Integer width) {
635 | drawable.setWidth(width);
636 | }
637 |
638 | @Override
639 | public Integer get(SwitchDrawable drawable) {
640 | return drawable.getWidth();
641 | }
642 | };
643 |
644 | static final Property HEIGHT =
645 | new Property(Integer.class, "height") {
646 | @Override
647 | public void set(SwitchDrawable drawable, Integer height) {
648 | drawable.setHeight(height);
649 | }
650 |
651 | @Override
652 | public Integer get(SwitchDrawable drawable) {
653 | return drawable.getHeight();
654 | }
655 | };
656 |
657 | static final Property ALPHA =
658 | new Property(Integer.class, "alpha") {
659 | @Override
660 | public void set(SwitchDrawable drawable, Integer alpha) {
661 | drawable.setAlpha(alpha);
662 | }
663 |
664 | @Override
665 | public Integer get(SwitchDrawable drawable) {
666 | return drawable.getAlpha();
667 | }
668 | };
669 |
670 | static final Property PROGRESS =
671 | new Property(Float.class, "progress") {
672 | @Override
673 | public void set(SwitchDrawable drawable, Float progress) {
674 | drawable.setProgress(progress);
675 | }
676 |
677 | @Override
678 | public Float get(SwitchDrawable drawable) {
679 | return 0f;
680 | }
681 | };
682 |
683 | private final Paint paint;
684 | private final float switchThreshold;
685 | private Bitmap currentBitmap;
686 | private final Bitmap endBitmap;
687 | private Rect currentBitmapSrcBounds;
688 | private final Rect endBitmapSrcBounds;
689 | private boolean hasSwitched = false;
690 | private PointF topLeft;
691 | private int width, height;
692 |
693 | SwitchDrawable(
694 | @NonNull Bitmap startBitmap,
695 | @NonNull Rect startBitmapSrcBounds,
696 | float startFontSize,
697 | @NonNull Bitmap endBitmap,
698 | @NonNull Rect endBitmapSrcBounds,
699 | float endFontSize) {
700 | currentBitmap = startBitmap;
701 | currentBitmapSrcBounds = startBitmapSrcBounds;
702 | this.endBitmap = endBitmap;
703 | this.endBitmapSrcBounds = endBitmapSrcBounds;
704 | switchThreshold = startFontSize / (startFontSize + endFontSize);
705 | paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
706 | }
707 |
708 | @Override
709 | public void draw(@NonNull Canvas canvas) {
710 | canvas.drawBitmap(currentBitmap, currentBitmapSrcBounds, getBounds(), paint);
711 | }
712 |
713 | @Override
714 | public int getAlpha() {
715 | return paint.getAlpha();
716 | }
717 |
718 | @Override
719 | public void setAlpha(int alpha) {
720 | paint.setAlpha(alpha);
721 | }
722 |
723 | @Override
724 | public ColorFilter getColorFilter() {
725 | return paint.getColorFilter();
726 | }
727 |
728 | @Override
729 | public void setColorFilter(ColorFilter colorFilter) {
730 | paint.setColorFilter(colorFilter);
731 | }
732 |
733 | @Override
734 | public int getOpacity() {
735 | return PixelFormat.TRANSLUCENT;
736 | }
737 |
738 | void setProgress(float progress) {
739 | if (!hasSwitched && progress >= switchThreshold) {
740 | currentBitmap = endBitmap;
741 | currentBitmapSrcBounds = endBitmapSrcBounds;
742 | hasSwitched = true;
743 | }
744 | }
745 |
746 | PointF getTopLeft() {
747 | return topLeft;
748 | }
749 |
750 | void setTopLeft(PointF topLeft) {
751 | this.topLeft = topLeft;
752 | updateBounds();
753 | }
754 |
755 | int getWidth() {
756 | return width;
757 | }
758 |
759 | void setWidth(int width) {
760 | this.width = width;
761 | updateBounds();
762 | }
763 |
764 | int getHeight() {
765 | return height;
766 | }
767 |
768 | void setHeight(int height) {
769 | this.height = height;
770 | updateBounds();
771 | }
772 |
773 | private void updateBounds() {
774 | int left = Math.round(topLeft.x);
775 | int top = Math.round(topLeft.y);
776 | setBounds(left, top, left + width, top + height);
777 | }
778 | }
779 |
780 | /**
781 | * Interface describing a view which supports re-flowing i.e. it exposes enough information to
782 | * construct a {@link ReflowData} object;
783 | */
784 | public interface Reflowable {
785 |
786 | T getView();
787 |
788 | String getText();
789 |
790 | Point getTextPosition();
791 |
792 | int getTextWidth();
793 |
794 | int getTextHeight();
795 |
796 | float getTextSize();
797 |
798 | @ColorInt
799 | int getTextColor();
800 |
801 | float getLineSpacingAdd();
802 |
803 | float getLineSpacingMult();
804 |
805 | int getBreakStrategy();
806 |
807 | float getLetterSpacing();
808 |
809 | @Nullable
810 | String getFontName();
811 |
812 | int getMaxLines();
813 | }
814 |
815 | /**
816 | * Wraps a {@link TextView} and implements {@link Reflowable}.
817 | */
818 | public static class ReflowableTextView implements Reflowable {
819 |
820 | private final TextView textView;
821 |
822 | public ReflowableTextView(TextView textView) {
823 | this.textView = textView;
824 | }
825 |
826 | @Override
827 | public TextView getView() {
828 | return textView;
829 | }
830 |
831 | @Override
832 | public String getText() {
833 | return textView.getText().toString();
834 | }
835 |
836 | @Override
837 | public Point getTextPosition() {
838 | return new Point(textView.getCompoundPaddingLeft(), textView.getCompoundPaddingTop());
839 | }
840 |
841 | @Override
842 | public int getTextWidth() {
843 | return textView.getWidth()
844 | - textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight();
845 | }
846 |
847 | @Override
848 | public int getTextHeight() {
849 | if (textView.getMaxLines() != -1) {
850 | return (textView.getMaxLines() * textView.getLineHeight()) + 1;
851 | } else {
852 | return textView.getHeight() - textView.getCompoundPaddingTop()
853 | - textView.getCompoundPaddingBottom();
854 | }
855 | }
856 |
857 | @Override
858 | public float getLineSpacingAdd() {
859 | return textView.getLineSpacingExtra();
860 | }
861 |
862 | @Override
863 | public float getLineSpacingMult() {
864 | return textView.getLineSpacingMultiplier();
865 | }
866 |
867 | @Override
868 | public int getBreakStrategy() {
869 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
870 | return textView.getBreakStrategy();
871 | }
872 | return -1;
873 | }
874 |
875 | @Override
876 | public float getLetterSpacing() {
877 | return textView.getLetterSpacing();
878 | }
879 |
880 | @Override
881 | public float getTextSize() {
882 | return textView.getTextSize();
883 | }
884 |
885 | @Override
886 | public int getTextColor() {
887 | return textView.getCurrentTextColor();
888 | }
889 |
890 | @Nullable
891 | @Override
892 | public String getFontName() {
893 | String name = FontUtil.getName(textView.getTypeface());
894 | if (TextUtils.isEmpty(name)) {
895 | return "roboto-mono-regular";
896 | }
897 | return name;
898 | }
899 |
900 | @Override
901 | public int getMaxLines() {
902 | return textView.getMaxLines();
903 | }
904 | }
905 |
906 | }
907 |
--------------------------------------------------------------------------------
/app/src/main/java/io/plaidapp/util/FontUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.plaidapp.util;
18 |
19 | import android.content.Context;
20 | import android.graphics.Typeface;
21 | import android.support.annotation.NonNull;
22 |
23 | import java.util.HashMap;
24 | import java.util.Map;
25 |
26 | /**
27 | * Adapted from github.com/romannurik/muzei/
28 | *
29 | * Also see https://code.google.com/p/android/issues/detail?id=9904
30 | */
31 | public class FontUtil {
32 |
33 | private FontUtil() { }
34 |
35 | private static final Map sTypefaceCache = new HashMap<>();
36 |
37 | public static Typeface get(Context context, String font) {
38 | synchronized (sTypefaceCache) {
39 | if (!sTypefaceCache.containsKey(font)) {
40 | Typeface tf = Typeface.createFromAsset(
41 | context.getApplicationContext().getAssets(), "fonts/" + font + ".ttf");
42 | sTypefaceCache.put(font, tf);
43 | }
44 | return sTypefaceCache.get(font);
45 | }
46 | }
47 |
48 | public static String getName(@NonNull Typeface typeface) {
49 | for (Map.Entry entry : sTypefaceCache.entrySet()) {
50 | if (entry.getValue() == typeface) {
51 | return entry.getKey();
52 | }
53 | }
54 | return null;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main2.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphamu/ReflowTextExample/233f9281478cdbc1a40bc48bdf370557b79d2d10/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphamu/ReflowTextExample/233f9281478cdbc1a40bc48bdf370557b79d2d10/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphamu/ReflowTextExample/233f9281478cdbc1a40bc48bdf370557b79d2d10/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphamu/ReflowTextExample/233f9281478cdbc1a40bc48bdf370557b79d2d10/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphamu/ReflowTextExample/233f9281478cdbc1a40bc48bdf370557b79d2d10/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/transition/reflow_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/transition/reflow_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs_reflow_text.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TextReflowExample
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
14 |
15 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/test/java/com/alimuzaffar/demo/textreflowexample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.alimuzaffar.demo.textreflowexample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.1.3'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | google()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphamu/ReflowTextExample/233f9281478cdbc1a40bc48bdf370557b79d2d10/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jul 28 18:32:45 AEST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------