Android 理化表达式MathExpressTextView定义

原创
2020/08/08 11:15
阅读数 137

一、可行性分析

在Android 中实现上下标我们一般使用SpannableString去完成,需要计算开始位置和结束位置,也要设置各种Span,而且动态性不是很好,因为无法做到规则统一约束,因此有必要进行专有规则设定,提高代码使用的灵活程度。

二、效果预览

三、代码实现

public class MathExpressTextView extends View {

	private final List<TextInfo> TEXT_INFOS = new ArrayList<>();
	private int  textSpace = 15;
	private String TAG = "MathExpressTextView";
    protected Paint mTextPaint;
	protected Paint mSubTextPaint;
	protected Paint mMarkTextPaint;

    protected float mContentWidth = 0f;
	protected float mContentHeight = 0f;
	protected float mMaxSize = 0;


	public void setMaxTextSize(float sizePx) {
		mMaxSize = sizePx;

		mTextPaint.setTextSize(mMaxSize);
		mSubTextPaint.setTextSize(mMaxSize / 3f);
		mMarkTextPaint.setTextSize(mMaxSize / 2f);

		invalidate();
	}

	public void setTextSpace(int textSpace) {
		this.textSpace = textSpace;
	}

	public void setContentHeight(float height){
		mContentHeight = height;
		invalidate();
    }


