package com.zhy.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.widget.RelativeLayout; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Created by zhy on 15/8/20. */ public class MixtureTextView extends RelativeLayout { private Layout layout = null; /** * 行高 */ private int mLineHeight; private int mTextColor = Color.BLACK; private int mTextSize = sp2px(14); private String mText; private int mLineSpace; private TextPaint mTextPaint; private List<List<Rect>> mDestRects = new ArrayList<List<Rect>>(); private List<Integer> mCorYs = null; private HashSet<Integer> mCorYHashes = new HashSet<Integer>(); private int mMaxHeight; private int mHeightMeasureSpec; private int mOriginHeightMeasureMode; private int mHeightReMeasureSpec; private boolean mNeedReMeasure; private boolean mNeedRenderText; private int mMinHeight; private static int[] ATTRS = new int[]{ android.R.attr.textSize,//16842901 android.R.attr.textColor,//16842904 android.R.attr.text//16843087 }; private static final int INDEX_ATTR_TEXT_SIZE = 0; private static final int INDEX_ATTR_TEXT_COLOR = 1; private static final int INDEX_ATTR_TEXT = 2; private Map<Integer, Point> mViewBounds = new HashMap<Integer, Point>(); public MixtureTextView(Context context, AttributeSet attrs) { super(context, attrs); readAttrs(context, attrs); //just for text if (mText == null) { mText = getResources().getString(R.string.text1); } //get text if (!TextUtils.isEmpty(mText)) { mNeedRenderText = true; } if (!mNeedRenderText) return; mTextPaint = new TextPaint(); mTextPaint.setDither(true); mTextPaint.setAntiAlias(true); mTextPaint.setColor(mTextColor); } private void readAttrs(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS); mTextSize = ta.getDimensionPixelSize(INDEX_ATTR_TEXT_SIZE, mTextSize); mTextColor = ta.getColor(INDEX_ATTR_TEXT_COLOR, mTextColor); mText = ta.getString(INDEX_ATTR_TEXT); ta.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!mNeedRenderText) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } mHeightMeasureSpec = heightMeasureSpec; mTextPaint.setTextSize(mTextSize); cacuLineHeight(); if (mNeedReMeasure) { super.onMeasure(widthMeasureSpec, mHeightReMeasureSpec); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } private void cacuLineHeight() { layout = new StaticLayout("爱我中华", mTextPaint, 0, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f, false); mLineHeight = layout.getLineBottom(0) - layout.getLineTop(0); } private boolean mFirstInLayout = true; @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mFirstInLayout) { mOriginHeightMeasureMode = MeasureSpec.getMode(mHeightMeasureSpec); mFirstInLayout = false; mMinHeight = getMeasuredHeight(); } super.onLayout(changed, l, t, r, b); if (!mNeedRenderText) { return; } getAllYCors(); } private boolean tryDraw(Canvas canvas) { boolean kidding = canvas == null; int lineHeight = mLineHeight; List<List<Rect>> destRects = mDestRects; int start = 0; int lineSum = 0; int fullSize = mText.length(); for (int i = 0; i < destRects.size(); i++) { List<Rect> rs = destRects.get(i); Rect r = rs.get(0); int rectWidth = r.width(); int rectHeight = r.height(); layout = generateLayout(mText.substring(start), rectWidth); int lineCount = rectHeight / lineHeight; lineCount = layout.getLineCount() < lineCount ? layout.getLineCount() : lineCount; if (!kidding) { canvas.save(); canvas.translate(r.left, r.top); canvas.clipRect(0, 0, r.width(), layout.getLineBottom(lineCount - 1) - layout.getLineTop(0)); layout.draw(canvas); canvas.restore(); } start += layout.getLineEnd(lineCount - 1); lineSum += lineCount; if (start >= fullSize) { break; } } if (kidding) { mMaxHeight += lineSum * lineHeight; if ((mMaxHeight > mMinHeight && getHeight() != mMaxHeight) && mOriginHeightMeasureMode != MeasureSpec.EXACTLY) { mHeightReMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.EXACTLY); mNeedReMeasure = true; requestLayout(); return true; } } return false; } /** * 获取所有的y坐标 */ private void getAllYCors() { int lineHeight = mLineHeight; Set<Integer> corYSet = mCorYHashes; corYSet.clear(); mViewBounds.clear(); //获得所有的y轴坐标 int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View c = getChildAt(i); if (c.getVisibility() == View.GONE) continue; int top = c.getTop(); int availableTop = c.getTop() - getPaddingTop(); availableTop = availableTop / lineHeight * lineHeight; top = availableTop + getPaddingTop(); corYSet.add(top); int bottom = c.getBottom(); int availableBottom = bottom - getPaddingTop(); availableBottom = availableBottom % lineHeight == 0 ? availableBottom : (availableBottom / lineHeight + 1) * lineHeight; bottom = availableBottom + getPaddingTop(); corYSet.add(bottom); mViewBounds.put(i, new Point(top, bottom)); } corYSet.add(getPaddingTop()); if (mOriginHeightMeasureMode == MeasureSpec.EXACTLY) { corYSet.add(getHeight()); } else { corYSet.add(Integer.MAX_VALUE); } //排序 List<Integer> corYs = new ArrayList<Integer>(corYSet); Collections.sort(corYs); mCorYs = corYs; } @Override protected void dispatchDraw(Canvas canvas) { mMaxHeight = getPaddingBottom() + getPaddingTop(); initAllNeedRenderRect(); boolean skipDraw = tryDraw(null); if (skipDraw) return; tryDraw(canvas); super.dispatchDraw(canvas); } private void initAllNeedRenderRect() { int lineHeight = mLineHeight; List<List<Rect>> destRects = this.mDestRects; List<Integer> corYs = mCorYs; destRects.clear(); int minLeft = getPaddingLeft(); int maxRight = getWidth() - getPaddingRight(); //find rect between y1 and y2 List<Rect> viewRectBetween2Y = null; for (int i = 0; i < corYs.size() - 1; i++) { int y1 = corYs.get(i); int y2 = corYs.get(i + 1); viewRectBetween2Y = new ArrayList<Rect>(); List<Rect> rs = caculateViewYBetween(y1, y2); Rect leftFirst = null; switch (rs.size()) { case 0: viewRectBetween2Y.add(new Rect(minLeft, y1, maxRight, y2)); break; case 1: leftFirst = rs.get(0); //添加第一个Rect tryAddFirst(leftFirst, viewRectBetween2Y, y1, y2, minLeft); tryAddLast(leftFirst, viewRectBetween2Y, y1, y2, maxRight); break; default: //add first leftFirst = rs.get(0); tryAddFirst(leftFirst, viewRectBetween2Y, y1, y2, minLeft); //add mid for (int j = 0; j < rs.size() - 1; j++) { Rect ra = rs.get(j); Rect rb = rs.get(j + 1); if (ra.right < rb.left) viewRectBetween2Y.add(new Rect(ra.right, y1, rb.left, y2)); } //add last Rect lastRect = rs.get(rs.size() - 1); tryAddLast(lastRect, viewRectBetween2Y, y1, y2, maxRight); break; } destRects.add(viewRectBetween2Y); } //split List<List<Rect>> bak = new ArrayList<List<Rect>>(destRects); int destRectSize = destRects.size(); int inc = 0;//索引增量 for (int i = 0; i < destRectSize; i++) { List<Rect> rs = destRects.get(i); if (rs.size() > 1) { int index = inc + i; bak.remove(rs); inc--; Rect rect1 = rs.get(0); int lh = rect1.height() / lineHeight; mMaxHeight -= lh * (rs.size() - 1) * lineHeight; for (int k = 0; k < lh; k++) { for (int j = 0; j < rs.size(); j++) { inc++; bak.add(index++, Arrays.asList(new Rect( rs.get(j).left, rect1.top + lineHeight * k, rs.get(j).right, rect1.top + lineHeight * k + lineHeight))); } } } } mDestRects = bak; } private void tryAddLast(Rect leftFirst, List<Rect> viewRectBetween2Y, int y1, int y2, int maxRight) { if (leftFirst.right < maxRight) { viewRectBetween2Y.add(new Rect(leftFirst.right, y1, maxRight, y2)); } } private void tryAddFirst(Rect leftFirst, List<Rect> viewRectBetween2Y, int y1, int y2, int minLeft) { if (leftFirst.left > minLeft) { viewRectBetween2Y.add(new Rect(minLeft, y1, leftFirst.left, y2)); } } private StaticLayout generateLayout(String text, int width) { return new StaticLayout(text, mTextPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f, false); } public void setText(String text) { if (TextUtils.isEmpty(text)) { mNeedRenderText = false; requestLayout(); return; } mNeedRenderText = true; mText = text; requestLayout(); invalidate(); } public void setTextColor(int color) { mTextPaint.setColor(color); mTextColor = color; invalidate(); } public void setTextSize(int unit, int size) { switch (unit) { case TypedValue.COMPLEX_UNIT_PX: mTextSize = size; break; case TypedValue.COMPLEX_UNIT_DIP: mTextSize = dp2px(size); break; case TypedValue.COMPLEX_UNIT_SP: mTextSize = sp2px(size); break; } mTextPaint.setTextSize(mTextSize); requestLayout(); invalidate(); } public void setTextSize(int pxSize) { setTextSize(TypedValue.COMPLEX_UNIT_PX, pxSize); } /** * 计算包含在y1到y2间的矩形区域 * * @param y1 * @param y2 * @return */ private List<Rect> caculateViewYBetween(int y1, int y2) { List<Rect> rs = new ArrayList<>(); Rect tmp = null; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View v = getChildAt(i); Point p = mViewBounds.get(i); int top = p.x; int bottom = p.y; if (top <= y1 && bottom >= y2) { tmp = new Rect(v.getLeft(), y1, v.getRight(), y2); rs.add(tmp); } } //TODO ADD Collections.sort(rs, new Comparator<Rect>() { @Override public int compare(Rect lhs, Rect rhs) { if (lhs.left > rhs.left) return 1; return -1; } }); if (rs.size() >= 2) { List<Rect> res = new ArrayList<Rect>(rs); Rect pre = rs.get(0), next = rs.get(1); //合并 for (int i = 1; i < rs.size(); i++) { //if相交 if (Rect.intersects(pre, next)) { int left = Math.min(pre.left, next.left); int right = Math.max(pre.right, next.right); res.remove(pre); res.remove(next); res.add(new Rect(left, y1, right, y2)); if(res.size() >= 2) { pre = rs.get(0); next = rs.get(1); } else { break; } } else { if((res.size() - i) >= 2) { pre = next; next = rs.get(i + 1); } else { break; } } } rs = res; } return rs; } public int sp2px(int spVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics()); } public int dp2px(int dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics()); } public int getTextSize() { return mTextSize; } public int getTextColor() { return mTextColor; } public String getText() { return mText; } }