700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 自定义TextView实现渐变色边框 渐变色文字并绘制drawable

自定义TextView实现渐变色边框 渐变色文字并绘制drawable

时间:2024-01-13 03:45:37

相关推荐

自定义TextView实现渐变色边框 渐变色文字并绘制drawable

工作需求,渐变色的边框和文字,还需要显示drawable。我们知道如果是View的背景色渐变,那么很好办,只需要写一个drawable文件,里面定义shape然后设置为View的background就行了。但是如果需要渐变色的文字,就得需要重写onDraw方法了,当然渐变色的边框也是这样的。如果重写了onDraw方法,即使设置了drawableLeft、drawableRight等drawable,也是会被覆盖掉的。那么怎么办呢?别急,我来给你一个解决方法。

首先看一下需要实现的效果吧!

说到自定义View,首先:在atts.xml中定义一些属性

<resources> <declare-styleable name="BorderTextView"> <attr name="drawable_src" format="reference"/> <attr name="imageHight" format="dimension"/> <attr name="imageWidth" format="dimension"/> <attr name="imageLocation"> <enum name="left" value="0"/> <enum name="top" value="1"/> <enum name="right" value="2"/> <enum name="bottom" value="3"/> </attr> </declare-styleable></resources>

第二步:定义一个名为“BorderTextView”的class,当然需要继承自TextView。然后呢?既然定义了属性,那么就得使用。在布局文件中使用

<com.qijukeji.customView.BorderTextView android:id="@+id/share_thirdFragment" android:layout_width="wrap_content" android:layout_height="32dp" android:layout_alignParentRight="true" android:gravity="center_vertical" android:layout_marginTop="8dp" android:layout_marginRight="10dp" android:textColor="@color/colorPrimary" android:drawablePadding="8dp" app:drawable_src="@drawable/share_orange_icon" app:imageHight="15sp" app:imageWidth="15sp" app:imageLocation="right" />

第三步:布局文件写好了,接下来就该在构造方法中获取到用户用到的参数了。

public BorderTextView(Context context) {this(context, null);}public BorderTextView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public BorderTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BorderTextView, defStyleAttr, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) {int attr = a.getIndex(i); switch (attr) {case R.styleable.BorderTextView_imageWidth:mWidth = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 0, getResources().getDisplayMetrics()));break; case R.styleable.BorderTextView_imageHight:mHight = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 0, getResources().getDisplayMetrics()));break; case R.styleable.BorderTextView_drawable_src:mImage = BitmapFactory.decodeResource(getResources(), a.getResourceId(attr, 0));break; case R.styleable.BorderTextView_imageLocation:mLocation = a.getInt(attr,LEFT);break; }}a.recycle();}

为了让各个构造方法最终都调用三个参数的构造方法,所以需要稍微修改一下前两个构造方法。

因为我们需要自定义的TextView大小想自己设置,所以必须重写onMeasure方法.

第四步:重写onMeasure方法,设置View的尺寸。

因为这个TextView里面包含有文字,所以我就用了文字占的宽高再加上图片的宽高设置的View的宽高。图片宽高固定为10sp,这里使用sp是为了和文字统一,因为文字的单位是sp。我设置了文字距离View的左侧留有10sp的距离,图片距离View右侧留有10sp的距离,文字和图片之间的距离是5sp,这样算下来,整个View的宽度就应该是 文字的宽度+图片的宽度10sp+左侧10sp+右侧10sp+中间5sp = 文字宽度+35sp。

宽度定下来了,轮到高度了。高度很简单,我就设置的View的高度是文字的高度+10sp。

需要注意的是:如果动态设置尺寸,就只能使用px作为单位,那么就需要将sp转换为px。方法?

/***将sp值转换为px值,保证文字大小不变**@paramspValue*@return*/public static int sp2px(Context context, float spValue) {final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f);}

文字宽度怎么得到呢?看代码吧!

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec); mPaint = getPaint(); mRect = new Rect(); String mTipText = getText().toString(); mPaint.getTextBounds(mTipText, 0, mTipText.length(), mRect); int textWidth = mRect.width(); int textHeight = mRect.height(); setMeasuredDimension(textWidth + Utils.sp2px(getContext(), 35), textHeight + Utils.sp2px(getContext(), 10));}

这里的getPaint方法可以得到TextPaint对象,然后通过getTextBounds方法,用Rect对象包裹了文字,这样mRect的宽度就是文字的宽度,mRect的高度就是文字的高度。

最后调用setMeasureDimension方法设置View的宽高。

接下来就是重点了,重写onDraw方法。

第五步:重写onDraw方法。

我的思路是先画渐变的文字,然后画图片,最后画边框。

先来绘制文字。

