編輯:關於Android編程
我要自定義的控件是一個蓋世英雄,
它不僅僅是一個Loading控件,同時還支持進度條 (ProgressBar)功能 。
它會在你需要的時候出現,
它支持 left,top,right,bottom 四個方向加載(變色),最重要的是,它可以是 文字,也可以是 圖片,能夠滿足開發者一切需求。
如果你想用它來做LoadingView(圖片文字都可以,下面用圖片演示)

這是你想要的效果嗎?限制太死?不會!你可以:
設置重新從底部加載,而不是從頂部折返 設置動畫時間 設置是否重復執行如果你想做進度條ProgressBar(圖片文字都可以,下面用文字演示)

怎麼樣?看完效果是不是覺得還不錯呢?
那麼這樣一個實用又酷炫的自定義控件到底有多難呢?
代碼寫到一半的時候我忽然理解了鴻洋的那個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在 /value/attrs.xml 中,
text:文字,文字大小,文字默認顏色,文字高亮顏色
bitmap:默認圖片,高亮圖片
direction:圖片/文字變色的方向,左到右,右到左,上到下,下到上
load_style:加載方式,圖片或者文字
// 最大值
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 mPaint;
/**
* 繪制的范圍
*/
private Rect mBound;
/**
* 控件繪制位置起始的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 mText;
/**
* 繪制方向
*/
private int mDirection = LEFT;
/**
* 控件風格
*/
private int mLoadStyle = STYLE_TEXT;
/**
* bitmap正常/默認
*/
private Bitmap mBitmapNormal;
/**
* bitmap高亮
*/
private Bitmap mBitmapHighLight;
/**
* 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);
// 獲取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 圖的情況,分兩種情況獲取
重寫onMesure 方法,重新測量控件的寬高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = onMeasureR(0, widthMeasureSpec);
int height = onMeasureR(1, heightMeasureSpec);
setMeasuredDimension(width, height);
}
/**
* 計算控件寬高
*
* @param attr屬性
* [0寬,1高]
* @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 = oldSize;
break;
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());
}
break;
}
return newSize;
}
文字和圖片的寬高獲取方式不同,需要判斷獲取。
可以先按照類型為文字的情況一路看下來思路比較清晰,理解了文字類型的繪制,再看圖片的更容易接受。
@Override
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/u014702653/article/details/51985821
*/
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軸起始坐標值,這裡要求垂直居中
特別注意:Android中文本會中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());
break;
case TOP:
case BOTTOM:
// Y軸畫圖
canvas.clipRect(0, start, getMeasuredWidth(), end);
break;
}
if (mLoadStyle == STYLE_TEXT) {
// 繪制文字
if (normalOrHightLight == 0) {
mPaint.setColor(mTextColorNormal);
} else {
mPaint.setColor(mTextColorHighLight);
}
canvas.drawText(mText, mStartX, mStartY, mPaint);
} else if (mLoadStyle == STYLE_BITMAP) {
// 繪制圖片
if (normalOrHightLight == 0) {
canvas.drawBitmap(mBitmapNormal, mStartX, mStartY, mPaint);
} else {
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/20160721172427552)圖中descent位置+paddingTop
spliteYMax = Math.abs(fm.top) + (fm.descent);
// Y變色(裁剪)的進度最大值(坐標):(http://img.blog.csdn.net/20160721172427552)看圖
} else if (mLoadStyle == STYLE_BITMAP) {
drawTotalWidth = mBound.width();
drawTotalHeight = mBound.height();
spliteYStart = mStartY;// 開始裁剪的Y坐標值:圖片開始繪制的地方
spliteYMax = mStartY + drawTotalHeight;
// Y變色(裁剪)的進度最大值(坐標):圖片開始繪制的地方+需要變色(裁剪)的高總值(長度)
}
spliteXPro = (int) ((mProgress / MAX) * drawTotalWidth);
spliteYPro = (int) ((mProgress / MAX) * drawTotalHeight);
spliteXStart = mStartX;// 開始裁剪的X坐標值:文字開始繪畫的地方
spliteXMax = mStartX + drawTotalWidth;
// X變色(裁剪)的進度最大值(坐標):X變色(裁剪)起始位置+需要變色(裁剪)的寬總值(長度)
switch (mDirection) {
case TOP:
// 從上到下,分界線上邊是高亮顏色,下邊是原始默認顏色
onDrawTextOrBitmap(canvas, 1, spliteYStart, spliteYStart
+ spliteYPro);
onDrawTextOrBitmap(canvas, 0, spliteYStart + spliteYPro, spliteYMax);
break;
case BOTTOM:
// 從下到上,分界線下邊是默認顏色 ,上邊是高亮顏色
onDrawTextOrBitmap(canvas, 0, spliteYStart, spliteYMax - spliteYPro);
onDrawTextOrBitmap(canvas, 1, spliteYMax - spliteYPro, spliteYMax);
break;
case LEFT:
// 從左到右,分界線左邊是高亮顏色,右邊是原始默認顏色
onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart
+ spliteXPro);
onDrawTextOrBitmap(canvas, 0, spliteXStart + spliteXPro, spliteXMax);
break;
case RIGHT:
// 從右到左,分界線左邊是默認顏色 ,右邊是高亮顏色
onDrawTextOrBitmap(canvas, 0, spliteXStart, spliteXMax - spliteXPro);
onDrawTextOrBitmap(canvas, 1, spliteXMax - spliteXPro, spliteXMax);
break;
}
}
這個方法是控制裁剪起始和終止坐標值的關鍵方法。
可以結合這篇博客理解
說一下思路,思路很關鍵啊,拿筆記好啦。哈哈
文章開頭就說了,文字/圖片變色原理是使用兩種不同的顏色(或者兩張圖片)繪制兩遍文字/圖片
先看幾個變量
// 需要變色的寬高總值(長度)
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 ):表示變色進度的實時值。好好理解這幾個概念,接下來就是繪制控件了。
繪制的原理都是一樣的,只要理解了,從哪個方向開始變色都是一樣的,無非是起始值和結束值的計算。計算之前一定要搞清楚上面的幾個概念,不然,呵呵。說句不炫耀的話,寫代碼的時候,我差點把自己弄暈。
// 從左到右,分界線左邊是高亮顏色,右邊是原始默認顏色 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停止線程執行,停止變色。
public void setProgress(float progress)
設置進度值。使用在ProgressBar 情形中。
在下載文件情形下,實時設置progress,方法會重繪控件,更新進度條。
public float getProgress()
獲取進度值。更適合使用在 ProgressBar 情形中。
在 LoadingView 情形中獲取進度值沒有意義,會在0-100之間不斷變化。
//獲取自定義控件 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();
使用起來從未如此簡單,方便。兩行代碼搞定一切的既視感!關鍵是效果酷炫!
還在等什麼?趕快下載體驗吧
Github:https://github.com/RuffianZhong/RLoadView
順便說一下另外一個自定義控件,簡單實用的ViewPageIndicator,RVPIndicator
高仿MIUI但更勝於MIUI,提供多種指示器類型{下滑線,三角形,全背景}