	public MathExpressTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
		setEidtDesignTextInfos();
	}

	public MathExpressTextView setText(String text, String subText, String supText, float space){
		this.TEXT_INFOS.clear();
		TextInfo.Builder tb = new TextInfo.Builder(text, mTextPaint, mSubTextPaint)
				.subText(subText)
				.supText(supText)
				.textSpace(space);

		this.TEXT_INFOS.add(tb.build());
		return this;
	}

	public MathExpressTextView appendMarkText(String text){
		TextInfo.Builder tb = new TextInfo.Builder(text, mMarkTextPaint, mMarkTextPaint);
		this.TEXT_INFOS.add(tb.build());
		return this;
	}

	public MathExpressTextView appendText(String text, String subText, String supText, float space){
		TextInfo.Builder tb = new TextInfo.Builder(text, mTextPaint, mSubTextPaint)
				.subText(subText)
				.supText(supText)
				.textSpace(space);

		this.TEXT_INFOS.add(tb.build());
		return this;
	}


	private void setEidtDesignTextInfos() {

		if(!isInEditMode()) return;
		setText("2H","2","",10);
		appendMarkText("+");
		appendText("O","2","",10);
		appendMarkText("=");
		appendText("2H","2","",10);
		appendText("O","","",10);

	}

	public Paint getTextPaint() {
		return mTextPaint;
	}

	public Paint getSubTextPaint() {
		return mSubTextPaint;
	}

	public Paint getMarkTextPaint() {
		return mMarkTextPaint;
	}

	private float dpTopx(int dp) {
		return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
	}


	private void init() {
        Typeface textFont = TypefaceFontManager.get(getContext(), "fonts/main_textfont.ttf");
        Typeface subTextFont = TypefaceFontManager.get(getContext(), "fonts/sub_textfont.ttf");

		mTextPaint = new Paint();
		mTextPaint.setColor(Color.WHITE);
		mTextPaint.setAntiAlias(true);
		mTextPaint.setTypeface(textFont);
		mTextPaint.setStyle(Paint.Style.STROKE);


		mMarkTextPaint = new Paint();
		mMarkTextPaint.setColor(Color.WHITE);
		mMarkTextPaint.setAntiAlias(true);
		mMarkTextPaint.setTypeface(textFont);
		mMarkTextPaint.setStyle(Paint.Style.STROKE);

		mSubTextPaint = new Paint();
		mSubTextPaint.setColor(Color.WHITE);
		mSubTextPaint.setAntiAlias(true);
		mSubTextPaint.setTypeface(subTextFont);
		mSubTextPaint.setStyle(Paint.Style.STROKE);

		setMaxTextSize(dpTopx(30));

	}

	private int[] colors = new int[] {
			0xC0FFFFFF, 0x9fFFFFFF,
			0x98FFFFFF, 0xA5FFFFFF,
			0xB3FFFFFF, 0xBEFFFFFF,
			0xCCFFFFFF, 0xD8FFFFFF,
			0xE5FFFFFF,0xFFFFFFFF };
	private float[] positions = new float[] {
			0f, 0.05f,
			0.3f, 0.4f,
			0.5f, 0.6f,
			0.7f, 0.8f,
			0.9f, 1f };

	public void setShaderColors(int[] colors) {
		this.colors = colors;
	}


	public void setShaderColors(int c){
		this.colors =  new int[]{c, c, c, c,
				c, c, c, c, c, c};
	}

	private void setSubTextShader() {
		if(this.colors!=null){
			float textHeight = mSubTextPaint.descent() - mSubTextPaint.ascent();
			float textOffset = (textHeight / 2) - mSubTextPaint.descent();
			Rect bounds = new Rect();
			mSubTextPaint.getTextBounds("%", 0, 1, bounds);
			mSubTextPaint.setShader(new LinearGradient(0,mContentHeight / 2 + textOffset - mMaxSize/100 * 22f, 0,
					mContentHeight / 2 + textOffset - mMaxSize/100 * 22f - bounds.height(), colors, positions, Shader.TileMode.CLAMP));
		}else{
			mSubTextPaint.setShader(null);
		}

	}
	private void setMarTextShader() {
		if(this.colors!=null){
			float textHeight = mMarkTextPaint.descent() - mMarkTextPaint.ascent();
			float textOffset = (textHeight / 2) - mMarkTextPaint.descent();
			Rect bounds = new Rect();
			mMarkTextPaint.getTextBounds("%", 0, 1, bounds);
			mMarkTextPaint.setShader(new LinearGradient(0,mContentHeight / 2 + textOffset - mMaxSize/100 * 22f, 0,
					mContentHeight / 2 + textOffset - mMaxSize/100 * 22f - bounds.height(), colors, positions, Shader.TileMode.CLAMP));
		}else{
			mMarkTextPaint.setShader(null);
		}

	}

	private void setTextShader() {
		if(this.colors!=null){
			float textHeight = mTextPaint.descent() - mTextPaint.ascent();
			float textOffset = (textHeight / 2) - mTextPaint.descent();
			Rect bounds = new Rect();
			mTextPaint.getTextBounds("A", 0, 1, bounds);
			mTextPaint.setShader(new LinearGradient(0, mContentHeight / 2 + textOffset, 0, mContentHeight / 2 + textOffset - bounds.height(), colors, positions, Shader.TileMode.CLAMP));
		}else{
			mTextPaint.setShader(null);
		}
	}


	public void setColor(int unitColor, int numColor){
		mSubTextPaint.setColor(unitColor);
		mTextPaint.setColor(numColor);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		if(mContentWidth<=0) {
			mContentWidth = getWidth();
		}
		if(mContentHeight<=0) {
			mContentHeight = getHeight();
		}

		if(mContentWidth==0 || mContentHeight==0) return;

		setTextShader();
		setSubTextShader();
		setMarTextShader();

		if(TEXT_INFOS.size()==0) return;

		int width = getWidth();
		int height = getHeight();

		RectF contentRect = new RectF();
		contentRect.left 	= (width-mContentWidth)/2f;
		contentRect.top 	= (height-mContentHeight)/2f;
		contentRect.right 	= contentRect.left + mContentWidth;
		contentRect.bottom 	= contentRect.top + mContentHeight;

		int id = canvas.save();
		float centerX = contentRect.centerX();
		float centerY = contentRect.centerY();
		canvas.translate(centerX,centerY);

		contentRect.left 	= -centerX;
		contentRect.right 	= centerX;
		contentRect.top 	= -centerY;
		contentRect.bottom 	= centerY;


		float totalTextWidth = 0l;
		int textCount = TEXT_INFOS.size();

		for (int i=0;i<textCount;i++){
			totalTextWidth += TEXT_INFOS.get(i).getTextWidth();
			if(i<textCount-1){
				totalTextWidth+= textSpace;
			}
		}

		drawGuideBaseline(canvas, contentRect, totalTextWidth);

		float startOffsetX = -(totalTextWidth)/2f;
		for (int i=0;i<textCount;i++){
			TEXT_INFOS.get(i).draw(canvas,startOffsetX,contentRect.centerY());
			startOffsetX += TEXT_INFOS.get(i).getTextWidth()+textSpace;
		}

		canvas.restoreToCount(id);


	}

	private void drawGuideBaseline(Canvas canvas, RectF contentRect, float totalTextWidth) {

		if(!isInEditMode()) return;

		Paint guidelinePaint = new Paint();
		guidelinePaint.setAntiAlias(true);
		guidelinePaint.setStrokeWidth(0);
		guidelinePaint.setStyle(Paint.Style.FILL);

		RectF hline = new RectF();
		hline.top = -1;
		hline.bottom = 1;
		hline.left 	= - totalTextWidth/2;
		hline.right = totalTextWidth/2;
		canvas.drawRect(hline,guidelinePaint);

		RectF vline = new RectF();
		hline.left =  -1;
		vline.top = contentRect.top;
		vline.bottom = contentRect.bottom;
		vline.right = 1;

		canvas.drawRect(vline,guidelinePaint);
	}


	private static class TextInfo{
		Paint subOrSupTextPaint = null;
		String subText = null;
		String supText = null;
		Paint textPaint =null ;
		String text;
		float space;

		private TextInfo(String text,String subText,String supText,Paint textPaint,Paint subOrSupTextPaint,float space) {
			this.text = text;
			if(this.text==null){
				this.text = "";
			}
			this.subText = subText;
			this.supText = supText;
			this.space = space;
			this.textPaint = textPaint;
			this.subOrSupTextPaint = subOrSupTextPaint;
		}

		public void draw(Canvas canvas, float startX,float startY) {

			if(this.textPaint==null){
				return;
			}

			canvas.drawText(this.text,startX,startY+getTextPaintBaseline(this.textPaint),this.textPaint);

			if(this.subOrSupTextPaint==null){
				return;
			}
			if(this.supText!=null){
				RectF rect = new RectF();
				rect.left 	= startX+space+getTextWidth(this.text,this.textPaint);
				rect.top 	= -getTextHeight(this.textPaint)/2;
				rect.bottom = 0;
				rect.right  = rect.left + getTextWidth(supText,this.subOrSupTextPaint);
				canvas.drawText(supText,rect.left,rect.centerY()+getTextPaintBaseline(this.subOrSupTextPaint),this.subOrSupTextPaint);
			}


			if(this.subText!=null){
				RectF rect = new RectF();
				rect.left 	= startX+space+getTextWidth(this.text,this.textPaint);
				rect.top 	= 0;
				rect.bottom = getTextHeight(this.textPaint)/2;
				rect.right  = rect.left + getTextWidth(subText,this.subOrSupTextPaint);
				canvas.drawText(subText,rect.left,rect.centerY()+getTextPaintBaseline(this.subOrSupTextPaint),this.subOrSupTextPaint);
			}

		}

		/**
		 * 基线到中线的距离=(Descent+Ascent)/2-Descent
		 注意,实际获取到的Ascent是负数。公式推导过程如下:
		 中线到BOTTOM的距离是(Descent+Ascent)/2,这个距离又等于Descent+中线到基线的距离,即(Descent+Ascent)/2=基线到中线的距离+Descent。
		 */
		public static float getTextPaintBaseline(Paint p) {
			Paint.FontMetrics fontMetrics = p.getFontMetrics();
			return (fontMetrics.descent - fontMetrics.ascent) / 2 -fontMetrics.descent;
		}


		public  float getTextWidth(){

			if(textPaint==null){
				return 0;
			}

			float width = 0;

			width =  getTextWidth(this.text,textPaint);

			float subTextWidth = 0;
			if(this.subText!=null && subOrSupTextPaint!=null){
				subTextWidth = getTextWidth(this.subText,subOrSupTextPaint)+space;
			}

			float supTextWidth = 0;
			if(this.supText!=null&&subOrSupTextPaint!=null){
				supTextWidth = getTextWidth(this.supText,subOrSupTextPaint)+space;
			}
			return width+Math.max(subTextWidth,supTextWidth);
		}


		//获取文本最小宽度(真实宽度)
		private static int getTextRealWidth(String text, Paint paint) {
			if(TextUtils.isEmpty(text)) return 0;
			Rect rect = new Rect(); // 文字所在区域的矩形
			paint.getTextBounds(text, 0, text.length(), rect);
			//获取最小矩形,该矩形紧贴文字笔画开始的位置
			return rect.width();
		}
		//获取文本最小高度(真实高度)
		private static int getTextRealHeight(String text, Paint paint) {
			if(TextUtils.isEmpty(text)) return 0;
			Rect rect = new Rect(); // 文字所在区域的矩形
			paint.getTextBounds(text, 0, text.length(), rect);
			//获取最小矩形,该矩形紧贴文字笔画开始的位置
			return rect.height();
		}

		//真实宽度 + 笔画左右两侧间隙(一般绘制的的时候建议使用这种,左右两侧的间隙和字形有关)
		private static  int getTextWidth(String text, Paint paint) {
			if(TextUtils.isEmpty(text)) return 0;
			return (int) paint.measureText(text);
		}
		//真实宽度 + 笔画上下两侧间隙(符合文本绘制基线)
		private static int getTextHeight(Paint paint) {
			Paint.FontMetricsInt fm = paint.getFontMetricsInt();
			int textHeight = ~fm.top - (~fm.top - ~fm.ascent) - (fm.bottom - fm.descent);
			return textHeight;
		}



		private   static class Builder{

			Paint subOrSupTextPaint = null;
			Paint textPaint =null ;
			String subText = null;
			String supText = null;
			String text;
			float space;

			public Builder(String text, Paint textPaint,Paint subOrSupTextPaint){
				this.text = text;
				this.textPaint = textPaint;
				this.subOrSupTextPaint = subOrSupTextPaint;
			}

			public Builder subText(String subText){
				this.subText = subText;
				return this;
			}

			public Builder supText(String supText){
				this.supText = supText;
				return this;
			}

			public Builder textSpace(float space){
				this.space  = space;
				return this;
			}

			public TextInfo build(){
				return new TextInfo(text,this.subText,this.supText,this.textPaint,this.subOrSupTextPaint,this.space);
			}
		}

	}

}

 