canvas.save(); mViewWidth = getMeasuredWidth(); mViewHeight = getMeasuredHeight(); mPaint = getPaint(); mRect = new Rect(); String mTipText = getText().toString(); mPaint.getTextBounds(mTipText, 0, mTipText.length(), mRect);// 关于LinearGradient的使用,// 推荐网页/u012702547/article/details/50821044,讲得很明白 mLinearGradient = new LinearGradient(0, 0, mViewWidth, mViewHeight,new int[]{0xFFFFBE7C, 0xFFFF6483},null, Shader.TileMode.MIRROR); mPaint.setShader(mLinearGradient);// -------------------------------绘制不一样大的文字-------------------------------------- mPaint.setTextSize(Utils.sp2px(getContext(), 11)); canvas.drawText(mTipText, 0, 1,getMeasuredWidth() / 2 - (mRect.width() + Utils.sp2px(getContext(), 15)) / 2,getMeasuredHeight() / 2 + mRect.height() / 2, mPaint); mPaint.setTextSize(Utils.sp2px(getContext(), 8)); canvas.drawText(mTipText, 2, 3,getMeasuredWidth() / 2 - mRect.width() / 2 + Utils.sp2px(getContext(), 10),getMeasuredHeight() / 2 + mRect.height() / 2, mPaint); mPaint.setTextSize(Utils.sp2px(getContext(), 16)); canvas.drawText(mTipText, 3, mTipText.length(),getMeasuredWidth() / 2 - mRect.width() / 2 + Utils.sp2px(getContext(), 18),getMeasuredHeight() / 2 + mRect.height() / 2, mPaint);// -------------------------------------------------------------------------------------// -----------------------------绘制一样大的文字------------------------------------------// canvas.drawText(mTipText,//getMeasuredWidth() / 2 - (mRect.width() + Utils.sp2px(getContext(), 15)) / 2,//getMeasuredHeight() / 2 + mRect.height() / 2, mPaint);// ------------------------------------------------------------------------------------- canvas.restore();

最开始我是设置了文字大小一样,那么代码就是注释掉的那部分。后来该需求,设置字体不一样大了,所以重新写

首先说一下颜色渐变,使用到了LinearGradient类,这个类支持线性渐变。参数的含义来看一下源码:

/** Create a shader that draws a linear gradient along a line.@paramx0The x-coordinate for the start of the gradient line@paramy0The y-coordinate for the start of the gradient line@paramx1The x-coordinate for the end of the gradient line@paramy1The y-coordinate for the end of the gradient line@paramcolorsThe colors to be distributed along the gradient line@parampositions May be null. The relative positions [0..1] of each corresponding color in the colors array. If this is null, the the colors are distributed evenly along the gradient line.@paramtile The Shader tiling mode*/public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], TileMode tile) {if (colors.length < 2) {throw new IllegalArgumentException("needs >= 2 number of colors"); }if (positions != null && colors.length != positions.length) {throw new IllegalArgumentException("color and position arrays must be of equal length"); }mType =TYPE_COLORS_AND_POSITIONS; mX0 = x0; mY0 = y0; mX1 = x1; mY1 = y1; mColors = colors; mPositions = positions; mTileMode = tile; init(nativeCreate1(x0, y0, x1, y1, colors, positions, tile.nativeInt));}

前四个参数分别是渲染的起始x、y和结束的x、y,这几个参数也表示了颜色渐变的方向是从左上角渐变到右下角。第五个参数是颜色值,线性渐变的话只需要给两个颜色值就可以了,一个起始颜色一个结束颜色。第六个参数是相对位置,当传入两个颜色值的时候直接传入null即可,如果给的颜色值比较多,那么positions的size需要跟colors的size一样,表示在positions[0]的颜色值colors[0], 在positions[1]的颜色值colors[1],以此类推。第七个参数是渐变的模式,有三种:

Shader.TileMode.CLAMP、//当渐变到结束的颜色之后,后面的颜色将一直使用endColor

Shader.TileMode.REPEAT、//当渐变到结束的颜色之后,会重复从startColor到endColor

Shader.TileMode.MIRROR //当渐变到结束的颜色之后,会镜像的重复颜色值

Paint对象的setShader方法允许我们使用渐变来绘制。

最后调用drawText就可以了。

接下来介绍一下6各参数的drawText方法。

/*** Draw the text, with origin at (x,y), using the specified paint.* The origin is interpreted based on the Align setting in the paint.**@paramtext The text to be drawn*@paramstartThe index of the first character in text to draw*@paramend (end - 1) is the index of the last character in text to draw*@paramx The x-coordinate of the origin of the text being drawn*@paramy The y-coordinate of the baseline of the text being drawn*@parampaintThe paint used for the text (e.g. color, size, style)*/public void drawText(@NonNull String text, int start, int end, float x, float y, @NonNull Paint paint) {if ((start | end | (end - start) | (text.length() - end)) < 0) {throw new IndexOutOfBoundsException(); }native_drawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);}

这个方法允许我们绘制text的一部分。第一个参数是要绘制的text,第二三个参数是要绘制的起始下标和结束下标,第四五个参数是想绘制在画布的哪个位置,最后一个参数是画笔对象。