覺得這不滿足你的需求?沒問題,RVPIndicator 還支持使用圖片作為指示器。一張圖實現你的願望

不會作圖?你想自定義?OK,添加兩三行代碼就可以增加新的指示器樣式
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文字正常顏色
// 設置Tab上的標題
mIndicator.setTabItemTitles(mDatas);
// 設置關聯的ViewPager
mIndicator.setViewPager(mViewPager, 0);
Github:https://github.com/RuffianZhong/RVPIndicator
敢不敢留個言,點個贊,證明真的有人在看,哈哈
Android編程使用android-support-design實現MD風格對話框功能示例
本文實例講述了Android編程使用android-support-design實現MD風格對話框功能。分享給大家供大家參考,具體如下:首先上效果圖: 測試設備
android中的高級組件(一)progressBar、SeekBar、RatingBar等
ProgressBar作用:當應在後台執行時,前台界面不會有任何信息,這時用戶根本不知道程序是否在執行,以及執行進度等,因些需要使用進度條來提示程序執行的進度.在Andr
Android 繼承DialogFragment彈出dialog對話框二
之前寫過一篇關於Android 繼承DialogFragment彈出dialog對話框一,這次是在上次的基礎上修改了一些東西,就是怎樣在DialogFragment中獲取
淺談android中的自定義封裝易用的Dialog
好久沒寫android的博客,最近在做一個android的項目,裡面用到我們經常用的一個控件就是對話框,大家都知道android自帶的對話框是很丑的,android5.x
Android Multimedia框架總結(十九)Camera2框架C/S模型之CameraService啟動及與Client連接過程
Agenda:一張圖看Camera2框架類圖 CameraService