package com.starcor.xul.Render; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.text.Html; import android.text.Layout; import android.text.Spanned; import android.text.StaticLayout; import android.text.TextPaint; import android.view.KeyEvent; import com.starcor.xul.Graphics.XulDC; import com.starcor.xul.Graphics.XulDrawable; import com.starcor.xul.Prop.XulAttr; import com.starcor.xul.Prop.XulFocus; import com.starcor.xul.Prop.XulPropNameCache; import com.starcor.xul.Prop.XulStyle; import com.starcor.xul.Render.Components.BaseScrollBar; import com.starcor.xul.Render.Drawer.IXulAnimation; import com.starcor.xul.Render.Drawer.XulDrawer; import com.starcor.xul.Render.Transform.ITransformer; import com.starcor.xul.Utils.XulCachedHashMap; import com.starcor.xul.Utils.XulLayoutHelper; import com.starcor.xul.Utils.XulPropParser; import com.starcor.xul.Utils.XulRenderDrawableItem; import com.starcor.xul.XulArea; import com.starcor.xul.XulItem; import com.starcor.xul.XulLayout; import com.starcor.xul.XulManager; import com.starcor.xul.XulRenderContext; import com.starcor.xul.XulUtils; import com.starcor.xul.XulView; import com.starcor.xul.XulWorker; import java.util.Map; /** * Created by hy on 2014/5/13. */ public class XulSpannedLabelRender extends XulViewRender { private static final String TAG = XulSpannedLabelRender.class.getSimpleName(); public static void register() { XulRenderFactory.registerBuilder("item", "spanned_label", new XulRenderFactory.RenderBuilder() { @Override protected XulViewRender createRender(XulRenderContext ctx, XulView view) { assert view instanceof XulItem; return new XulSpannedLabelRender(ctx, view); } }); } float _fontSize = 12; int _fontColor = Color.BLACK; float _fontWeight = 0.0f; boolean _fontItalic = false; boolean _fontUnderline = false; boolean _fontStrikeThrough = false; float _fontShadowX = 0; float _fontShadowY = 0; float _fontShadowSize = 0; int _fontShadowColor = 0; float _fontAlignY = 0.0f; float _fontAlignX = 0.0f; float _fontScaleX = 1.0f; boolean _drawEllipsis = false; // … String _fontFace = null; boolean _multiLine = true; class TextMarqueeAnimation implements IXulAnimation { XulViewRender _render; int _marqueeSpeed = 0; int _marqueeDelay = 500; int _marqueeInterval = 500; int _marqueeSpace = 60; long _beginTime; long _intervalBeginTime; boolean _running = false; public TextMarqueeAnimation(XulViewRender render, ITransformer aniTransformer) { _render = render; } public TextMarqueeAnimation(XulViewRender render) { _render = render; } @Override public boolean updateAnimation(long timestamp) { if (_marqueeSpeed <= 0 || !_running) { _marqueePosition = -1; return false; } long progress = timestamp - _beginTime; if (progress <= _marqueeDelay) { return true; } if (_multiLine) { return false; } if (_isInvisible()) { return false; } if (_intervalBeginTime == _beginTime) { progress -= _marqueeDelay; _marqueePosition = _textHeight / 1.1f * progress / _marqueeSpeed; long marqueeTextWidth = _textWidth; if (_marqueeSpace >= 0) { marqueeTextWidth += _marqueeSpace; } else { int viewWidth = getWidth(); if (_padding != null) { viewWidth -= _padding.left + _padding.right; } marqueeTextWidth += -(viewWidth * _marqueeSpace / 100); } if (_marqueePosition >= marqueeTextWidth) { _marqueePosition = -1; _intervalBeginTime = timestamp; } } if (_marqueePosition < 0) { _intervalBeginTime = _beginTime = timestamp - (_marqueeDelay - _marqueeInterval); // check for parent XulArea parent = _view.getParent(); while (true) { if (parent instanceof XulLayout) { break; } if (parent == null) { return false; } XulViewRender render = parent.getRender(); if (render == null) { return false; } if (render._isInvisible()) { return false; } parent = parent.getParent(); } } markDirtyView(); return true; } public void stop() { _running = false; } public void prepareMarqueeAnimation(XulPropParser.xulParsedAttr_Text_Marquee marquee) { _marqueeDelay = marquee.delay; _marqueeInterval = marquee.interval; _marqueeSpace = marquee.space; _marqueeSpeed = marquee.speed; _marqueeDirection = marquee.direction; _running = true; if (marquee.speed != _marqueeSpeed || _marqueePosition < 0) { _intervalBeginTime = _beginTime = animationTimestamp(); _marqueeSpeed = marquee.speed; if (_marqueeSpeed == 0) { _marqueePosition = -1; } else { _marqueePosition = 0; } } } } TextMarqueeAnimation _marqueeAnimation; float _marqueePosition = -1; int _marqueeDirection = 1; int _scrollX = 0; int _scrollY = 0; int _scrollTargetY = 0; private Object _textData; private Spanned _spannedText; private Layout _textLayout; private int _textWidth; private int _textHeight; private int _textLineHeight; private float _lineHeightScalar = 1.0f; private XulDC _drawingDC; private float _superResample = 1.0f; private BaseScrollBar _scrollBar; private BaseScrollBar.ScrollBarHelper _scrollBarHelper; private class SpannedLabelImage extends XulRenderDrawableItem { String _source; Drawable _drawable; XulDrawable _xulDrawable; XulDrawer _xulDrawer; volatile boolean _isLoading = false; @Override public void onImageReady(XulDrawable bmp) { _xulDrawable = bmp; _isLoading = false; if (_xulDrawable == null) { return; } _textData = ""; _drawable = new Drawable() { @Override public void draw(Canvas canvas) { if (_drawingDC == null) { return; } _xulDrawer.draw(_drawingDC, _xulDrawable, this.getBounds(), XulRenderContext.getDefPicPaint()); } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter cf) { } @Override public int getOpacity() { return 0; } }; _xulDrawer = XulDrawer.create(_xulDrawable, _view, getRenderContext()); _drawable.setBounds(0, 0, bmp.getWidth(), bmp.getHeight()); setUpdateLayout(); } } private XulCachedHashMap<String, SpannedLabelImage> _imageCache; private Html.ImageGetter _imageGetter; private volatile boolean _imageCacheChanged = false; private Html.ImageGetter obtainImageGetter() { if (_imageGetter != null) { return _imageGetter; } _imageGetter = new Html.ImageGetter() { @Override public Drawable getDrawable(String source) { if (_imageCache == null) { _imageCache = new XulCachedHashMap<String, SpannedLabelImage>(); } SpannedLabelImage spannedLabelImage = _imageCache.get(source); if (spannedLabelImage == null) { spannedLabelImage = new SpannedLabelImage(); spannedLabelImage._source = source; _imageCache.put(source, spannedLabelImage); _imageCacheChanged = true; } if (spannedLabelImage._drawable == null) { return null; } return spannedLabelImage._drawable; } }; return _imageGetter; } public XulSpannedLabelRender(XulRenderContext ctx, XulView view) { super(ctx, view); } @Override public XulWorker.DrawableItem collectPendingImageItem() { XulWorker.DrawableItem pendingImageItem = super.collectPendingImageItem(); if (pendingImageItem != null) { return pendingImageItem; } if (_imageCache == null || !_imageCacheChanged) { return null; } for (Map.Entry<String, SpannedLabelImage> entry : _imageCache.entrySet()) { SpannedLabelImage imageItem = entry.getValue(); if (imageItem._isLoading) { continue; } if (imageItem._drawable != null) { continue; } imageItem._isLoading = true; imageItem.url = imageItem._source; imageItem.width = 0; imageItem.height = 0; return imageItem; } _imageCacheChanged = false; return null; } @Override public void draw(XulDC dc, Rect rect, int xBase, int yBase) { if (_isInvisible()) { return; } _drawingDC = dc; super.draw(dc, rect, xBase, yBase); drawText(dc, xBase, yBase); _drawingDC = null; if (_scrollBar != null) { RectF animRect = getAnimRect(); // TODO: add support for float version _scrollBar.draw(dc, rect, XulUtils.roundToInt(xBase + animRect.left), XulUtils.roundToInt(yBase + animRect.top)); } if (_scrollTargetY == _scrollY) { int viewWidth = XulUtils.calRectWidth(_rect) - _padding.left - _padding.right; int viewHeight = XulUtils.calRectHeight(_rect) - _padding.top - _padding.bottom; if (_textHeight <= viewHeight) { _scrollTargetY = 0; } else if (_scrollY + _textHeight < viewHeight) { _scrollTargetY = viewHeight - _textHeight; } } if (_scrollTargetY != _scrollY) { int deltaY = _scrollY - _scrollTargetY; if (Math.abs(deltaY) > 4) { deltaY /= 2; } else { if (deltaY > 0) { deltaY = Math.min(deltaY, 2); } else { deltaY = Math.max(deltaY, -2); } } _scrollY -= deltaY; markDirtyView(); } } private void drawText(XulDC dc, int xBase, int yBase) { if (_textLayout == null) { return; } dc.save(); Rect viewRect = _rect; int viewRectHeight = XulUtils.calRectHeight(viewRect); int viewRectWidth = XulUtils.calRectWidth(viewRect); int itemOffsetX = _screenX + xBase + viewRect.left; int itemOffsetY = _screenY + yBase + viewRect.top; int xBaseOffset = itemOffsetX + _padding.left; int yBaseOffset = itemOffsetY + _padding.top; if (Math.abs(_scalarX - 1.0f) > 0.001f || Math.abs(_scalarY - 1.0f) > 0.001f) { float xAlignment = viewRectWidth * _scalarXAlign; float yAlignment = viewRectHeight * _scalarYAlign; dc.scale(_scalarX, _scalarY, itemOffsetX + xAlignment, itemOffsetY + yAlignment); } dc.translate(xBaseOffset, yBaseOffset); int viewWidth = viewRectWidth - _padding.left - _padding.right; int viewHeight = viewRectHeight - _padding.top - _padding.bottom; dc.clipRect( 0, 0, viewWidth, viewHeight ); float resampleScalar = _superResample; boolean doResample = Math.abs(resampleScalar - 1.0f) > 0.01f; if (!_multiLine) { // 计算单行文本的align偏移 float xOff = (viewWidth - _textWidth) * _fontAlignX; float yOff = (viewHeight - _textHeight) * _fontAlignY; if (_isRTL()) { if (_textWidth >= viewWidth) { xOff = viewWidth - _textWidth; } } else if (_textWidth >= viewWidth) { xOff = 0; } // 转换文本偏移 dc.translate(xOff, yOff); // 处理marquee动画 if (_textWidth <= viewWidth) { _stopMarqueeAnimation(); } else if (_marqueeAnimation != null && _marqueePosition >= 0) { int marqueeSpace = _marqueeAnimation._marqueeSpace; int space; if (marqueeSpace > 0) { space = marqueeSpace; } else { space = -viewWidth * marqueeSpace / 100; } // 处理滚动偏移 xOff = -1 * _marqueePosition * _marqueeDirection; if (xOff < viewWidth || xOff + _textWidth > 0) { dc.translate(xOff, 0); if (doResample) { dc.save(Canvas.MATRIX_SAVE_FLAG); dc.scale(1.0f / resampleScalar, 1.0f / resampleScalar); _textLayout.draw(dc.getCanvas()); dc.restore(); } else { _textLayout.draw(dc.getCanvas()); } } // 处理space偏移 float drawPos = (_textWidth + space) * _marqueeDirection; if (drawPos < viewWidth || drawPos + _textWidth > 0) { xOff = drawPos; dc.translate(xOff, 0); } } } dc.translate(0, _scrollY); if (doResample) { dc.scale(1.0f / resampleScalar, 1.0f / resampleScalar); } _textLayout.draw(dc.getCanvas()); dc.restore(); } static private <T> T _testAndSet(T newVal, T oldVal, boolean[] isChanged) { if (newVal == oldVal) { return oldVal; } isChanged[0] = true; return newVal; } static private String _testAndSet(String newVal, String oldVal, boolean[] isChanged) { if (newVal == oldVal) { return oldVal; } if (newVal != null && oldVal != null && newVal.equals(oldVal)) { return oldVal; } isChanged[0] = true; return newVal; } private void syncTextInfo() { if (!_isViewChanged()) { return; } boolean[] anyStyleChanged = new boolean[]{false}; XulStyle fontFaceStyle = _view.getStyle(XulPropNameCache.TagId.FONT_FACE); XulStyle fontSizeStyle = _view.getStyle(XulPropNameCache.TagId.FONT_SIZE); XulStyle fontColorStyle = _view.getStyle(XulPropNameCache.TagId.FONT_COLOR); XulStyle fontWeightStyle = _view.getStyle(XulPropNameCache.TagId.FONT_WEIGHT); XulStyle fontShadowStyle = _view.getStyle(XulPropNameCache.TagId.FONT_SHADOW); XulStyle fontAlignStyle = _view.getStyle(XulPropNameCache.TagId.FONT_ALIGN); XulStyle fontUnderlineStyle = _view.getStyle(XulPropNameCache.TagId.FONT_STYLE_UNDERLINE); XulStyle fontItalicStyle = _view.getStyle(XulPropNameCache.TagId.FONT_STYLE_ITALIC); XulStyle lineHeightStyle = _view.getStyle(XulPropNameCache.TagId.LINE_HEIGHT); XulStyle fontScaleXStyle = _view.getStyle(XulPropNameCache.TagId.FONT_SCALE_X); XulStyle fontStrikeStyle = _view.getStyle(XulPropNameCache.TagId.FONT_STYLE_STRIKE); XulStyle fontResampleStyle = _view.getStyle(XulPropNameCache.TagId.FONT_RESAMPLE); if (fontResampleStyle != null) { _superResample = ((XulPropParser.xulParsedProp_Float) fontResampleStyle.getParsedValue()).val; _superResample = Math.min(Math.max(1.0f, _superResample), 4.0f); } else { _superResample = 1.0f; } if (fontScaleXStyle != null) { _fontScaleX = _testAndSet(((XulPropParser.xulParsedStyle_FontScaleX) fontScaleXStyle.getParsedValue()).val, _fontScaleX, anyStyleChanged); } else { _fontScaleX = _testAndSet(1.0f, _fontScaleX, anyStyleChanged); } if (fontStrikeStyle != null) { _fontStrikeThrough = _testAndSet(((XulPropParser.xulParsedStyle_FontStyleStrike) fontStrikeStyle.getParsedValue()).val, _fontStrikeThrough, anyStyleChanged); } else { _fontStrikeThrough = _testAndSet(false, _fontStrikeThrough, anyStyleChanged); } if (fontFaceStyle != null) { _fontFace = _testAndSet(fontFaceStyle.getStringValue(), _fontFace, anyStyleChanged); } else { _fontFace = _testAndSet(null, _fontFace, anyStyleChanged); } if (lineHeightStyle != null) { _lineHeightScalar = _testAndSet(((XulPropParser.xulParsedStyle_LineHeight) lineHeightStyle.getParsedValue()).val, _lineHeightScalar, anyStyleChanged); } else { _lineHeightScalar = _testAndSet(1.0f, _lineHeightScalar, anyStyleChanged); } if (fontUnderlineStyle != null) { _fontUnderline = _testAndSet(((XulPropParser.xulParsedProp_booleanValue) fontUnderlineStyle.getParsedValue()).val, _fontUnderline, anyStyleChanged); } else { _fontUnderline = _testAndSet(false, _fontUnderline, anyStyleChanged); } if (fontItalicStyle != null) { _fontItalic = _testAndSet(((XulPropParser.xulParsedProp_booleanValue) fontItalicStyle.getParsedValue()).val, _fontItalic, anyStyleChanged); } else { _fontItalic = _testAndSet(false, _fontItalic, anyStyleChanged); } double xScalar = _ctx.getXScalar(); double yScalar = _ctx.getYScalar(); if (fontSizeStyle != null) { XulPropParser.xulParsedStyle_FontSize fontSize = fontSizeStyle.getParsedValue(); _fontSize = _testAndSet((float) (xScalar * fontSize.val), _fontSize, anyStyleChanged); } else { _fontSize = _testAndSet((float) (xScalar * 12), _fontSize, anyStyleChanged); } if (fontColorStyle != null) { XulPropParser.xulParsedStyle_FontColor fontColor = fontColorStyle.getParsedValue(); _fontColor = _testAndSet(fontColor.val, _fontColor, anyStyleChanged); } else { _fontColor = _testAndSet(Color.BLACK, _fontColor, anyStyleChanged); } if (fontWeightStyle != null) { XulPropParser.xulParsedStyle_FontWeight fontWeight = fontWeightStyle.getParsedValue(); _fontWeight = _testAndSet((float) (xScalar * fontWeight.val), _fontWeight, anyStyleChanged); } else { _fontWeight = _testAndSet((float) (xScalar * 1.0), _fontWeight, anyStyleChanged); } if (fontShadowStyle != null) { XulPropParser.xulParsedStyle_FontShadow fontShadow = fontShadowStyle.getParsedValue(); _fontShadowX = _testAndSet((float) (fontShadow.xOffset * xScalar), _fontShadowX, anyStyleChanged); _fontShadowY = _testAndSet((float) (fontShadow.yOffset * yScalar), _fontShadowY, anyStyleChanged); _fontShadowSize = _testAndSet((float) (fontShadow.size * xScalar), _fontShadowSize, anyStyleChanged); _fontShadowColor = _testAndSet(fontShadow.color, _fontShadowColor, anyStyleChanged); } else { _fontShadowSize = _testAndSet(0.0f, _fontShadowSize, anyStyleChanged); _fontShadowColor = _testAndSet(0, _fontShadowColor, anyStyleChanged); } if (fontAlignStyle != null) { XulPropParser.xulParsedStyle_FontAlign fontAlign = fontAlignStyle.getParsedValue(); _fontAlignX = _testAndSet(fontAlign.xAlign, _fontAlignX, anyStyleChanged); _fontAlignY = _testAndSet(fontAlign.yAlign, _fontAlignY, anyStyleChanged); } else { _fontAlignX = _testAndSet(0.0f, _fontAlignX, anyStyleChanged); _fontAlignY = _testAndSet(0.0f, _fontAlignY, anyStyleChanged); } XulAttr multiAttr = _view.getAttr(XulPropNameCache.TagId.MULTI_LINE); if (multiAttr == null || "true".equals(multiAttr.getStringValue())) { _multiLine = _testAndSet(true, _multiLine, anyStyleChanged); } else { _multiLine = _testAndSet(false, _multiLine, anyStyleChanged); } if ("true".equals(_view.getAttrString(XulPropNameCache.TagId.ELLIPSIS))) { _drawEllipsis = _testAndSet(true, _drawEllipsis, anyStyleChanged); } else { _drawEllipsis = _testAndSet(false, _drawEllipsis, anyStyleChanged); } XulAttr text = _view.getAttr(XulPropNameCache.TagId.TEXT); if (text != null && text.getValue() != null) { Object textData = text.getValue(); if (textData == null) { _spannedText = null; _textLayout = null; } else { while (_textData != textData) { _textData = textData; String newText; if (_textData instanceof String) { newText = (String) _textData; } else { _spannedText = null; _textLayout = null; break; } _spannedText = Html.fromHtml(newText, obtainImageGetter(), null); _textLayout = null; break; } } } else { _spannedText = null; _textLayout = null; } Paint.FontMetrics fontMetrics = _getTextPaint().getFontMetrics(); _textLineHeight = XulUtils.ceilToInt((fontMetrics.bottom - fontMetrics.top) * _lineHeightScalar); if (anyStyleChanged[0]) { _textLayout = null; } if (_multiLine || _spannedText == null) { _stopMarqueeAnimation(); } else while (true) { // 创建marquee动画 XulAttr marqueeAttr = _view.getAttr(XulPropNameCache.TagId.MARQUEE); if (marqueeAttr == null) { _stopMarqueeAnimation(); break; } XulPropParser.xulParsedAttr_Text_Marquee marquee = marqueeAttr.getParsedValue(); if (marquee.speed <= 0) { _stopMarqueeAnimation(); break; } if (_marqueeAnimation == null) { _marqueeAnimation = new TextMarqueeAnimation(this); } _marqueeAnimation.prepareMarqueeAnimation(marquee); addAnimation(_marqueeAnimation); break; } if (_isRTL()) { _fontAlignX = 1.0f - _fontAlignX; _marqueeDirection = -_marqueeDirection; } } private void _stopMarqueeAnimation() { if (_marqueeAnimation == null) { return; } _marqueeAnimation.stop(); if (_marqueePosition < 0) { return; } markDirtyView(); _marqueePosition = -1; } @Override public boolean onKeyEvent(KeyEvent event) { int scrollLines = 3; // 处理滚动事件 int viewHeight = XulUtils.calRectHeight(_rect) - _padding.top - _padding.bottom; if (_textHeight > viewHeight && event.getAction() == KeyEvent.ACTION_DOWN) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_UP: if (_scrollTargetY < 0) { _scrollTargetY += _textLineHeight * scrollLines; _scrollTargetY -= (_scrollTargetY % _textLineHeight); if (_scrollTargetY > 0) { _scrollTargetY = 0; } if (_scrollBar != null) { _scrollBar.reset(); } markDirtyView(); return true; } break; case KeyEvent.KEYCODE_DPAD_DOWN: int minScrollYPos = viewHeight - _textHeight; if (_scrollTargetY > minScrollYPos) { _scrollTargetY -= _textLineHeight * scrollLines; if (_scrollTargetY < minScrollYPos) { _scrollTargetY = minScrollYPos; } if (_scrollBar != null) { _scrollBar.reset(); } markDirtyView(); return true; } break; } } return false; } @Override public void syncData() { if (!_isDataChanged()) { return; } super.syncData(); XulAttr attrScrollbar = _view.getAttr("scrollbar"); if (attrScrollbar == null || attrScrollbar.getValue() == null) { _scrollBar = XulRenderFactory.buildScrollBar(_scrollBar, "", _getScrollBarHelper(), this); } else { _scrollBar = XulRenderFactory.buildScrollBar(_scrollBar, attrScrollbar.getStringValue(), _getScrollBarHelper(), this); } } private BaseScrollBar.ScrollBarHelper _getScrollBarHelper() { if (_scrollBarHelper == null) { _scrollBarHelper = new BaseScrollBar.ScrollBarHelper() { @Override public boolean isVertical() { return true; } @Override public int getScrollPos() { return _scrollY; } @Override public int getContentWidth() { return _textWidth; } @Override public int getContentHeight() { return _textHeight; } }; } return _scrollBarHelper; } private void updateTextLayout(int maxWidth) { if (_textLayout != null) { return; } if (_spannedText == null) { _textWidth = 0; _textHeight = 0; return; } if (!_multiLine) { maxWidth = Integer.MAX_VALUE; } else { maxWidth *= _superResample; } TextPaint textPaint = new TextPaint(_getTextPaint(_superResample)); _textLayout = new StaticLayout(_spannedText, textPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, _lineHeightScalar, 0, false); float textWidth = 0; for (int i = 0; i < _textLayout.getLineCount(); ++i) { float lineWidth = _textLayout.getLineWidth(i); if (textWidth < lineWidth) { textWidth = lineWidth; } } _textWidth = XulUtils.roundToInt(XulUtils.ceilToInt(textWidth) / _superResample); _textHeight = XulUtils.roundToInt(_textLayout.getHeight() / _superResample); } private Paint _getTextPaint() { return _getTextPaint(1.0f); } private Paint _getTextPaint(float fontSizeScale) { Paint defPaint = _ctx.getTextPaintByName(_fontFace); if (!(_fontShadowSize == 0 || (_fontShadowColor & 0xFF000000) == 0)) { defPaint = _ctx.getShadowTextPaintByName(_fontFace); defPaint.setShadowLayer(_fontShadowSize, _fontShadowX, _fontShadowY, _fontShadowColor); } defPaint.setColor(_fontColor); if (Math.abs(fontSizeScale - 1.0f) > 0.01f) { defPaint.setTextSize(_fontSize * fontSizeScale); } else { defPaint.setTextSize(_fontSize); } //defPaint.setStrokeWidth(_fontWeight / 2.0f); if (_fontWeight > 1.0) { defPaint.setFakeBoldText(true); } else { defPaint.setFakeBoldText(false); } defPaint.setUnderlineText(_fontUnderline); defPaint.setTextSkewX(_fontItalic ? -0.25f : 0); defPaint.setTextAlign(Paint.Align.LEFT); return defPaint; } @Override public int getDefaultFocusMode() { return XulFocus.MODE_NOFOCUS; } protected class LayoutElement extends XulViewRender.LayoutElement { @Override public int doFinal() { super.doFinal(); if (_isInvisible()) { return 0; } refreshTextLayout(); return 0; } private void refreshTextLayout() { if (_spannedText != null && _textLayout == null) { updateTextLayout(XulUtils.calRectWidth(_rect) - _padding.left - _padding.right); } } @Override public int prepare() { super.prepare(); if (_isInvisible()) { return 0; } syncTextInfo(); switch (getWidth()) { case XulManager.SIZE_AUTO: case XulManager.SIZE_MATCH_CONTENT: updateTextLayout(Integer.MAX_VALUE); break; case XulManager.SIZE_MATCH_PARENT: break; default: updateTextLayout(XulUtils.calRectWidth(_rect) - _padding.left - _padding.right); break; } return 0; } @Override public int getContentWidth() { return _textWidth; } @Override public int getContentHeight() { refreshTextLayout(); return _textHeight; } } @Override protected XulLayoutHelper.ILayoutElement createElement() { return new LayoutElement(); } }