其中需要注意:getTextWidth和getTextSmallestWidth的区别。

四、使用方式

 MathExpressTextView m1 = findViewById(R.id.math_exp_1);
        MathExpressTextView m2 = findViewById(R.id.math_exp_2);
        MathExpressTextView m3 = findViewById(R.id.math_exp_3);
        MathExpressTextView m4 = findViewById(R.id.math_exp_4);


        m1.setShaderColors(0xffFF4081);
        m1.setText("2H","2","",10)
                .appendMarkText("+")
                .appendText("O","2","",10)
                .appendMarkText("=")
                .appendText("2H","2","",10)
                .appendText("O","","",10);

        m2.setShaderColors(0xffff9922);
        m2.setText("2","","2",10)
                .appendMarkText("+")
                .appendText("5","","-1",10)
                .appendMarkText("=")
                .appendText("4.2","","",10);

        m3.setShaderColors(0xffFFEAC4);
        m3.setText("H","2","0",10)
                .appendMarkText("+")
                .appendText("Cu","","+2",10)
                .appendText("O","","-2",10)
                .appendMarkText("==")
                .appendText("Cu","","0",10)
                .appendText("H","2","+1",10)
                .appendText("O","","-2",10);


        m4.setText("985","","GB",10)
                .appendMarkText("+")
                .appendText("211","","MB",10);

 

 

 

 

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部