第六步:绘制图片drawable。

canvas.save();drawPicture(canvas);//设置图片方法canvas.restore();

drawPicture的内容:

private void drawPicture(Canvas canvas) {if (mImage != null) {Drawable mDrawable; if (mHight != 0 && mWidth != 0) {mDrawable = new BitmapDrawable(getResources(), getRealBitmap(mImage)); } else {mDrawable = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(mImage, mImage.getWidth(), mImage.getHeight(), true)); }mDrawable.setBounds(mRect.width() + Utils.sp2px(getContext(), 15),getMeasuredHeight() / 2 - Utils.sp2px(getContext(), 6),mRect.width() + Utils.sp2px(getContext(), 25),getMeasuredHeight() / 2 + Utils.sp2px(getContext(), 6)); mDrawable.draw(canvas);// switch (mLocation) {//case LEFT://this.setCompoundDrawablesWithIntrinsicBounds(mDrawable, null,// null, null);//break;//case TOP://this.setCompoundDrawablesWithIntrinsicBounds(null, mDrawable,// null, null);//break;//case RIGHT://this.setCompoundDrawablesWithIntrinsicBounds(null, null,// mDrawable, null);//break;//case BOTTOM://this.setCompoundDrawablesWithIntrinsicBounds(null, null, null,// mDrawable);//break;// } }}

getRealBitmap是用来获取缩略图的,因为直接获取到的bitmap可能尺寸不合适,所以需要进行缩放。内容:

private Bitmap getRealBitmap(Bitmap image) {//根据需要Drawable原来的大小和目标宽高进行裁剪(缩放) int width = image.getWidth();// 获得图片的宽高 int height = image.getHeight(); // 取得想要缩放的matrix参数 float scaleWidth = (float) Utils.sp2px(getContext(), 10) / width; float scaleHeight = (float) Utils.sp2px(getContext(), 10) / height; Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight); // 返回新的Bitmap return Bitmap.createBitmap(image, 0, 0, width, height, matrix, true);}

在drawPicture方法中我先得到了要画的图片,然后调用setBounds方法设置了图片要绘制的top、left、bottom、right。可以理解为图片距离View的上下左右的距离。

然后调用draw方法画在画布上就行了。

第七步:绘制渐变色边框。

canvas.save();mPaint = new Paint();mRect = canvas.getClipBounds();mPaint.setAntiAlias(true);mRect.bottom--;mRect.right--;mPaint.setStyle(Paint.Style.STROKE);//设置边框宽度mPaint.setStrokeWidth(Utils.dip2Px(getContext(), 2));mPaint.setShader(mLinearGradient);RectF rectF = new RectF(mRect);canvas.drawRoundRect(rectF, 12, 12, mPaint);canvas.restore();

这里我们就需要重新初始化Paint和Rect对象了。注意在绘制边框的时候mRect是使用画布来获取clipBounds,注意与绘制文字的时候的不同。

设置好画笔空心、画笔宽度、然后就可以为画笔设置渐变的LinearGradient对象了。绘制矩形很简单,直接就是

canvas.drawRect(mRect,mPaint);

传入的第一个参数是Rect对象。

但是我现在需要绘制的是圆角矩形,就需要调用canvas.drawRoundRect方法来绘制了,但是找了一下,没有找到直接使用Rect对象来绘制的方法,但是有一个用RectF对象来绘制的方法,很好,我们需要用mRect来构造RectF对象。

RectF rectF = new RectF(mRect);

drawRoundRect方法的第二三个参数是圆角的x半径和y半径。

资源已经上传,想要看详细内容的请下载查看。地址:自定义BorderTextView

---------------------------------------------------------------------------------------------------Bug更新------------------------------------------------------------------------------------------------------------------

以上代码在初次运行没有问题,但是如果刷新了textview(setEnable等方法都会刷新),里面的字体还有图片的位置都会发还是能变化。

打印log查看各种宽度的值后发现,初次得到的包裹字体的Rect对象的宽高跟刷新后的不一样。仔细想了一下,猜测是因为onDraw里面设置的文字大小不一样,位置也不一样,第一次加载textview时是按照字体大小一样距离一样进行加载的,但是记载完后对字体位置和大小改变了,所以包裹字体的Rect的大小也发生变化了。因此,为修复此Bug,我统一使用第一次加载时的宽高进行重绘,这样就能保证刷新也不会出现这种情况。

因为onMeasure方法在onDraw方法前被调用,而我在onMeasure里面获取过一次包裹字体的Rect,因此直接得到的宽高设置成全局变量,以后避免修改这次得到的宽高,在使用这个宽高时直接调用已经保存的,而不是再次获取,这样就达到目的了。

将BorderTextView的onDraw和drawPicture方法中的mRect.width()和mRect.height()全都换成保存好的宽高,测试,OK,解决!

我可不会告诉你修改版的BorderTextView.java在这里哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。