swift xib 自定义vieww过程,三个测量模式,怎样从onmesure的两个参数中获取长度

Android 自定义View-图片文字变色,实现酷炫LoadingView或者进度条
大概半年之前,看过鸿洋大神的一篇博客
Android 自定义控件玩转字体变色 打造炫酷ViewPager指示器
他说大概想了32秒就知道了实现思路,这深深的刺痛了我。最近又看了一遍,决定做点什么
我要自定义的控件
我要自定义的控件是一个盖世英雄,
它不仅仅是一个Loading控件,同时还支持进度条 (ProgressBar)功能 。
它会在你需要的时候出现,
它支持 left,top,right,bottom 四个方向加载(变色),最重要的是,它可以是 文字,也可以是 图片,能够满足开发者一切需求。
如果你想用它来做LoadingView(图片文字都可以,下面用图片演示)
这是你想要的效果吗?限制太死?不会!你可以:
设置重新从底部加载,而不是从顶部折返 设置动画时间 设置是否重复执行
如果你想做进度条ProgressBar(图片文字都可以,下面用文字演示)
怎么样?看完效果是不是觉得还不错呢?
那么这样一个实用又酷炫的自定义控件到底有多难呢?
实现RLoadView
代码写到一半的时候我忽然理解了鸿洋的那个32秒。这样的控件,简单到爆。
原理:其实就是使用两种不同的颜色绘制两遍文字,通过裁剪画布控制两种颜色的展示
那么重点就是一个方法啦,裁剪画布 canvas.clipRect。知道这个方法的人简直小菜一碟。难怪鸿洋32秒搞定。
不过有一点还是值得注意的:绘制居中文字
绘制居中文字
凡事由简单到复杂。先实现文字类型的功能,之后再加一个图片功能,就几行代码的事情。
为什么说文字居中需要注意呢?以前学习的第一个自定义View应该就是从绘制文字开始,但是大家可能都没有注意到,网上使用的方式绘制居中文字是有问题的。在宽高设置为 wrap_content,并且不设置 padding 的情况下,文本是不能完整绘制的。
博客中详细的描述,测试,对比了文本居中绘制的各种情况,为了节省时间,简单总结为以下几点:
宽度测量使用:
int width=mPaint.measureText(mText);
mPaint.measureText(mText)精确度高于mBound.width()
高度测量使用:
FontMetrics fontMetrics = mPaint.getFontMetrics();
int height=Math.abs((fontMetrics.bottom - fontMetrics.top));
垂直居中方式:
FontMetricsInt fm = mPaint.getFontMetricsInt();
int startY = getHeight() / 2 - fm.descent + (fm.bottom - fm.top) / 2;
如果不明白的可以看上面那篇文章,很详细说明。
自定义控件
其实一切没多难,孰能生巧,现在写这个自定控件的步骤都腻死了。还是简单带过
自定义View的属性 在View的构造方法中获得我们自定义的属性 # 重写onMesure # 重写onDraw
1. 自定义View的属性
在 /value/attrs.xml 中,
text:文字,文字大小,文字默认颜色,文字高亮颜色
bitmap:默认图片,高亮图片
direction:图片/文字变色的方向,左到右,右到左,上到下,下到上
load_style:加载方式,图片或者文字
2. 在View的构造方法中获得我们自定义的属性
private static final float MAX = 100;
// 默认:文字正常时颜色
private static final int TEXT_COLOR_NORMAL = Color.parseColor("#000000");
// 系统默认:文字高亮颜色
private static final int TEXT_COLOR_HIGHLIGHT = Color.parseColor("#FF0000");
// 绘制方向
private static final int LEFT = 1, TOP = 2, RIGHT = 3, BOTTOM = 4;
// 文字样式
private static final int STYLE_TEXT = 1;
// 图片样式
private static final int STYLE_BITMAP = 2;
// 顺序绘制
private static final int LOAD_ASC = 0;
// 反向/降序绘制
private static final int LOAD_DESC = 1;
private Paint mP
* 绘制的范围
private Rect mB
* 控件绘制位置起始的X,Y坐标值
private int mStartX = 0, mStartY = 0;
* 文字大小
private int mTextSize = 16;
* 文字正常颜色
private int mTextColorNormal = TEXT_COLOR_NORMAL;
* 文字高亮颜色
private int mTextColorHighLight = TEXT_COLOR_HIGHLIGHT;
private String mT
* 绘制方向
private int mDirection = LEFT;
* 控件风格
private int mLoadStyle = STYLE_TEXT;
* bitmap正常/默认
private Bitmap mBitmapN
* bitmap高亮
private Bitmap mBitmapHighL
* loading刻度
private float mProgress = 0;
* 是否正在加载,避免开启多个线程绘图
private boolean mIsLoading =
* 是否终止线程运行
private boolean mCanRun =
* 加载方式{顺序,反向}
private int mLoadMode = LOAD_ASC;
public RLoadView(Context context) {
this(context, null);
public RLoadView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.RLoadView);
mText = a.getString(R.styleable.RLoadView_text);
mTextColorNormal = a.getColor(R.styleable.RLoadView_text_color_normal,
TEXT_COLOR_NORMAL);
mTextColorHighLight = a.getColor(
R.styleable.RLoadView_text_color_hightlight,
TEXT_COLOR_HIGHLIGHT);
mTextSize = a
.getDimensionPixelSize(R.styleable.RLoadView_text_size, 16);
mDirection = a.getInt(R.styleable.RLoadView_direction, LEFT);
mLoadStyle = a.getInt(R.styleable.RLoadView_load_style, STYLE_TEXT);
// 获取bitmap
mBitmapNormal = getBitmap(a, R.styleable.RLoadView_bitmap_src_normal);
mBitmapHighLight = getBitmap(a,
R.styleable.RLoadView_bitmap_src_hightlight);
a.recycle();
* 初始化画笔
mBound = new Rect();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.FILL);
if (mLoadStyle == STYLE_TEXT) {
mPaint.setTextSize(mTextSize);
mPaint.getTextBounds(mText, 0, mText.length(), mBound);
} else if (mLoadStyle == STYLE_BITMAP) {
mBound = new Rect(0, 0, mBitmapNormal.getWidth(),
mBitmapNormal.getHeight());
代码很简单,一眼带过就可以
注意一下文字和图片不同的绘制范围控制(mBound)
在获取图片的时候要考虑到 点9 图的情况,分两种情况获取
3. # 重写onMesure
重写onMesure 方法,重新测量控件的宽高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = onMeasureR(0, widthMeasureSpec);
int height = onMeasureR(1, heightMeasureSpec);
setMeasuredDimension(width, height);
* 计算控件宽高
* @param attr属性
* @param oldMeasure
* @author Ruffian
public int onMeasureR(int attr, int oldMeasure) {
int newSize = 0;
int mode = MeasureSpec.getMode(oldMeasure);
int oldSize = MeasureSpec.getSize(oldMeasure);
switch (mode) {
case MeasureSpec.EXACTLY:
newSize = oldS
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
float value = 0;
if (attr == 0) {
if (mLoadStyle == STYLE_TEXT) {
value = mPaint.measureText(mText);
} else if (mLoadStyle == STYLE_BITMAP) {
value = mBound.width();
// newSize
newSize = (int) (getPaddingLeft() + value + getPaddingRight());
} else if (attr == 1) {
if (mLoadStyle == STYLE_TEXT) {
FontMetrics fontMetrics = mPaint.getFontMetrics();
value = Math.abs((fontMetrics.bottom - fontMetrics.top));
} else if (mLoadStyle == STYLE_BITMAP) {
value = mBound.height();
// newSize
newSize = (int) (getPaddingTop() + value + getPaddingBottom());
return newS
文字和图片的宽高获取方式不同,需要判断获取。
可以先按照类型为文字的情况一路看下来思路比较清晰,理解了文字类型的绘制,再看图片的更容易接受。
4. 重写onDraw
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
* X,Y控件居中绘制
* 对于文本居中绘制
* 1.mPaint.measureText(mText)精确度高于mBound.width()
* 2.文字高度测量:Math.abs((fontMetrics.bottom - fontMetrics.top))
* 3.http://blog.csdn.net/u/article/details/
if (mLoadStyle == STYLE_TEXT) {
// 控件高度/2 + 文字高度/2,绘制文字从文字左下角开始,因此"+"
FontMetricsInt fm = mPaint.getFontMetricsInt();
mStartY = getMeasuredHeight() / 2 - fm.descent
+ (fm.bottom - fm.top) / 2;
mStartX = (int) (getMeasuredWidth() / 2 - mPaint.measureText(mText) / 2);
} else if (mLoadStyle == STYLE_BITMAP) {
mStartX = getMeasuredWidth() / 2 - mBound.width() / 2;
mStartY = getMeasuredHeight() / 2 - mBound.height() / 2;
onDrawR(canvas);
这段代码说明一下 mStartX ,mStartY
mStartX:开始绘制文字/图片的X轴起始坐标值,这里要求水平居中
mStartY:开始绘制文字/图片的Y轴起始坐标值,这里要求垂直居中
特别注意:中文本会中Y轴是从文字底部开始绘制,
text:mStartY=控件高度/2 + 文字高度/2,绘制文字从文字左下角开始,因此”+”
* 绘制文字或者图片
* @param canvas
* @param normalOrHightLight
[0:正常模式,1:高亮模式]
* @param start
* @param end
* @author Ruffian
protected void onDrawTextOrBitmap(Canvas canvas, int normalOrHightLight,
int start, int end) {
canvas.save(Canvas.CLIP_SAVE_FLAG);
switch (mDirection) {
case LEFT:
case RIGHT:
// X轴画图
canvas.clipRect(start, 0, end, getMeasuredHeight());
case BOTTOM:
// Y轴画图
canvas.clipRect(0, start, getMeasuredWidth(), end);
if (mLoadStyle == STYLE_TEXT) {
// 绘制文字
if (normalOrHightLight == 0) {
mPaint.setColor(mTextColorNormal);
mPaint.setColor(mTextColorHighLight);
canvas.drawText(mText, mStartX, mStartY, mPaint);
} else if (mLoadStyle == STYLE_BITMAP) {
// 绘制图片
if (normalOrHightLight == 0) {
canvas.drawBitmap(mBitmapNormal, mStartX, mStartY, mPaint);
canvas.drawBitmap(mBitmapHighLight, mStartX, mStartY, mPaint);
canvas.restore();
绘制文字或者图片的方法,保存当前画布之后进行裁剪画布在绘制文字。
比如说现在需要绘制“一串文字”的后半部分,就把前半部分裁减掉,然后绘制,当恢复画布之后效果就是前面一半是空白,后面一半是文字
* 控件绘制
* @param canvas
* @author Ruffian
public void onDrawR(Canvas canvas) {
* 主要思想:绘制两遍文字/图像,通过裁剪画布拼接两部分文字/图像,实现进度绘制的效果
// 需要变色的宽高总值(长度)
int drawTotalWidth = 0;
int drawTotalHeight = 0;
// X,Y变色的进度实时值
int spliteXPro = 0;
int spliteYPro = 0;
// X,Y变色的最大值(坐标)
int spliteXMax = 0;
int spliteYMax = 0;
// 开始变色的X,Y起始坐标值
int spliteYStart = 0;
int spliteXStart = 0;
FontMetricsInt fm = mPaint.getFontMetricsInt();
if (mLoadStyle == STYLE_TEXT) {
drawTotalWidth = (int) mPaint.measureText(mText);
drawTotalHeight = Math.abs(fm.ascent);
spliteYStart = (fm.descent - fm.top) - Math.abs(fm.ascent)
+ getPaddingTop();
// 开始裁剪的Y坐标值:(http://img.blog.csdn.net/27552)图中descent位置+paddingTop
spliteYMax = Math.abs(fm.top) + (fm.descent);
// Y变色(裁剪)的进度最大值(坐标):(http://img.blog.csdn.net/27552)看图
} else if (mLoadStyle == STYLE_BITMAP) {
drawTotalWidth = mBound.width();
drawTotalHeight = mBound.height();
spliteYStart = mStartY;// 开始裁剪的Y坐标值:图片开始绘制的地方
spliteYMax = mStartY + drawTotalH
// Y变色(裁剪)的进度最大值(坐标):图片开始绘制的地方+需要变色(裁剪)的高总值(长度)
spliteXPro = (int) ((mProgress / MAX) * drawTotalWidth);
spliteYPro = (int) ((mProgress / MAX) * drawTotalHeight);
spliteXStart = mStartX;// 开始裁剪的X坐标值:文字开始绘画的地方
spliteXMax = mStartX + drawTotalW
// X变色(裁剪)的进度最大值(坐标):X变色(裁剪)起始位置+需要变色(裁剪)的宽总值(长度)
switch (mDirection) {
// 从上到下,分界线上边是高亮颜色,下边是原始默认颜色
onDrawTextOrBitmap(canvas, 1, spliteYStart, spliteYStart
+ spliteYPro);
onDrawTextOrBitmap(canvas, 0, spliteYStart + spliteYPro, spliteYMax);
case BOTTOM:
// 从下到上,分界线下边是默认颜色 ,上边是高亮颜色
onDrawTextOrBitmap(canvas, 0, spliteYStart, spliteYMax - spliteYPro);
onDrawTextOrBitmap(canvas, 1, spliteYMax - spliteYPro, spliteYMax);
case LEFT:
// 从左到右,分界线左边是高亮颜色,右边是原始默认颜色
onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart
+ spliteXPro);
onDrawTextOrBitmap(canvas, 0, spliteXStart + spliteXPro, spliteXMax);
case RIGHT:
// 从右到左,分界线左边是默认颜色 ,右边是高亮颜色
onDrawTextOrBitmap(canvas, 0, spliteXStart, spliteXMax - spliteXPro);
onDrawTextOrBitmap(canvas, 1, spliteXMax - spliteXPro, spliteXMax);
这个方法是控制裁剪起始和终止坐标值的关键方法。
可以结合这篇博客理解
说一下思路,思路很关键啊,拿笔记好啦。哈哈
文章开头就说了,文字/图片变色原理是使用两种不同的颜色(或者两张图片)绘制两遍文字/图片
先看几个变量
// 需要变色的宽高总值(长度)
int drawTotalWidth = 0;
int drawTotalHeight = 0;
// X,Y变色的进度实时值
int spliteXPro = 0;
int spliteYPro = 0;
// X,Y变色的最大值(坐标)
int spliteXMax = 0;
int spliteYMax = 0;
// 开始变色的X,Y起始坐标值
int spliteYStart = 0;
int spliteXStart = 0;
需要变色的宽度总值(drawTotalWidth ):这里就是文字或者图片自身的宽度 X变色的进度实时值(spliteXPro ):表示变色进度的实时值。
比如变色到50%的时候 spliteXPro = (int) (( 50 / MAX) * drawTotalWidth); X开始变色的起始坐标值(spliteXStart ):记住这是一个坐标值,从哪里开始变色。 X变色的最大值(spliteXMax ):表示最多能变色到什么位置 ,记住这是一个坐标值,所以需要加上起始值。
spliteXMax = spliteXStart + drawTotalW
好好理解这几个概念,接下来就是绘制控件了。
绘制的原理都是一样的,只要理解了,从哪个方向开始变色都是一样的,无非是起始值和结束值的计算。计算之前一定要搞清楚上面的几个概念,不然,呵呵。说句不炫耀的话,写代码的时候,我差点把自己弄晕。
#以从左到右变色为例讲解变色原理
// 从左到右,分界线左边是高亮颜色,右边是原始默认颜色
onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart + spliteXPro);
onDrawTextOrBitmap(canvas, 0, spliteXStart + spliteXPro, spliteXMax);
onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart + spliteXPro);
第一个参数表示画布,
第二个参数表示文本类型(1:高亮,0:默认),
第三个参数变色开始的X值,
第四个参数变色结束X值
先绘制一遍高亮颜色的文字,再绘制一遍默认颜色的。
开始变色 &- - - - - - - - - - 变色最大值
高亮颜色从 开始变色的地方,绘制到,某个值停止
默认颜色从 高亮颜色停止的位置,绘制到,变色最大值位置
从progress=0,到progress=100,随着progress增加 高亮颜色慢慢向右递增,默认颜色慢慢递减,形成变色效果
其实就是画布裁剪出一部分来绘制 高亮颜色,然后再裁剪出一部分颜色来绘制 默认颜色。
如果进度停在50%,就会看到一半高亮,一半默认
OK,看看这个自定义类里面都有哪些方法
RLoadView:构造方法,获取基本属性 #onMeasure:重写onMeasure方法计算控件宽高 onMeasureR():自定义方法,计算控件宽高 #onDraw:重写onDraw方法,绘制控件 onDrawR():自定义方法,绘制控件之前计算,处理 onDrawTextOrBitmap:自定义方法,绘制文本/图片逻辑 start:自定义方法,开始执行文本/图片变色 stop:自定义方法,结束文本/图片变色 getProgress:获取进度值 setProgress:设置进度值 getBitmap:获取图片属性,区分 点9图 #onSaveInstanceState:重写方法,保存信息(进度值等) #onRestoreInstanceState:重写方法,重新设置信息(进度值等)
其中对外提供的方法:start,stop,getProgress,setProgress
public void start(final long duration, final boolean isRepeat, final boolean isReverse)
start 开始执行loading,使用在 LoadingView 情形中
第一参数:执行时间,设置变色执行的时间
第二个参数:是否循环重新变色,true循环变色,并且重头开始变色
第三个参数:是否反向褪色,true则表示变色完成之后反向褪色
可以自由组合第二第三个参数,实现不同的效果。
public void stop()
停止变色。配合 start 使用,由于start开启子线程实现变色,通过stop停止线程执行,停止变色。
setProgress
public void setProgress(float progress)
设置进度值。使用在ProgressBar 情形中。
在下载文件情形下,实时设置progress,方法会重绘控件,更新进度条。
getProgress
public float getProgress()
获取进度值。更适合使用在 ProgressBar 情形中。
在 LoadingView 情形中获取进度值没有意义,会在0-100之间不断变化。
在xml布局文件中使用控件
Activity中代码调用
//获取自定义控件
RLoadView mLoadView = (RLoadView) findViewById(R.id.id_loadView);
* 使用情形1:LoadingView
//变色时间,变色模式,开始LoadingView
mLoadView.start(1500, true, false);
//停止Loading
mLoadView.stop();
* 使用情形2:ProgressBar
//设置进度值,模拟下载设置下载进度值
mLoadView.setProgress(mProgress);
//获取进度值,模拟下载获取已下载进度值
mLoadView.getProgress();
使用起来从未如此简单,方便。两行代码搞定一切的既视感!关键是效果酷炫!
还在等什么?赶快下载体验吧
这是一条华丽的分割线
顺便说一下另外一个自定义控件,简单实用的ViewPageIndicator,RVPIndicator
1.什么是 RVPIndicator
简单实用的ViewPageIndicator,支持item自身滚动
高仿MIUI但更胜于MIUI,提供多种指示器类型{下滑线,三角形,全背景}
觉得这不满足你的需求?没问题,RVPIndicator 还支持使用图片作为指示器。一张图实现你的愿望
不会作图?你想自定义?OK,添加两三行代码就可以增加新的指示器样式
2. RVPIndicator 使用
2.1 自定义属性解释
rvp:indicator_color="#f29b76"
//指示器颜色
rvp:indicator_src="@drawable/heart_love"
//指示器图片{指示器类型为bitmap时需要}
rvp:indicator_style="triangle"
//指示器类型
//{bitmap:图片;line:下划线;square:方形全背景;triangle:三角形}
rvp:item_count="4"
//item展示个数
rvp:text_color_hightlight="#FF0000"
//item文字高亮颜色
rvp:text_color_normal="#fb9090"
//item文字正常颜色
2.2 代码调用
// 设置Tab上的标题
mIndicator.setTabItemTitles(mDatas);
// 设置关联的ViewPager
mIndicator.setViewPager(mViewPager, 0);
敢不敢留个言,点个赞,证明真的有人在看,哈哈Android 自定义View-图片文字变色,实现酷炫LoadingView或者进度条
Android 自定义View-图片文字变色,实现酷炫LoadingView或者进度条
【Android 自定义控件】
大概半年之前,看过鸿洋大神的一篇博客
他说大概想了32秒就知道了实现思路,这深深的刺痛了我。最近又看了一遍,决定做点什么
我要自定义的控件是一个盖世英雄,
它不仅仅是一个Loading控件,同时还支持进度条 (ProgressBar)功能 。
它会在你需要的时候出现,
它支持 left,top,right,bottom 四个方向加载(变色),最重要的是,它可以是 文字,也可以是 图片,能够满足开发者一切需求。
如果你想用它来做LoadingView(图片文字都可以,下面用图片演示)
这是你想要的效果吗?限制太死?不会!你可以:
设置重新从底部加载,而不是从顶部折返
设置动画时间
设置是否重复执行
如果你想做进度条ProgressBar(图片文字都可以,下面用文字演示)
怎么样?看完效果是不是觉得还不错呢?
那么这样一个实用又酷炫的自定义控件到底有多难呢?
实现RLoadView
代码写到一半的时候我忽然理解了鸿洋的那个32秒。这样的控件,简单到爆。
原理:其实就是使用两种不同的颜色绘制两遍文字,通过裁剪画布控制两种颜色的展示
那么重点就是一个方法啦,裁剪画布 canvas.clipRect。知道这个方法的人简直小菜一碟。难怪鸿洋32秒搞定。
不过有一点还是值得注意的:绘制居中文字
绘制居中文字
凡事由简单到复杂。先实现文字类型的功能,之后再加一个图片功能,就几行代码的事情。
为什么说文字居中需要注意呢?以前学习的第一个自定义View应该就是从绘制文字开始,但是大家可能都没有注意到,网上使用的方式绘制居中文字是有问题的。在宽高设置为 wrap_content,并且不设置 padding 的情况下,文本是不能完整绘制的。
后来就特地去研究了一下。关于文字居中我也顺便写了一篇博客。
博客中详细的描述,测试,对比了文本居中绘制的各种情况,为了节省时间,简单总结为以下几点:
宽度测量使用:
int width=mPaint.measureText(mText);
mPaint.measureText(mText)精确度高于mBound.width()
高度测量使用:
FontMetrics fontMetrics = mPaint.getFontMetrics()
int height=Math.abs((fontMetrics.bottom - fontMetrics.top))
垂直居中方式:
FontMetricsInt fm = mPaint.getFontMetricsInt()
int startY = getHeight() / 2 - fm.descent + (fm.bottom - fm.top) / 2
如果不明白的可以看上面那篇文章,很详细说明。
自定义控件
其实一切没多难,孰能生巧,现在写这个自定控件的步骤都腻死了。还是简单带过
自定义View的属性
在View的构造方法中获得我们自定义的属性
# 重写onMesure #
重写onDraw
1. 自定义View的属性
在 /value/attrs.xml 中,
&?xml version="1.0" encoding="utf-8"?&
name="text" format="string" /&
name="text_size" format="dimension" /&
name="text_color_normal" format="color" /&
name="text_color_hightlight" format="color" /&
name="bitmap_src_normal" format="reference" /&
name="bitmap_src_hightlight" format="reference" /&
name="direction"&
name="left" value="1" /&
name="top" value="2" /&
name="right" value="3" /&
name="bottom" value="4" /&
name="load_style"&
name="text" value="1" /&
name="bitmap" value="2" /&
name="RLoadView"&
name="text" /&
name="text_size" /&
name="text_color_normal" /&
name="text_color_hightlight" /&
name="direction" /&
name="load_style" /&
name="bitmap_src_normal" /&
name="bitmap_src_hightlight" /&
text:文字,文字大小,文字默认颜色,文字高亮颜色
bitmap:默认图片,高亮图片
direction:图片/文字变色的方向,左到右,右到左,上到下,下到上
load_style:加载方式,图片或者文字
在View的构造方法中获得我们自定义的属性
private static final float MAX = 100;
private static final int TEXT_COLOR_NORMAL = Color.parseColor("#000000");
private static final int TEXT_COLOR_HIGHLIGHT = Color.parseColor("#FF0000");
private static final int LEFT = 1, TOP = 2, RIGHT = 3, BOTTOM = 4;
private static final int STYLE_TEXT = 1;
private static final int STYLE_BITMAP = 2;
private static final int LOAD_ASC = 0;
private static final int LOAD_DESC = 1;
private Paint mP
* 绘制的范围
private Rect mB
* 控件绘制位置起始的X,Y坐标值
private int mStartX = 0, mStartY = 0;
* 文字大小
private int mTextSize = 16;
* 文字正常颜色
private int mTextColorNormal = TEXT_COLOR_NORMAL;
* 文字高亮颜色
private int mTextColorHighLight = TEXT_COLOR_HIGHLIGHT;
private String mT
* 绘制方向
private int mDirection = LEFT;
* 控件风格
private int mLoadStyle = STYLE_TEXT;
* bitmap正常/默认
private Bitmap mBitmapN
* bitmap高亮
private Bitmap mBitmapHighL
* loading刻度
private float mProgress = 0;
* 是否正在加载,避免开启多个线程绘图
private boolean mIsLoading = false;
* 是否终止线程运行
private boolean mCanRun = true;
* 加载方式{顺序,反向}
private int mLoadMode = LOAD_ASC;
public RLoadView(Context context) {
this(context, null);
public RLoadView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.RLoadView);
mText = a.getString(R.styleable.RLoadView_text);
mTextColorNormal = a.getColor(R.styleable.RLoadView_text_color_normal,
TEXT_COLOR_NORMAL);
mTextColorHighLight = a.getColor(
R.styleable.RLoadView_text_color_hightlight,
TEXT_COLOR_HIGHLIGHT);
mTextSize = a
.getDimensionPixelSize(R.styleable.RLoadView_text_size, 16);
mDirection = a.getInt(R.styleable.RLoadView_direction, LEFT);
mLoadStyle = a.getInt(R.styleable.RLoadView_load_style, STYLE_TEXT);
mBitmapNormal = getBitmap(a, R.styleable.RLoadView_bitmap_src_normal);
mBitmapHighLight = getBitmap(a,
R.styleable.RLoadView_bitmap_src_hightlight);
a.recycle();
* 初始化画笔
mBound = new Rect();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.FILL);
if (mLoadStyle == STYLE_TEXT) {
mPaint.setTextSize(mTextSize);
mPaint.getTextBounds(mText, 0, mText.length(), mBound);
} else if (mLoadStyle == STYLE_BITMAP) {
mBound = new Rect(0, 0, mBitmapNormal.getWidth(),
mBitmapNormal.getHeight());
代码很简单,一眼带过就可以
注意一下文字和图片不同的绘制范围控制(mBound)
在获取图片的时候要考虑到 点9 图的情况,分两种情况获取
3. # 重写onMesure
重写onMesure 方法,重新测量控件的宽高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = onMeasureR(0, widthMeasureSpec);
int height = onMeasureR(1, heightMeasureSpec);
setMeasuredDimension(width, height);
* 计算控件宽高
* attr属性
* oldMeasure
public int onMeasureR(int attr, int oldMeasure) {
int newSize = 0;
int mode = MeasureSpec.getMode(oldMeasure);
int oldSize = MeasureSpec.getSize(oldMeasure);
switch (mode) {
case MeasureSpec.EXACTLY:
newSize = oldS
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
float value = 0;
if (attr == 0) {
if (mLoadStyle == STYLE_TEXT) {
value = mPaint.measureText(mText);
} else if (mLoadStyle == STYLE_BITMAP) {
value = mBound.width();
newSize = (int) (getPaddingLeft() + value + getPaddingRight());
} else if (attr == 1) {
if (mLoadStyle == STYLE_TEXT) {
FontMetrics fontMetrics = mPaint.getFontMetrics();
value = Math.abs((fontMetrics.bottom - fontMetrics.top));
} else if (mLoadStyle == STYLE_BITMAP) {
value = mBound.height();
newSize = (int) (getPaddingTop() + value + getPaddingBottom());
return newS
文字和图片的宽高获取方式不同,需要判断获取。
可以先按照类型为文字的情况一路看下来思路比较清晰,理解了文字类型的绘制,再看图片的更容易接受。
4. 重写onDraw
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
* X,Y控件居中绘制 &br/&
* 对于文本居中绘制&br/&
* 1.mPaint.measureText(mText)精确度高于mBound.width()
* 2.文字高度测量:Math.abs((fontMetrics.bottom - fontMetrics.top))
* 3.http://blog.csdn.net/u/article/details/
if (mLoadStyle == STYLE_TEXT) {
FontMetricsInt fm = mPaint.getFontMetricsInt();
mStartY = getMeasuredHeight() / 2 - fm.descent
+ (fm.bottom - fm.top) / 2;
mStartX = (int) (getMeasuredWidth() / 2 - mPaint.measureText(mText) / 2);
} else if (mLoadStyle == STYLE_BITMAP) {
mStartX = getMeasuredWidth() / 2 - mBound.width() / 2;
mStartY = getMeasuredHeight() / 2 - mBound.height() / 2;
onDrawR(canvas);
这段代码说明一下 mStartX ,mStartY
mStartX:开始绘制文字/图片的X轴起始坐标值,这里要求水平居中
mStartY:开始绘制文字/图片的Y轴起始坐标值,这里要求垂直居中
特别注意:Android中文本会中Y轴是从文字底部开始绘制,
text:mStartY=控件高度/2 + 文字高度/2,绘制文字从文字左下角开始,因此”+”
* 绘制文字或者图片
* normalOrHightLight
[0:正常模式,1:高亮模式]
protected void onDrawTextOrBitmap(Canvas canvas, int normalOrHightLight,
int start, int end) {
canvas.save(Canvas.CLIP_SAVE_FLAG);
switch (mDirection) {
case LEFT:
case RIGHT:
canvas.clipRect(start, 0, end, getMeasuredHeight());
case BOTTOM:
canvas.clipRect(0, start, getMeasuredWidth(), end);
if (mLoadStyle == STYLE_TEXT) {
if (normalOrHightLight == 0) {
mPaint.setColor(mTextColorNormal);
mPaint.setColor(mTextColorHighLight);
canvas.drawText(mText, mStartX, mStartY, mPaint);
} else if (mLoadStyle == STYLE_BITMAP) {
if (normalOrHightLight == 0) {
canvas.drawBitmap(mBitmapNormal, mStartX, mStartY, mPaint);
canvas.drawBitmap(mBitmapHighLight, mStartX, mStartY, mPaint);
canvas.restore();
绘制文字或者图片的方法,保存当前画布之后进行裁剪画布在绘制文字。
比如说现在需要绘制“一串文字”的后半部分,就把前半部分裁减掉,然后绘制,当恢复画布之后效果就是前面一半是空白,后面一半是文字
* 控件绘制
public void onDrawR(Canvas canvas) {
* 主要思想:绘制两遍文字/图像,通过裁剪画布拼接两部分文字/图像,实现进度绘制的效果
int drawTotalWidth = 0;
int drawTotalHeight = 0;
int spliteXPro = 0;
int spliteYPro = 0;
int spliteXMax = 0;
int spliteYMax = 0;
int spliteYStart = 0;
int spliteXStart = 0;
FontMetricsInt fm = mPaint.getFontMetricsInt();
if (mLoadStyle == STYLE_TEXT) {
drawTotalWidth = (int) mPaint.measureText(mText);
drawTotalHeight = Math.abs(fm.ascent);
spliteYStart = (fm.descent - fm.top) - Math.abs(fm.ascent)
+ getPaddingTop();
spliteYMax = Math.abs(fm.top) + (fm.descent);
} else if (mLoadStyle == STYLE_BITMAP) {
drawTotalWidth = mBound.width();
drawTotalHeight = mBound.height();
spliteYStart = mStartY;
spliteYMax = mStartY + drawTotalH
spliteXPro = (int) ((mProgress / MAX) * drawTotalWidth);
spliteYPro = (int) ((mProgress / MAX) * drawTotalHeight);
spliteXStart = mStartX;
spliteXMax = mStartX + drawTotalW
switch (mDirection) {
onDrawTextOrBitmap(canvas, 1, spliteYStart, spliteYStart
+ spliteYPro);
onDrawTextOrBitmap(canvas, 0, spliteYStart + spliteYPro, spliteYMax);
case BOTTOM:
onDrawTextOrBitmap(canvas, 0, spliteYStart, spliteYMax - spliteYPro);
onDrawTextOrBitmap(canvas, 1, spliteYMax - spliteYPro, spliteYMax);
case LEFT:
onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart
+ spliteXPro);
onDrawTextOrBitmap(canvas, 0, spliteXStart + spliteXPro, spliteXMax);
case RIGHT:
onDrawTextOrBitmap(canvas, 0, spliteXStart, spliteXMax - spliteXPro);
onDrawTextOrBitmap(canvas, 1, spliteXMax - spliteXPro, spliteXMax);
这个方法是控制裁剪起始和终止坐标值的关键方法。如果看着很晕
可以结合这篇博客理解
说一下思路,思路很关键啊,拿笔记好啦。哈哈
文章开头就说了,文字/图片变色原理是使用两种不同的颜色(或者两张图片)绘制两遍文字/图片
先看几个变量
int drawTotalWidth = 0;
int drawTotalHeight = 0;
int spliteXPro = 0;
int spliteYPro = 0;
int spliteXMax = 0;
int spliteYMax = 0;
int spliteYStart = 0;
int spliteXStart = 0;
需要变色的宽度总值(drawTotalWidth ):这里就是文字或者图片自身的宽度
X变色的进度实时值(spliteXPro ):表示变色进度的实时值。
比如变色到50%的时候 spliteXPro = (int) (( 50 / MAX) * drawTotalWidth);
X开始变色的起始坐标值(spliteXStart ):记住这是一个坐标值,从哪里开始变色。
X变色的最大值(spliteXMax ):表示最多能变色到什么位置 ,记住这是一个坐标值,所以需要加上起始值。
spliteXMax = spliteXStart + drawTotalW
好好理解这几个概念,接下来就是绘制控件了。
绘制的原理都是一样的,只要理解了,从哪个方向开始变色都是一样的,无非是起始值和结束值的计算。计算之前一定要搞清楚上面的几个概念,不然,呵呵。说句不炫耀的话,写代码的时候,我差点把自己弄晕。
#以从左到右变色为例讲解变色原理
onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart + spliteXPro);
onDrawTextOrBitmap(canvas, 0, spliteXStart + spliteXPro, spliteXMax);
onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart + spliteXPro);
第一个参数表示画布,
第二个参数表示文本类型(1:高亮,0:默认),
第三个参数变色开始的X值,
第四个参数变色结束X值
先绘制一遍高亮颜色的文字,再绘制一遍默认颜色的。
开始变色 ↓- - - - - - - - - - 变色最大值
高亮颜色从 开始变色的地方,绘制到,某个值停止
默认颜色从 高亮颜色停止的位置,绘制到,变色最大值位置
从progress=0,到progress=100,随着progress增加 高亮颜色慢慢向右递增,默认颜色慢慢递减,形成变色效果
其实就是画布裁剪出一部分来绘制 高亮颜色,然后再裁剪出一部分颜色来绘制 默认颜色。
如果进度停在50%,就会看到一半高亮,一半默认
OK,看看这个自定义类里面都有哪些方法
RLoadView:构造方法,获取基本属性
#onMeasure:重写onMeasure方法计算控件宽高
onMeasureR():自定义方法,计算控件宽高
#onDraw:重写onDraw方法,绘制控件
onDrawR():自定义方法,绘制控件之前计算,处理
onDrawTextOrBitmap:自定义方法,绘制文本/图片逻辑
start:自定义方法,开始执行文本/图片变色
stop:自定义方法,结束文本/图片变色
getProgress:获取进度值
setProgress:设置进度值
getBitmap:获取图片属性,区分 点9图
#onSaveInstanceState:重写方法,保存信息(进度值等)
#onRestoreInstanceState:重写方法,重新设置信息(进度值等)
其中对外提供的方法:start,stop,getProgress,setProgress
public void start(final long duration, final boolean isRepeat, final boolean isReverse)
start 开始执行loading,使用在 LoadingView 情形中
第一参数:执行时间,设置变色执行的时间
第二个参数:是否循环重新变色,true循环变色,并且重头开始变色
第三个参数:是否反向褪色,true则表示变色完成之后反向褪色
可以自由组合第二第三个参数,实现不同的效果。
public void stop()
停止变色。配合 start 使用,由于start开启子线程实现变色,通过stop停止线程执行,停止变色。
setProgress
public void setProgress(float progress)
设置进度值。使用在ProgressBar 情形中。
在下载文件情形下,实时设置progress,方法会重绘控件,更新进度条。
getProgress
public float getProgress()
获取进度值。更适合使用在 ProgressBar 情形中。
在 LoadingView 情形中获取进度值没有意义,会在0-100之间不断变化。
在xml布局文件中使用控件
&cn.r.loadview.android.view.RLoadView
xmlns:rlv="/apk/res-auto"
android:id="@+id/id_loadView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center_horizontal"
rlv:bitmap_src_hightlight="@drawable/base_hightlight"
rlv:bitmap_src_normal="@drawable/base_normal"
rlv:direction="bottom"
rlv:load_style="bitmap"
rlv:text="Downloading..."
rlv:text_color_hightlight="#FF0000"
rlv:text_color_normal="#000000"
rlv:text_size="25sp" /&
Activity中代码调用
RLoadView mLoadView = (RLoadView) findViewById(R.id.id_loadView);
* 使用情形1:LoadingView
mLoadView.start(1500, true, false);
mLoadView.stop();
* 使用情形2:ProgressBar
mLoadView.setProgress(mProgress);
mLoadView.getProgress();
使用起来从未如此简单,方便。两行代码搞定一切的既视感!关键是效果酷炫!
还在等什么?赶快下载体验吧
这是一条华丽的分割线
顺便说一下另外一个自定义控件,简单实用的ViewPageIndicator,RVPIndicator
1.什么是 RVPIndicator
简单实用的ViewPageIndicator,支持item自身滚动
高仿MIUI但更胜于MIUI,提供多种指示器类型{下滑线,三角形,全背景}
觉得这不满足你的需求?没问题,RVPIndicator 还支持使用图片作为指示器。一张图实现你的愿望
不会作图?你想自定义?OK,添加两三行代码就可以增加新的指示器样式
2. RVPIndicator 使用
xmlns:android="/apk/res/android"
xmlns:tools="/tools"
xmlns:rvp="/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffffff"
android:orientation="vertical" &
android:id="@+id/id_indicator"
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="#ADD597"
android:orientation="horizontal"
rvp:indicator_color="#f29b76"
rvp:indicator_src="@drawable/heart_love"
rvp:indicator_style="triangle"
rvp:item_count="4"
rvp:text_color_hightlight="#FF0000"
rvp:text_color_normal="#fb9090" /&
android:id="@+id/id_vp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" /&
2.1 自定义属性解释
rvp:indicator_color="#f29b76"
//指示器颜色
rvp:indicator_src="@drawable/heart_love"
//指示器图片{指示器类型为bitmap时需要}
rvp:indicator_style="triangle"
//指示器类型
//{bitmap:图片;line:下划线;square:方形全背景;triangle:三角形}
rvp:item_count="4"
//item展示个数
rvp:text_color_hightlight="#FF0000"
//item文字高亮颜色
rvp:text_color_normal="#fb9090"
//item文字正常颜色
2.2 代码调用
mIndicator.setTabItemTitles(mDatas);
mIndicator.setViewPager(mViewPager, 0);
敢不敢留个言,点个赞,证明真的有人在看,哈哈
我的热门文章
即使是一小步也想与你分享}

我要回帖

更多关于 自定义textview 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信