立网站系,宁波专业外贸网站建设,建筑方案设计步骤,wordpress手机版跳转到页面今天来一起学习一下最简单的自定义view,自己动手写一个MyTextView,当然不会像系统的TextView那么复杂#xff0c;只是实现一下TextView的简单功能#xff0c;包括分行显示及自定义属性的处理#xff0c;主要目的是介绍自定义view的实现的基本思路和需要掌握的一些基础知识。…今天来一起学习一下最简单的自定义view,自己动手写一个MyTextView,当然不会像系统的TextView那么复杂只是实现一下TextView的简单功能包括分行显示及自定义属性的处理主要目的是介绍自定义view的实现的基本思路和需要掌握的一些基础知识。《一》先展示一下实现的最终效果image.png《二》实现步骤分析1、创建MyTextView extends View重写构造方法。一般是重写前三个构造方法让前两个构造方法最终调用三个参数的构造方法然后在第三个构造方法中进行一些初始化操作。2、在构造方法中进行一些初始化操作如初始化画笔及获取自定义属性等。如何自定义属性(1)在values下创建attrs.xml.//定义你的view可以在布局文件中配置的自定义属性(2)获取自定义属性TypedArray typedArray context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextViewApprence, defStyleAttr, 0);mText typedArray.getString(R.styleable.MyTextViewApprence_text);mTextColor typedArray.getColor(R.styleable.MyTextViewApprence_textColor, Color.BLACK);mTextSize (int) typedArray.getDimension(R.styleable.MyTextViewApprence_textSize, 15);showMode typedArray.getInt(R.styleable.MyTextViewApprence_showMode, 0);typedArray.recycle();3、重写OnDraw()方法在onDraw()中使用canvas绘制文字x,y为绘制的起点。需要注意两点(1)这里的x,y不是指的左上顶点而是左下顶点。(2)drawText绘制文字时是有规则的这个规则就是基线详细可阅读drawText()详解image.png//绘制每行文字的建议高度为Paint.FontMetrics fm mPaint.getFontMetrics();drawTextHeight (int) (fm.descent - fm.ascent);绘制文字的方法canvas.drawText(NonNull String text, float x, float y, NonNull Paint paint)4、到第三步其实就可以绘制出文字了但是会发现一个问题无论在布局文件中声明控件的宽高是wrap_content和match_parent,效果都是铺满了整个屏幕这个时候我们就需要重写onMesure()方法来测量控件的实际大小了分析View的源码protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}public static int getDefaultSize(int size, int measureSpec) {int result size;int specMode MeasureSpec.getMode(measureSpec);int specSize MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result specSize;break;}return result;}/**MeasureSpec 封装了父控件对其孩子的布局要求有大小和模式两种而模式则有三种模式public static class MeasureSpec {private static final int MODE_SHIFT 30;private static final int MODE_MASK 0x3 MODE_SHIFT;//父控件不强加任何约束给子控件它可以为它逍遥的任何大小public static final int UNSPECIFIED 0 MODE_SHIFT; //0//父控件给子控件一个精确的值public static final int EXACTLY 1 MODE_SHIFT; //1073741824//父控件给子控件竟可能最大的值public static final int AT_MOST 2 MODE_SHIFT; //-2147483648//设定尺寸和模式创建的统一约束规范public static int makeMeasureSpec(int size, int mode) {if (sUseBrokenMakeMeasureSpec) {return size mode;} else {return (size ~MODE_MASK) | (mode MODE_MASK);}}// 从规范中获取模式public static int getMode(int measureSpec) {return (measureSpec MODE_MASK);}//从规范中获取尺寸public static int getSize(int measureSpec) {return (measureSpec ~MODE_MASK);}}关于specMode测量的几种模式你需要知道它们的作用如下图。image.png可以看到我们的源码中调用是自身的getDefaultSize()方法然后在MeasureSpec.AT_MOST和MeasureSpec.EXACTLY全部返回的是specSize而specSize表示的是父控件剩余宽度也就是我们看到的全屏。所以默认onMeasure方法中wrap_content 和match_parent 的效果是一样的都是填充剩余的空间。所以我们重新onMesure()方法对wrap_content这种情况进行处理。Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//获取宽的模式int widthMode MeasureSpec.getMode(widthMeasureSpec);int heightMode MeasureSpec.getMode(heightMeasureSpec);//获取宽的尺寸int widthSize MeasureSpec.getSize(widthMeasureSpec);int heightSize MeasureSpec.getSize(heightMeasureSpec);Log.e(TAG, widthMode widthMode heightMode heightMode widthSize widthSize heightSize heightSize);//对wrap_content这种模式进行处理int width;int height;if (widthMode MeasureSpec.EXACTLY) {width widthSize;} else {//如果是wrap_content,我们需要得到控件需要多大的尺寸//首先丈量文本的宽度float textWidth;textWidth mTextBoundOther.width();//控件的宽度就是文本的宽度加上两边的内边距。内边距就是padding的值在构造方法执行完被赋值width (int) (getPaddingLeft() textWidth getPaddingRight());}if (heightMode MeasureSpec.EXACTLY) {height heightSize;} else {//如果是wrap_content,我们需要得到控件需要多大的尺寸//首先丈量文本的宽度float textHeight mTextBoundOther.height();//控件的宽度就是文本的宽度加上两边的内边距。内边距就是padding的值在构造方法执行完被赋值。遗留问题最后一行显示高度不够在这里加上10px处理height (int) (getPaddingTop() textHeight getPaddingBottom() 10);}//保存丈量结果setMeasuredDimension(width, height);}下面是实现了自动换行的TextView的完整代码package com.example.jojo.learn.customview;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.support.annotation.Nullable;import android.text.TextUtils;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;import android.view.View;import com.example.jojo.learn.R;import java.util.ArrayList;/*** Created by JoJo on 2018/7/27.* wechat:18510829974* description:自定义Textview*/public class MyTextView extends View {//文字内容private String mText;//文字大小private int mTextSize;//文字颜色private int mTextColor;//绘制的范围private Rect mTextBound;//绘制文字的画笔private Paint mPaint;private int mScreenWidth;private int mScreenHeight;private int baseLineY;private float ascent;private float descent;private float top;private float bottom;private int baseLineX;private Rect mMaxRect;private Rect mTextBoundOther;private String text This is a great day;private int drawTextHeight;public MyTextView(Context context) {this(context, null);}public MyTextView(Context context, Nullable AttributeSet attrs) {this(context, attrs, 0);}public MyTextView(Context context, Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray typedArray context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextViewApprence, defStyleAttr, 0);mText typedArray.getString(R.styleable.MyTextViewApprence_text);mTextColor typedArray.getColor(R.styleable.MyTextViewApprence_textColor, Color.BLACK);mTextSize (int) typedArray.getDimension(R.styleable.MyTextViewApprence_textSize, 15);showMode typedArray.getInt(R.styleable.MyTextViewApprence_showMode, 0);typedArray.recycle();//屏幕信息DisplayMetrics dm getResources().getDisplayMetrics();mScreenHeight dm.heightPixels;mScreenWidth dm.widthPixels;if (TextUtils.isEmpty(mText)) {mText Hello.....Hello.....Hello.....Hello.....Hello.....Hello.....Hello.....Hello.....Hello.....Hello.....Hello....;}init();}private void init() {//基线baseLineY mTextSize;baseLineX 0;//初始化画笔mPaint new Paint();mPaint.setColor(mTextColor);mPaint.setTextSize(mTextSize);mPaint.setAntiAlias(true);mPaint.setStrokeWidth(1);//获取绘制的宽高mTextBound new Rect();mPaint.getTextBounds(text, 0, text.length(), mTextBound);mTextBound.top baseLineY mTextBound.top;mTextBound.bottom baseLineY mTextBound.bottom;mTextBound.left baseLineX mTextBound.left;mTextBound.right baseLineX mTextBound.right;//获取文字所占区域最小矩形Log.e(TAG, mTextBound.toShortString());//换行的文字mTextBoundOther new Rect();mPaint.getTextBounds(mText, 0, mText.length(), mTextBoundOther);//计算各线在位置Paint.FontMetrics fm mPaint.getFontMetrics();ascent baseLineY fm.ascent;//当前绘制顶线descent baseLineY fm.descent;//当前绘制底线top baseLineY fm.top;//可绘制最顶线bottom baseLineY fm.bottom;//可绘制最低线//每行文字的绘制高度drawTextHeight (int) (fm.descent - fm.ascent);//字符串所占的高度和宽度int width (int) mPaint.measureText(mText);int height (int) (bottom - top);//文字绘制时可以占据的最大矩形区域mMaxRect new Rect(baseLineX, (int) (baseLineY fm.top), (baseLineX width), (int) (baseLineY fm.bottom));}private ArrayList mTextList new ArrayList();private float lineNum;//文字最终所占的行数private float spLineNum;//换行展示的对齐方式private int showMode;/*** 测量* 父控件不强加任何约束给子控件它可以为它逍遥的任何大小* public static final int UNSPECIFIED 0 MODE_SHIFT; //0* 父控件给子控件一个精确的值-match_parent* public static final int EXACTLY 1 MODE_SHIFT; //1073741824* 父控件给子控件竟可能最大的值-wrap_content* public static final int AT_MOST 2 MODE_SHIFT; //-2147483648** param widthMeasureSpec* param heightMeasureSpec*/Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//获取宽的模式int widthMode MeasureSpec.getMode(widthMeasureSpec);int heightMode MeasureSpec.getMode(heightMeasureSpec);//获取宽的尺寸int widthSize MeasureSpec.getSize(widthMeasureSpec);int heightSize MeasureSpec.getSize(heightMeasureSpec);Log.e(TAG, widthMode widthMode heightMode heightMode widthSize widthSize heightSize heightSize);//(1)实现文字自动换行显示//文字的宽度int mTextWidth mTextBoundOther.width();if (mTextList.size() 0) {//将文本分段int padding getPaddingLeft() getPaddingRight();int specMaxWidth widthSize - padding;//可显示文本的最大宽度//最大宽度大于文字所占宽度则一行就能显示完全if (specMaxWidth mTextWidth) {lineNum 1;mTextList.add(mText);} else {//超过一行需切割分行显示spLineNum mTextWidth * 1.0f / specMaxWidth;//如果有小数的话就进1if ((spLineNum ).contains(.)) {lineNum (float) (spLineNum 0.5);} else {lineNum spLineNum;}//每行展示的文字的长度int lineLength (int) (mText.length() / spLineNum);for (int i 0; i lineNum; i) {String lineStr;//判断是否可以一行展示if (mText.length() lineLength) {lineStr mText.substring(0, mText.length());} else {lineStr mText.substring(0, lineLength);}mTextList.add(lineStr);//内容切割完记录切割后的字符串重新赋值给mTextif (!TextUtils.isEmpty(mText)) {if (mText.length() lineLength) {mText mText.substring(0, mText.length());} else {mText mText.substring(lineLength, mText.length());}} else {break;}}}}//(2)下面对wrap_content这种模式进行处理int width;int height;if (widthMode MeasureSpec.EXACTLY) {width widthSize;} else {//如果是wrap_content,我们需要得到控件需要多大的尺寸//首先丈量文本的宽度float textWidth;if (mTextList.size() 1) {textWidth widthSize;} else {textWidth mTextBoundOther.width();}//控件的宽度就是文本的宽度加上两边的内边距。内边距就是padding的值在构造方法执行完被赋值width (int) (getPaddingLeft() textWidth getPaddingRight());}if (heightMode MeasureSpec.EXACTLY) {height heightSize;} else {//如果是wrap_content,我们需要得到控件需要多大的尺寸//首先丈量文本的宽度// float textHeight mTextBoundOther.height();float textHeight drawTextHeight * mTextList.size();//控件的宽度就是文本的宽度加上两边的内边距。内边距就是padding的值在构造方法执行完被赋值。遗留问题最后一行显示高度不够在这里加上10px处理height (int) (getPaddingTop() textHeight getPaddingBottom() 10);}//保存丈量结果setMeasuredDimension(width, height);}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);/*** 测试文字的绘制区域*/// //绘制字符串所占的矩形区域// mPaint.setColor(Color.GREEN);// canvas.drawRect(mMaxRect, mPaint);//// //绘制最小矩形// mPaint.setColor(Color.RED);// canvas.drawRect(mTextBound, mPaint);//// //绘制文字-绘制的起点是绘制文字所在矩形的左下角顶点// mPaint.setColor(Color.WHITE);// canvas.drawText(text, baseLineX, baseLineY, mPaint);//// //绘制基线// mPaint.setColor(Color.RED);// canvas.drawLine(0, baseLineY, mScreenWidth, baseLineY, mPaint);//// mPaint.setColor(Color.YELLOW);// canvas.drawLine(0, top, mScreenWidth, top, mPaint);// mPaint.setColor(Color.GREEN);// canvas.drawLine(0, ascent, mScreenWidth, ascent, mPaint);// mPaint.setColor(Color.BLACK);// canvas.drawLine(0, descent, mScreenWidth, descent, mPaint);// mPaint.setColor(Color.WHITE);// canvas.drawLine(0, bottom, mScreenWidth, bottom, mPaint);// 绘制Hello World !// canvas.drawText(text, getWidth() / 2 - mTextBoundOther.width() / 2, getHeight() / 2 mTextBoundOther.height() / 2, mPaint);//分行绘制文字for (int i 0; i mTextList.size(); i) {mPaint.getTextBounds(mTextList.get(i), 0, mTextList.get(i).length(), mTextBoundOther);//换行左对齐展示if (showMode 0) {canvas.drawText(mTextList.get(i), 0 getPaddingLeft(), (getPaddingTop() drawTextHeight * (i 1)), mPaint);} else if (showMode 1) {//换行居中展示canvas.drawText(mTextList.get(i), (getWidth() / 2 - mTextBoundOther.width() / 2) getPaddingLeft(), (getPaddingTop() drawTextHeight * (i 1)), mPaint);}}}/*** 控制文字对齐方式居中或者居左** param showMode*/public void reLayoutText(int showMode) {this.showMode showMode;invalidate();}}涉及到的自定义属性attrs.xml中在布局文件中使用测试代码xmlns:mytexthttp://schemas.android.com/apk/res-autoandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:backgroundcolor/colorAccentandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:backgroundandroid:color/whiteandroid:onClicktextLayoutLeftandroid:text文字居左对齐 /android:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_alignParentRighttrueandroid:backgroundandroid:color/whiteandroid:onClicktextLayoutCenterandroid:text文字居中对齐 /android:idid/mytextviewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_centerInParenttrueandroid:backgroundandroid:color/holo_red_darkmytext:showModecentermytext:text来一碗大的毒鸡汤无论做什么事情你首先要想到的不是你能得到什么而是你能接受失去什么当你无畏失去什么的时候你就变得无敌了。人生最重要的不是所站的位置是所站的位置是所站的位置是所站的位置是所站的位置你来自何处并不重要重要的是你要去往何方人生最重要的不是所站的位置而是所去的方向。人只要不失去方向就永远不会失去自己无论做什么事情你首先要想到的不是你能得到什么而是你能接受失去什么当你无畏失去什么的时候你就变得无敌了mytext:textColorandroid:color/whitemytext:textSize50px /欢迎各位读者一起来探索下面的待解决的问题1、中英文混排时展示有问题2、最后一行测量给的高度不够导致最后一行展示不全3、textSize的单位在布局文件中没有处理成sp而是px。如果需要处理成sp,可以参考系统TextView源码。image.png可以参考如下处理方式mTextSize (int) typedArray.getDimension(R.styleable.MyTextViewApprence_textSize, sp2px(mTextSize));/*** 将sp转换成px** param sp* return*/private int sp2px(int sp) {return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,getResources().getDisplayMetrics());}