public void expand(boolean animate) { if (!truncated) { return; } truncated = false; if (!animate) { requestLayout(); return; } ResizeAnimation expandAnimation = new ResizeAnimation(getHeight() + expandedLayout.getHeight() - truncatedLayout.getHeight()); expandAnimation.setDuration(400); expandAnimation.setAnimationListener( new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { animating = true; } @Override public void onAnimationEnd(Animation animation) { animating = false; } @Override public void onAnimationRepeat(Animation animation) {} }); startAnimation(expandAnimation); }
@SuppressWarnings("NullableProblems") @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int) event.getX(); int y = (int) event.getY(); x += getScrollX(); y += getScrollY(); boolean retValue = false; switch (action) { case MotionEvent.ACTION_DOWN: retValue = selectBubble(x, y); break; case MotionEvent.ACTION_MOVE: if (selectedSpan != null) { selectedSpan.setPressed(spanContains(selectedSpan, x, y), (Spannable) layout().getText()); retValue = true; } break; case MotionEvent.ACTION_UP: if (listener != null && selectedSpan != null && spanContains(selectedSpan, x, y)) { listener.onBubbleClicked(this, selectedSpan); } case MotionEvent.ACTION_CANCEL: if (selectedSpan != null) { retValue = true; if (-1 != ((Spannable) truncatedLayout.getText()).getSpanEnd(selectedSpan)) { selectedSpan.setPressed(false, (Spannable) truncatedLayout.getText()); } if (-1 != ((Spannable) expandedLayout.getText()).getSpanEnd(selectedSpan)) { selectedSpan.setPressed(false, (Spannable) expandedLayout.getText()); } } selectedSpan = null; break; default: break; } invalidate(); return retValue; }
private void reflow(CharSequence s, int where, int before, int after) { if (s != mBase) return; CharSequence text = mDisplay; int len = text.length(); // seek back to the start of the paragraph int find = TextUtils.lastIndexOf(text, '\n', where - 1); if (find < 0) find = 0; else find = find + 1; { int diff = where - find; before += diff; after += diff; where -= diff; } // seek forward to the end of the paragraph int look = TextUtils.indexOf(text, '\n', where + after); if (look < 0) look = len; else look++; // we want the index after the \n int change = look - (where + after); before += change; after += change; // seek further out to cover anything that is forced to wrap together if (text instanceof Spanned) { Spanned sp = (Spanned) text; boolean again; do { again = false; Object[] force = sp.getSpans(where, where + after, WrapTogetherSpan.class); for (int i = 0; i < force.length; i++) { int st = sp.getSpanStart(force[i]); int en = sp.getSpanEnd(force[i]); if (st < where) { again = true; int diff = where - st; before += diff; after += diff; where -= diff; } if (en > where + after) { again = true; int diff = en - (where + after); before += diff; after += diff; } } } while (again); } // find affected region of old layout int startline = getLineForOffset(where); int startv = getLineTop(startline); int endline = getLineForOffset(where + before); if (where + after == len) endline = getLineCount(); int endv = getLineTop(endline); boolean islast = (endline == getLineCount()); // generate new layout for affected text StaticLayout reflowed; synchronized (sLock) { reflowed = sStaticLayout; sStaticLayout = null; } if (reflowed == null) { reflowed = new StaticLayout(null); } else { reflowed.prepare(); } reflowed.generate( text, where, where + after, getPaint(), getWidth(), getTextDirectionHeuristic(), getSpacingMultiplier(), getSpacingAdd(), false, true, mEllipsizedWidth, mEllipsizeAt); int n = reflowed.getLineCount(); // If the new layout has a blank line at the end, but it is not // the very end of the buffer, then we already have a line that // starts there, so disregard the blank line. if (where + after != len && reflowed.getLineStart(n - 1) == where + after) n--; // remove affected lines from old layout mInts.deleteAt(startline, endline - startline); mObjects.deleteAt(startline, endline - startline); // adjust offsets in layout for new height and offsets int ht = reflowed.getLineTop(n); int toppad = 0, botpad = 0; if (mIncludePad && startline == 0) { toppad = reflowed.getTopPadding(); mTopPadding = toppad; ht -= toppad; } if (mIncludePad && islast) { botpad = reflowed.getBottomPadding(); mBottomPadding = botpad; ht += botpad; } mInts.adjustValuesBelow(startline, START, after - before); mInts.adjustValuesBelow(startline, TOP, startv - endv + ht); // insert new layout int[] ints; if (mEllipsize) { ints = new int[COLUMNS_ELLIPSIZE]; ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED; } else { ints = new int[COLUMNS_NORMAL]; } Directions[] objects = new Directions[1]; for (int i = 0; i < n; i++) { ints[START] = reflowed.getLineStart(i) | (reflowed.getParagraphDirection(i) << DIR_SHIFT) | (reflowed.getLineContainsTab(i) ? TAB_MASK : 0); int top = reflowed.getLineTop(i) + startv; if (i > 0) top -= toppad; ints[TOP] = top; int desc = reflowed.getLineDescent(i); if (i == n - 1) desc += botpad; ints[DESCENT] = desc; objects[0] = reflowed.getLineDirections(i); if (mEllipsize) { ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i); ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i); } mInts.insertAt(startline + i, ints); mObjects.insertAt(startline + i, objects); } synchronized (sLock) { sStaticLayout = reflowed; reflowed.finish(); } }
@Override public BasicElement getElement(final Environment env, final ElementMap item) { this.env = env; this.elementMaps.push(item); final ElementMap elementMap = this.elementMaps.peek(); final String id = "popup"; final PopupElement element = new PopupElement(); element.onCheckOut(); element.setElementMap(elementMap); if (elementMap != null && id != null) { elementMap.add(id, element); } element.setAlign(Alignment9.NORTH_EAST); element.setHotSpotPosition(Alignment9.SOUTH_WEST); element.setHideOnClick(false); element.onAttributesInitialized(); final StaticLayoutData element2 = new StaticLayoutData(); element2.onCheckOut(); element2.setElementMap(elementMap); element2.setSize(new Dimension(-2, -2)); element.addBasicElement(element2); element2.onAttributesInitialized(); element2.onChildrenAdded(); final Container checkOut = Container.checkOut(); checkOut.setElementMap(elementMap); element.addBasicElement(checkOut); checkOut.onAttributesInitialized(); final StaticLayout element3 = new StaticLayout(); element3.onCheckOut(); element3.setAdaptToContentSize(true); checkOut.addBasicElement(element3); element3.onAttributesInitialized(); element3.onChildrenAdded(); final String id2 = "container"; final Container checkOut2 = Container.checkOut(); checkOut2.setElementMap(elementMap); if (elementMap != null && id2 != null) { elementMap.add(id2, checkOut2); } checkOut2.setStyle("chatBubble"); checkOut.addBasicElement(checkOut2); checkOut2.onAttributesInitialized(); final StaticLayoutData element4 = new StaticLayoutData(); element4.onCheckOut(); element4.setElementMap(elementMap); element4.setSize(new Dimension(100.0f, 100.0f)); checkOut2.addBasicElement(element4); element4.onAttributesInitialized(); element4.onChildrenAdded(); final DecoratorAppearance appearance = checkOut2.getAppearance(); appearance.setElementMap(elementMap); checkOut2.addBasicElement(appearance); appearance.onAttributesInitialized(); final Margin checkOut3 = Margin.checkOut(); checkOut3.setElementMap(elementMap); checkOut3.setInsets(new Insets(0, 0, 15, 0)); appearance.addBasicElement(checkOut3); checkOut3.onAttributesInitialized(); checkOut3.onChildrenAdded(); final Padding element5 = new Padding(); element5.onCheckOut(); element5.setElementMap(elementMap); element5.setInsets(new Insets(10, 15, 10, 15)); appearance.addBasicElement(element5); element5.onAttributesInitialized(); element5.onChildrenAdded(); appearance.onChildrenAdded(); final TextView element6 = new TextView(); element6.onCheckOut(); element6.setElementMap(elementMap); element6.setStyle("smallboldMap"); element6.setMinWidth(1); element6.setMaxWidth(200); checkOut2.addBasicElement(element6); element6.onAttributesInitialized(); final PropertyElement checkOut4 = PropertyElement.checkOut(); checkOut4.setElementMap(elementMap); checkOut4.setName("mapPopupDescription"); checkOut4.setAttribute("text"); element6.addBasicElement(checkOut4); checkOut4.onAttributesInitialized(); checkOut4.onChildrenAdded(); final PropertyElement checkOut5 = PropertyElement.checkOut(); checkOut5.setElementMap(elementMap); checkOut5.setName("mapPopupIsEditing"); checkOut5.setAttribute("visible"); element6.addBasicElement(checkOut5); checkOut5.onAttributesInitialized(); final ConditionResult element7 = new ConditionResult(); element7.onCheckOut(); element7.setElementMap(elementMap); checkOut5.addBasicElement(element7); element7.onAttributesInitialized(); final FalseCondition element8 = new FalseCondition(); element8.onCheckOut(); element8.setElementMap(elementMap); element7.addBasicElement(element8); element8.onAttributesInitialized(); element8.onChildrenAdded(); element7.onChildrenAdded(); checkOut5.onChildrenAdded(); element6.onChildrenAdded(); final String id3 = "textEditor"; final TextEditor textEditor = new TextEditor(); textEditor.onCheckOut(); textEditor.setElementMap(elementMap); if (elementMap != null && id3 != null) { elementMap.add(id3, textEditor); } textEditor.setStyle("withoutBorder"); textEditor.setMaxChars(200); textEditor.setMinWidth(200); textEditor.setMaxWidth(200); final KeyTypedListener onKeyType = new KeyTypedListener(); onKeyType.setCallBackFunc("wakfu.map:onTextEditorChange"); textEditor.setOnKeyType(onKeyType); final KeyPressedListener onKeyPress = new KeyPressedListener(); onKeyPress.setCallBackFunc("wakfu.map:onTextEditorKeyPress"); textEditor.setOnKeyPress(onKeyPress); textEditor.setFocusable(true); textEditor.setSelectOnFocus(true); checkOut2.addBasicElement(textEditor); textEditor.onAttributesInitialized(); final PropertyElement checkOut6 = PropertyElement.checkOut(); checkOut6.setElementMap(elementMap); checkOut6.setName("mapPopupDescription"); checkOut6.setAttribute("text"); textEditor.addBasicElement(checkOut6); checkOut6.onAttributesInitialized(); checkOut6.onChildrenAdded(); final PropertyElement checkOut7 = PropertyElement.checkOut(); checkOut7.setElementMap(elementMap); checkOut7.setName("mapPopupIsEditing"); checkOut7.setAttribute("visible"); textEditor.addBasicElement(checkOut7); checkOut7.onAttributesInitialized(); checkOut7.onChildrenAdded(); final PropertyElement checkOut8 = PropertyElement.checkOut(); checkOut8.setElementMap(elementMap); checkOut8.setName("mapPopupIsEditing"); checkOut8.setAttribute("focused"); textEditor.addBasicElement(checkOut8); checkOut8.onAttributesInitialized(); checkOut8.onChildrenAdded(); textEditor.onChildrenAdded(); final String id4 = "valid"; final Button button = new Button(); button.onCheckOut(); button.setElementMap(elementMap); if (elementMap != null && id4 != null) { elementMap.add(id4, button); } button.setStyle("smallValid"); final MouseClickedListener onClick = new MouseClickedListener(); onClick.setCallBackFunc("wakfu.map:applyNote"); button.setOnClick(onClick); checkOut2.addBasicElement(button); button.onAttributesInitialized(); final PropertyElement checkOut9 = PropertyElement.checkOut(); checkOut9.setElementMap(elementMap); checkOut9.setName("mapPopupIsEditing"); checkOut9.setAttribute("visible"); button.addBasicElement(checkOut9); checkOut9.onAttributesInitialized(); checkOut9.onChildrenAdded(); button.onChildrenAdded(); checkOut2.onChildrenAdded(); final String id5 = "image"; final Image image = new Image(); image.onCheckOut(); image.setElementMap(elementMap); if (elementMap != null && id5 != null) { elementMap.add(id5, image); } image.setStyle("BubbleArrowLeft"); image.setNonBlocking(true); checkOut.addBasicElement(image); image.onAttributesInitialized(); final StaticLayoutData element9 = new StaticLayoutData(); element9.onCheckOut(); element9.setElementMap(elementMap); element9.setAlign(Alignment17.SOUTH_WEST); element9.setSize(new Dimension(-2, -2)); image.addBasicElement(element9); element9.onAttributesInitialized(); element9.onChildrenAdded(); image.onChildrenAdded(); checkOut.onChildrenAdded(); element.onChildrenAdded(); return element; }
private void build(int width) { positions.clear(); width = width - getPaddingLeft() - getPaddingRight(); if (width <= 0 || TextUtils.isEmpty(text)) { truncatedLayout = expandedLayout = null; return; } for (BubbleSpan span : spans) { span.resetWidth(width); } try { truncatedLayout = expandedLayout = new StaticLayout( text, textPaint, width, Layout.Alignment.ALIGN_NORMAL, lineSpacing, 1, false); if (maxLines > 0 && truncatedLayout.getLineCount() > maxLines) { int lineEnd = truncatedLayout.getLineEnd(maxLines - 1); int offset = -1; StaticLayout sl = new StaticLayout( moreText, textPaint, width, Layout.Alignment.ALIGN_NORMAL, lineSpacing, 1, false); sl.getWidth(); while (truncatedLayout.getLineCount() > maxLines && lineEnd > 0) { if (offset == -1 && truncatedLayout.getLineWidth(maxLines - 1) + sl.getLineWidth(0) > width) { offset = truncatedLayout.getOffsetForHorizontal(maxLines - 1, width - sl.getLineWidth(0)); lineEnd = offset; } else if (offset > 0) { lineEnd--; } SpannableStringBuilder textTruncated = new SpannableStringBuilder(text.subSequence(0, lineEnd)); textTruncated.append(moreText); truncatedLayout = new StaticLayout( textTruncated, textPaint, width, Layout.Alignment.ALIGN_NORMAL, lineSpacing, 1, false); } } } catch (java.lang.ArrayIndexOutOfBoundsException e) { return; } if (truncated) { recomputeSpans((Spannable) truncatedLayout.getText()); } else { recomputeSpans((Spannable) expandedLayout.getText()); } for (BubbleSpan span : spans) { positions.put(span, span.rect(this)); } }
public void build(MessageWireframe wireframe, int desiredWidth, StelsApplication application) { Logger.d(TAG, "Build layout start"); checkResources(application); senderPaint = initTextPaint(); senderPaint.setTypeface(FontController.loadTypeface(application, "regular")); senderPaint.setTextSize(bodyPaint.getTextSize()); senderPaint.setColor(0xff000000); forwardingPaint = initTextPaint(); forwardingPaint.setTypeface(FontController.loadTypeface(application, "light")); forwardingPaint.setTextSize(bodyPaint.getTextSize()); forwardingPaint.setColor(0xff000000); this.layoutDesiredWidth = desiredWidth; this.isOut = wireframe.message.isOut(); this.showState = isOut; this.isGroup = wireframe.message.getPeerType() == PeerType.PEER_CHAT && !isOut; if (isGroup) { User user = wireframe.senderUser; this.senderName = user.getDisplayName(); if (!wireframe.message.isForwarded()) { senderPaint.setColor( Placeholders.USER_PLACEHOLDERS_COLOR[ wireframe.message.getSenderId() % Placeholders.USER_PLACEHOLDERS_COLOR.length]); forwardingPaint.setColor( Placeholders.USER_PLACEHOLDERS_COLOR[ wireframe.message.getSenderId() % Placeholders.USER_PLACEHOLDERS_COLOR.length]); } } if (wireframe.message.isForwarded()) { isForwarded = true; this.forwarderName = wireframe.forwardUser.getDisplayName(); if (isOut) { forwardingPaint.setColor(0xff739f53); senderPaint.setColor(0xff739f53); } else { forwardingPaint.setColor(0xff4884cf); senderPaint.setColor(0xff4884cf); } } else { isForwarded = false; } if (isGroup) { User user = application.getEngine().getUser(wireframe.message.getSenderId()); this.senderName = user.getDisplayName(); } if (wireframe.message.isForwarded()) { isForwarded = true; this.forwarderName = wireframe.forwardUser.getDisplayName(); } else { isForwarded = false; } layoutDesiredWidth = desiredWidth; long start = SystemClock.uptimeMillis(); this.spannable = application .getEmojiProcessor() .processEmojiCompatMutable( wireframe.message.getMessage(), EmojiProcessor.CONFIGURATION_BUBBLES); // spannable = new SpannableString(wireframe.message.getMessage()); Logger.d(TAG, "Emoji processed in " + (SystemClock.uptimeMillis() - start) + " ms"); start = SystemClock.uptimeMillis(); Linkify.addLinks( this.spannable, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES); fixLinks(spannable); Logger.d(TAG, "Added links in " + (SystemClock.uptimeMillis() - start) + " ms"); start = SystemClock.uptimeMillis(); layout = new StaticLayout( spannable, bodyPaint, desiredWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); Logger.d(TAG, "Built base layout in " + (SystemClock.uptimeMillis() - start) + " ms"); if (layout.getLineCount() < 20) { int layoutTextWidth = 0; for (int i = 0; i < layout.getLineCount(); i++) { layoutTextWidth = (int) Math.max(layout.getLineWidth(i), layoutTextWidth); } if (layoutTextWidth < layout.getWidth() - px(10)) { layout = new StaticLayout( spannable, bodyPaint, layoutTextWidth + px(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); } } layoutRealWidth = layout.getWidth(); timeWidth = (int) clockOutPaint.measureText(wireframe.date) + px((showState ? 23 : 0) + 6); if (layout.getLineCount() == 1) { boolean isLastRtl = layout.getParagraphDirection(0) == Layout.DIR_RIGHT_TO_LEFT; if (!isLastRtl && desiredWidth - layoutRealWidth > timeWidth) { layoutRealWidth += timeWidth; layoutHeight = layout.getHeight() + px(3); } else if (isLastRtl && desiredWidth - layout.getWidth() > timeWidth) { layoutRealWidth = layout.getWidth() + timeWidth; layoutHeight = layout.getHeight() + px(3); } else { if (isLastRtl) { layoutRealWidth = layout.getWidth(); } layoutHeight = layout.getHeight() + px(17); } } else { boolean isLastRtl = layout.getParagraphDirection(layout.getLineCount() - 1) == Layout.DIR_RIGHT_TO_LEFT; if (!isLastRtl && (desiredWidth - layout.getLineWidth(layout.getLineCount() - 1) > timeWidth)) { layoutRealWidth = (int) Math.max( layoutRealWidth, layout.getLineWidth(layout.getLineCount() - 1) + timeWidth); layoutHeight = layout.getHeight() + px(3); } else if (isLastRtl && (desiredWidth - layout.getWidth() > timeWidth)) { layoutRealWidth = (int) Math.max(layoutRealWidth, layout.getWidth() + timeWidth); layoutHeight = layout.getHeight() + px(3); } else { layoutHeight = layout.getHeight() + px(17); } } if (layoutRealWidth < timeWidth) { layoutRealWidth = timeWidth; } if (isForwarded) { layoutHeight += px(19) * 2; forwardOffset = (int) forwardingPaintBase.measureText("From "); layoutRealWidth = (int) Math.max(layoutRealWidth, forwardingPaintBase.measureText("Forwarded message")); forwarderNameMeasured = TextUtils.ellipsize( forwarderName, senderPaintBase, desiredWidth - forwardOffset, TextUtils.TruncateAt.END) .toString(); layoutRealWidth = (int) Math.max( layoutRealWidth, forwardOffset + senderPaintBase.measureText(forwarderNameMeasured)); } if (isGroup && !isOut && !isForwarded) { layoutHeight += px(19); senderNameMeasured = TextUtils.ellipsize(senderName, senderPaintBase, desiredWidth, TextUtils.TruncateAt.END) .toString(); int width = (int) senderPaintBase.measureText(senderNameMeasured); layoutRealWidth = Math.max(layoutRealWidth, width); } Logger.d(TAG, "Build layout end"); }
public List<Integer> getPageOffsets(CharSequence text, boolean includePageNumbers) { if (text == null) { return new ArrayList<Integer>(); } List<Integer> pageOffsets = new ArrayList<Integer>(); TextPaint textPaint = bookView.getInnerView().getPaint(); int boundedWidth = bookView.getInnerView().getWidth(); LOG.debug("Page width: " + boundedWidth); StaticLayout layout = layoutFactory.create(text, textPaint, boundedWidth, bookView.getLineSpacing()); LOG.debug("Layout height: " + layout.getHeight()); layout.draw(new Canvas()); // Subtract the height of the top margin int pageHeight = bookView.getHeight() - bookView.getVerticalMargin(); if (includePageNumbers) { String bottomSpace = "0\n"; StaticLayout numLayout = layoutFactory.create(bottomSpace, textPaint, boundedWidth, bookView.getLineSpacing()); numLayout.draw(new Canvas()); // Subtract the height needed to show page numbers, or the // height of the margin, whichever is more pageHeight = pageHeight - Math.max(numLayout.getHeight(), bookView.getVerticalMargin()); } else { // Just subtract the bottom margin pageHeight = pageHeight - bookView.getVerticalMargin(); } LOG.debug("Got pageHeight " + pageHeight); int totalLines = layout.getLineCount(); int topLineNextPage = -1; int pageStartOffset = 0; while (topLineNextPage < totalLines - 1) { LOG.debug("Processing line " + topLineNextPage + " / " + totalLines); int topLine = layout.getLineForOffset(pageStartOffset); topLineNextPage = layout.getLineForVertical(layout.getLineTop(topLine) + pageHeight); LOG.debug("topLine " + topLine + " / " + topLineNextPage); if (topLineNextPage == topLine) { // If lines are bigger than can fit on a page topLineNextPage = topLine + 1; } int pageEnd = layout.getLineEnd(topLineNextPage - 1); LOG.debug("pageStartOffset=" + pageStartOffset + ", pageEnd=" + pageEnd); if (pageEnd > pageStartOffset) { if (text.subSequence(pageStartOffset, pageEnd).toString().trim().length() > 0) { pageOffsets.add(pageStartOffset); } pageStartOffset = layout.getLineStart(topLineNextPage); } } return pageOffsets; }