1.前言
正在實(shí)習(xí)的公司最近特別忙诫舅,加上內(nèi)分泌失調(diào)羽利,順便TI7也開(kāi)始了(唯一指定冠軍!LGD是不可戰(zhàn)勝的?浮)这弧,以上種種娃闲,導(dǎo)致斷更了……下面進(jìn)入正文。
學(xué)會(huì)給女孩子化妝皇帮,是你走向人生巔峰的第一步。今天我們就走進(jìn)堪稱亞洲四大邪術(shù)之首的化妝術(shù)逞频,一起來(lái)剖析這門(mén)技術(shù)的幕后原理柒巫。
但是,不要跟我說(shuō)什么唇膏唇蜜唇釉唇彩!這種東西我不懂拴鸵!也不需要聘芜!作為一名Android程序員,老夫化妝向來(lái)都是一把梭庐扫!Paint萨醒!Martrix囤踩!合二為一涣仿!變白就走钧汹!
2.Paint基本使用
化妝屬于Paint的高級(jí)技巧,那么在學(xué)習(xí)之前嗤形,我們肯定是要把基礎(chǔ)都給夯扎實(shí)了叶组。
之前寫(xiě)過(guò)一篇Paint基礎(chǔ)臣淤,內(nèi)容大多數(shù)是枯燥的Api使用橄霉,考慮再三就給刪了。這些內(nèi)容完全可以參考官方文檔邑蒋,因此我只選出一些有意思的Api姓蜂。
Paint的使用主要可以分為兩大類,第一是圖形医吊、路徑的繪制钱慢,第二是文字的繪制。我們開(kāi)始吧~
2.1 繪制圖形與路徑
1.setStyle(Paint.Style style):設(shè)置畫(huà)筆樣式
Paint.Style.FILL :填充內(nèi)部
Paint.Style.FILL_AND_STROKE :填充內(nèi)部和描邊
Paint.Style.STROKE :僅描邊
這些樣式不難理解遮咖,顧名思義即可
2.setStrokeCap(Paint.Cap cap):設(shè)置線冒樣式
Paint.Cap.ROUND:圓形線冒
Paint.Cap.SQUARE:方形線冒
Paint.Cap.BUTT:無(wú)線冒)
這個(gè)不太好理解滩字,我們用代碼和圖片來(lái)說(shuō)明。
private void drawStrokeCap(Canvas canvas){
Paint paint = new Paint();
paint.setStrokeWidth(80);
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.BUTT);// 無(wú)線帽
canvas.drawLine(100,200,400,200,paint);
paint.setStrokeCap(Paint.Cap.SQUARE);// 方形線帽
canvas.drawLine(100,400,400,400,paint);
paint.setStrokeCap(Paint.Cap.ROUND);// 圓形線帽
canvas.drawLine(100,600,400,600,paint);
}
效果圖:
冒多出來(lái)的那塊區(qū)域就是線帽御吞,就相當(dāng)于給原來(lái)的直線加上一個(gè)帽子一樣麦箍。
3.setStrokeJoin(Paint.Join join):設(shè)置線段連接處樣式
Join.MITER:結(jié)合處為銳角
Join.ROUND:結(jié)合處為圓弧
Join.BEVEL:結(jié)合處為直線
代碼如下:
private void drawStrokeJoin(Canvas canvas){
...
Path path = new Path();
path.moveTo(100,100);
path.lineTo(450,100);
path.lineTo(100,300);
paint.setStrokeJoin(Paint.Join.MITER);//銳角
canvas.drawPath(path,paint);
path.moveTo(100,400);
path.lineTo(450,400);
path.lineTo(100,600);
paint.setStrokeJoin(Paint.Join.BEVEL);//直線
canvas.drawPath(path,paint);
path.moveTo(100,700);
path.lineTo(450,700);
path.lineTo(100,900);
paint.setStrokeJoin(Paint.Join.ROUND);//圓弧
canvas.drawPath(path,paint);
}
效果圖:
4.setPathEffect(PathEffect effect):設(shè)置繪制路徑的效果
這個(gè)方法很牛逼,根據(jù)PathEffect的不同陶珠,可以實(shí)現(xiàn)不同的效果挟裂,這里我舉兩個(gè)例子。
1.CornerPathEffect:圓形拐角效果
paint.setPathEffect(new CornerPathEffect(100));
利用半徑R=50的圓來(lái)代替原來(lái)兩條直線間的夾角
2.DashPathEffect:虛線效果
paint.setPathEffect(new DashPathEffect(new float[]{20,10,50,100},15));
intervals[]:表示組成虛線的各個(gè)線段的長(zhǎng)度揍诽;整條虛線就是由intervals[]中這些基本線段循環(huán)組成的诀蓉。
phase:表示開(kāi)始繪制的偏移值
代碼如下:
private void drawComposePathEffectDemo(Canvas canvas){
//畫(huà)原始路徑
Paint paint = getPaint();
Path path = getPath();
canvas.drawPath(path,paint);
//僅應(yīng)用圓角特效的路徑
canvas.translate(0,300);
CornerPathEffect cornerPathEffect = new CornerPathEffect(100);
paint.setPathEffect(cornerPathEffect);
canvas.drawPath(path,paint);
//僅應(yīng)用虛線特效的路徑
canvas.translate(0,300);
DashPathEffect dashPathEffect = new DashPathEffect(new float[]{2,5,10,10},0);
paint.setPathEffect(dashPathEffect);
canvas.drawPath(path,paint);
//利用ComposePathEffect先應(yīng)用圓角特效,再應(yīng)用虛線特效
canvas.translate(0,300);
ComposePathEffect composePathEffect = new ComposePathEffect(dashPathEffect,cornerPathEffect);
paint.setPathEffect(composePathEffect);
canvas.drawPath(path,paint);
//利用SumPathEffect,分別將圓角特效應(yīng)用于原始路徑,然后將生成的兩條特效路徑合并
canvas.translate(0,300);
paint.setStyle(Paint.Style.STROKE);
SumPathEffect sumPathEffect = new SumPathEffect(cornerPathEffect,dashPathEffect);
paint.setPathEffect(sumPathEffect);
canvas.drawPath(path,paint);
}
效果圖:
(5-8就是我們真正的化妝技術(shù)了!等會(huì)一個(gè)個(gè)詳細(xì)介紹的暑脆!這里先留個(gè)印象吧)
5.setXfermode(Xfermode xfermode):設(shè)置圖形重疊時(shí)的處理方式
如合并渠啤,取交集或并集,經(jīng)常用來(lái)制作橡皮的擦除效果
6.setMaskFilter(MaskFilter maskfilter):實(shí)現(xiàn)濾鏡的效果
如濾化添吗,立體等
7.setColorFilter(ColorFilter colorfilter):設(shè)置顏色過(guò)濾器
可以在繪制顏色時(shí)實(shí)現(xiàn)不用顏色的變換效果
8.setShader(Shader shader):設(shè)置圖像效果
使用Shader可以繪制出各種漸變效果
2.2 繪制文字
如果有的時(shí)候娜膘,你想在化妝的同時(shí)在你女朋友臉上寫(xiě)字沃暗,那么下面的知識(shí)點(diǎn)是一定要重點(diǎn)掌握的衣陶。
1.Typeface getTypeface() 樟氢、Typeface setTypeface(Typeface typeface):獲取與設(shè)置字體類型。
Android默認(rèn)有四種字體樣式:BOLD(加粗)、BOLD_ITALIC(加粗并傾斜)、ITALIC(傾斜)、NORMAL(正常)辰如。
我們也可以通過(guò)Typeface類來(lái)自定義個(gè)性化字體。這里有一個(gè)騷操作贵试,就是將圖標(biāo)直接放到ttf中琉兜,用起來(lái)很舒服。
2.Paint.Align getTextAlign() 锡移、void setTextAlign(Paint.Align align):獲取與設(shè)置文本對(duì)齊方式
取值為CENTER呕童、LEFT、RIGHT淆珊,也就是文字繪制是左邊對(duì)齊夺饲、右邊還是局中的。
3.int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth):判斷能顯示多少文字
private static final String STR = "ABCDEF";
mPaint.setTextSize(50);
float[] value = new float[1];
int ret = mPaint.breakText(STR, true, 200, value);
Log.i(TAG, "breakText="+ret+", STR="+STR.length()+", value="+value[1]);
//輸出結(jié)果如下:
//breakText=5, STR=8, value=195.0
2.3 繪制文字高級(jí)技巧
現(xiàn)在考慮這樣一種場(chǎng)景施符,自定義一個(gè)圓形的進(jìn)度條往声,在圓形中間顯示下載進(jìn)度。
乍一看感覺(jué)很簡(jiǎn)單戳吝,但是動(dòng)手之后就發(fā)現(xiàn)這里面有一個(gè)坑——下載進(jìn)度無(wú)法顯示在圓心浩销。
為什么呢?這兒就涉及到Android繪制文字的原理了听哭。在Paint中慢洋,我們可以找到一個(gè)叫FontMetrics
的內(nèi)部類,從名字上看陆盘,它和文字的測(cè)量時(shí)有關(guān)系的普筹,進(jìn)去看看
public static class FontMetrics {
/**
* The maximum distance above the baseline for the tallest glyph in
* the font at a given text size.
*/
public float top;
/**
* The recommended distance above the baseline for singled spaced text.
*/
public float ascent;
/**
* The recommended distance below the baseline for singled spaced text.
*/
public float descent;
/**
* The maximum distance below the baseline for the lowest glyph in
* the font at a given text size.
*/
public float bottom;
/**
* The recommended additional space to add between lines of text.
*/
public float leading;
}
這里一共定義了5個(gè)float類型的變量,我們用圖片來(lái)直觀的體現(xiàn)下他們的關(guān)系隘马。
top = top線的y坐標(biāo) - baseline線的y坐標(biāo)太防,不能超過(guò)
bottom = bottom線的y坐標(biāo) - baseline線的y坐標(biāo),不能超過(guò)
ascent = ascent線的y坐標(biāo) - baseline線的y坐標(biāo)酸员,是系統(tǒng)建議的高度蜒车,字母上有音標(biāo)符號(hào)時(shí),會(huì)超過(guò)
desent = desent線的y坐標(biāo) - baseline線的y坐標(biāo)幔嗦,可能超過(guò)
最坑的就是這個(gè)baseline酿愧,由于它不在文字的中心,而drawText()
中傳入的參數(shù)是以baseline為基準(zhǔn)的邀泉,因此在繪制時(shí)就會(huì)出現(xiàn)文字不在中心的情況嬉挡。
那么要如何解決呢叛氨?擁有數(shù)學(xué)頭腦的同學(xué)肯定一眼就看穿,中心點(diǎn)是可以計(jì)算出來(lái)的棘伴!
解:設(shè)文字中心到top、bottom的距離分別為A屁置、B焊夸,其中A=B;
設(shè)文字中心到baseLine的距離為C蓝角;
由圖可知
A=B = (bottom - top)/2
因?yàn)閎ottom = baseline + FontMetrics.bottom
且top = baseline + FontMetrics.top
所以A = B = (FontMetrics.bottom - FontMetrics.top)/ 2
又因?yàn)镃 = B - (bottom - baseline)= B - FontMetrics.bottom
且C = baseline - center
所以baseline - center = (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom
綜上所述
baseline = center +(FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom
計(jì)算完畢阱穗,是不是有點(diǎn)懷念自己的高中數(shù)學(xué)老師了?
2.4 實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)
現(xiàn)在我們知道了文字繪制的原理使鹅,又得到了中心點(diǎn)的計(jì)算公式揪阶,那么就來(lái)手寫(xiě)一個(gè)圓形的進(jìn)度條吧。
首先需要在創(chuàng)建attrs資源文件患朱,并且在其中自定義資源類型
<declare-styleable name="CircleProgressBar">
<attr name="progressColor" format="color"/>
<attr name="progressBackgroundColor" format="color"/>
<attr name="progressMax" format="float"/>
<attr name="circleWidth" format="dimension"/>
<attr name="textSize" format="dimension"/>
<attr name="textColor" format="color"/>
</declare-styleable>
接著自定義控件鲁僚,由于有自定義的資源類型,所以要在構(gòu)造方法中獲取他們裁厅,注意最后要typedArray.recycle()
冰沙。
public CircleProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
mProgressBackgroundColor = typedArray.getColor(R.styleable.CircleProgressBar_progressBackgroundColor, Color.GRAY);
mProgressColor = typedArray.getColor(R.styleable.CircleProgressBar_progressColor, Color.BLUE);
mProgressMax = typedArray.getFloat(R.styleable.CircleProgressBar_progressMax, 100);
mCircleWidth = typedArray.getDimension(R.styleable.CircleProgressBar_circleWidth, 20);
mTextSize = typedArray.getDimension(R.styleable.CircleProgressBar_textSize, 60);
mTextColor = typedArray.getColor(R.styleable.CircleProgressBar_textColor, Color.RED);
typedArray.recycle();
}
代碼主要是重寫(xiě)onDraw()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//畫(huà)圓環(huán)
mPaint = new Paint();
mPaint.setColor(mProgressBackgroundColor);
mPaint.setStrokeWidth(mCircleWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
int center = getWidth() / 2;
float radius = center - mCircleWidth / 2;
canvas.drawCircle(center, center, radius, mPaint);
//畫(huà)文字
mPaint = new Paint();
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
mPaint.setStrokeWidth(0);
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
int percent = (int) (mProgress / mProgressMax* 100) ;
String percentStr = percent + "%";
Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
canvas.drawText(percentStr,
center - mPaint.measureText(percentStr) / 2,
center + (fm.bottom - fm.top) / 2 - fm.bottom,
mPaint);
//畫(huà)圓弧
mPaint=new Paint();
mPaint.setColor(mProgressColor);
mPaint.setStrokeWidth(mCircleWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
RectF oval=new RectF(center-radius,center-radius,center+radius,center+radius);
canvas.drawArc(oval,0,mProgress/mProgressMax*360,false,mPaint);
}
重寫(xiě)onMeasure()
是為了解決wrap_content
的問(wèn)題。如果沒(méi)有加上這一段执虹,那么使用wrap_content
與match_parent
就沒(méi)有區(qū)別拓挥。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(300,300);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(300,heightSpecSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,300);
}
}
再給調(diào)用者提供一個(gè)設(shè)置進(jìn)度的方式,萬(wàn)事大吉袋励!
public void setProgress(int progress){
if(progress<0){
throw new IllegalArgumentException("進(jìn)度不能小于0侥啤!");
}
if(progress>mProgressMax){
mProgress= (int) mProgressMax;
}
if(progress<mProgressMax){
mProgress=progress;
//不知道應(yīng)用層在哪個(gè)線程調(diào)用
postInvalidate();
}
}
值得注意的是,在這里我們還沒(méi)有對(duì)padding進(jìn)行處理茬故。認(rèn)真閱讀奶奶系列的同學(xué)盖灸,自己解決一定是沒(méi)有問(wèn)題的,這里給出一個(gè)解決思路均牢,只要在onDraw()
中獲取這些值糠雨,并在canvas.drawXXX()
時(shí)加入即可。
int paddingLeft=getPaddingLeft();
int paddingRight=getPaddingRight();
int paddingTop=getPaddingTop();
int paddingBottom=getPaddingBottom();
最后來(lái)看看效果圖(挺丑的= =)
3.高級(jí)渲染
基礎(chǔ)的知識(shí)點(diǎn)大家應(yīng)該了解了不少徘跪,下面就一起來(lái)學(xué)習(xí)真正的化妝技巧吧甘邀。
首先是高級(jí)渲染部分。所謂高級(jí)渲染垮庐,其實(shí)就是通過(guò)setShader(Shader shader)
方法來(lái)設(shè)置圖像效果松邪。
Shader是著色器的意思,canvas.drawXXX()
畫(huà)出了具體的形狀哨查,而畫(huà)筆的shader則定義了圖形的著色和外觀逗抑。
3.1.BitmapShader
BitmapShader是指位圖圖像渲染,即用BitMap對(duì)繪制的圖形進(jìn)行渲染著色,簡(jiǎn)單來(lái)說(shuō)是用圖片對(duì)圖形進(jìn)行貼圖邮府。
在使用Shader時(shí)荧关,涉及到一個(gè)TileMode的概念,從名字上可以看出褂傀,這個(gè)參數(shù)就是用來(lái)設(shè)置拉伸模式的忍啤。也就是說(shuō),當(dāng)圖片小于容器的大小時(shí)仙辟,多出來(lái)的那部分要怎么畫(huà)同波。TileMode一共有三種類型:
TileMode.CLAMP 拉伸最后一個(gè)像素去鋪滿剩下的地方
TileMode.MIRROR 通過(guò)鏡像翻轉(zhuǎn)鋪滿剩下的地方。
TileMode.REPEAT 重復(fù)圖片平鋪整個(gè)畫(huà)面
具體該怎么使用就直接上代碼吧
private void BitmapShaderTest(Canvas canvas) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy2);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scale = Math.max(width, height) / Math.min(width, height);
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
android.graphics.Matrix matrix = new android.graphics.Matrix();
matrix.setScale(scale, scale);
bitmapShader.setLocalMatrix(matrix);
mPaint.setShader(bitmapShader);
canvas.drawRect(0,0,800,800,mPaint);
}
都是一些基本的套路叠国,接著看效果圖
注意觀察左側(cè)被拉伸的區(qū)域未檩,這就是TileMode.CLAMP的作用。剩下2個(gè)類型大家可以自己去玩玩看粟焊。
那么這玩意兒有什么用處呢冤狡?你可以和女朋友裝逼說(shuō)“用什么CircleImageView,直接用Paint畫(huà)一個(gè)不就好了吆玖?”筒溃。
只要把上面代碼最后一行canvas.drawRect(0,0,800,800,mPaint);
替換成canvas.drawCircle(height / 2, height / 2, height / 2, mPaint);
即可。
遺憾的是沾乘,如果你女朋友是會(huì)玩的并且學(xué)習(xí)過(guò)反裝逼怜奖,那她就會(huì)說(shuō),“這算什么翅阵,老娘不用你的Paint照樣能畫(huà)出來(lái)歪玲!”。于是唰唰唰一頓操作掷匠,你的代碼變成了這樣:
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
shapeDrawable.getPaint().setShader(bitmapShader);
shapeDrawable.setBounds(0,0,width,height);
shapeDrawable.draw(canvas);
效果圖就不放了滥崩,和上面的類似,只不過(guò)這次是個(gè)橢圓讹语。
3.1.1.BitmapShader放大鏡效果
結(jié)合上面的知識(shí)點(diǎn)钙皮,我們來(lái)學(xué)習(xí)第一個(gè)化妝技巧——放大妝!
首先顽决,我們需要原圖和放大圖兩張圖片短条。原圖直接展示在界面上,放大圖通過(guò)ShapeDrawable只展示出一塊圓形的區(qū)域才菠。
public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//原圖
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy3);
//縮放圖
mBitmapScale = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * FACTOR
, mBitmap.getHeight() * FACTOR, true);
mBitmapShader = new BitmapShader(mBitmapScale, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mShapeDrawable = new ShapeDrawable(new OvalShape());
mShapeDrawable.getPaint().setShader(mBitmapShader);
mShapeDrawable.setBounds(0,0,RADIUS*2,RADIUS*2);
mMatrix = new Matrix();
}
接著茸时,在ondraw()
方法中進(jìn)行繪制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap,0,0,null);
mShapeDrawable.draw(canvas);
}
最后,重寫(xiě)onTouchEvent()
實(shí)現(xiàn)自由放大赋访。需要理解的是可都,當(dāng)我的手指向右下方移動(dòng)時(shí)缓待,放大圖片本身是要向左上移動(dòng)的,而放大區(qū)域則是一直以手指觸摸點(diǎn)為圓心渠牲。
@Override
public boolean onTouchEvent(MotionEvent event) {
int x= (int) event.getX();
int y= (int) event.getY();
//將放大的圖片往相反的方向移動(dòng)
mMatrix.setTranslate(RADIUS-x*FACTOR,RADIUS-y*FACTOR);
mBitmapShader.setLocalMatrix(mMatrix);
//手指觸摸點(diǎn)為圓心
mShapeDrawable.setBounds(x-RADIUS,y-RADIUS,x+RADIUS,y+RADIUS);
invalidate();
return true;
}
放大妝的作用很明顯旋炒,比如你女朋友牙齒特別好看,那么出門(mén)的時(shí)候就可以給她來(lái)上這個(gè)一下签杈,把美麗的牙齒大方的展示出來(lái)国葬!我們來(lái)看看效果圖
3.2.LinearGradient
LinearGradient是線性渲染, 主要通過(guò)顏色的變化來(lái)進(jìn)行渲染芹壕。
關(guān)于基本使用,我們直接上代碼來(lái)看
private int[] mColors = {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW};
/**線性漸變
* x0, y0, 起始點(diǎn)
* x1, y1, 結(jié)束點(diǎn)
* int[] mColors, 中間依次要出現(xiàn)的幾個(gè)顏色
* float[] positions,數(shù)組大小跟colors數(shù)組一樣大接奈,中間依次擺放的幾個(gè)顏色分別放置在那個(gè)位置上(參考比例從左往右)
* tile
*/
private void LinearGradientTest(Canvas canvas) {
LinearGradient linearGradient = new LinearGradient(0, 0, 800, 800, mColors, null, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 800, 800, mPaint);
}
效果圖這樣
3.2.1.LinearGradient霓虹燈文字
下面這個(gè)妝就厲害了踢涌,是動(dòng)態(tài)的,還會(huì)來(lái)回轉(zhuǎn)序宦,我們看代碼
首先睁壁,這個(gè)自定義View要繼承TextView,這樣比較方便
public class LinearGradientTextView extends TextView
接著互捌,通過(guò)getPaint()
獲取到畫(huà)筆潘明,并為其設(shè)置LinearGradient
public LinearGradientTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//獲取屏幕寬度
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mPoint = new Point();
windowManager.getDefaultDisplay().getSize(mPoint);
String text = getText().toString();
//拿到TextView的畫(huà)筆
mPaint = getPaint();
mTextWidth = (int) mPaint.measureText(text);
int gradientTextSize = mTextWidth / text.length() * 3;
mLinearGradient = new LinearGradient(0, 0, gradientTextSize, 0, mColors, null, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
}
此時(shí)靜態(tài)的效果已經(jīng)出來(lái)了,剩下的就是重寫(xiě)onDraw()
實(shí)現(xiàn)動(dòng)態(tài)變換
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Matrix matrix = new Matrix();
mTranslate += DELTAX;
//反復(fù)循環(huán)
if (mTranslate > mPoint.x || mTranslate < 1) {
DELTAX = -DELTAX;
}
matrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(matrix);
postInvalidate();
}
國(guó)際慣例秕噪,看一張效果圖(gif什么的看上去好麻煩…大家根據(jù)代碼自己腦補(bǔ)吧)
3.3.SweepGradient
SweepGradient叫漸變渲染或者梯度渲染钳降,其效果與用法都和LinearGradient類似,還是先介紹基本用法
private void SweepGradientTest(Canvas canvas) {
SweepGradient mSweepGradient = new SweepGradient(300, 300, mColors, null);
mPaint.setShader(mSweepGradient);
canvas.drawRect(0, 0, 800,800, mPaint);
}
效果圖
3.3.1 SweepGradient雷達(dá)掃描
那么這倒霉玩意兒又能干啥呢腌巾?SweepGradient經(jīng)典的應(yīng)用場(chǎng)景就是實(shí)現(xiàn)雷達(dá)掃描效果遂填。
來(lái)看代碼,首先在構(gòu)造方法中初始化Paint
public RadarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
mRadarBg = new Paint(Paint.ANTI_ALIAS_FLAG); //設(shè)置抗鋸齒
mRadarBg.setColor(mRadarBgColor); //畫(huà)筆顏色
mRadarBg.setStyle(Paint.Style.FILL); //畫(huà)實(shí)心圓
mRadarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //設(shè)置抗鋸齒
mRadarPaint.setColor(mCircleColor); //畫(huà)筆顏色
mRadarPaint.setStyle(Paint.Style.STROKE); //設(shè)置空心的畫(huà)筆澈蝙,只畫(huà)圓邊
mRadarPaint.setStrokeWidth(2); //畫(huà)筆寬度
mRadarShader = new SweepGradient(0, 0, mStartColor, mEndColor);
mMatrix = new Matrix();
}
接著在onDraw()
方法中進(jìn)行繪制
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRadarBg.setShader(null);
//將畫(huà)板移動(dòng)到屏幕的中心點(diǎn)
canvas.translate(mRadarRadius, mRadarRadius);
//繪制底色吓坚,讓雷達(dá)的線看起來(lái)更清晰
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
//畫(huà)圓圈
for (int i = 1; i <= mCircleNum; i++) {
canvas.drawCircle(0, 0, (float) (i * 1.0 / mCircleNum * mRadarRadius), mRadarPaint);
}
//繪制雷達(dá)基線 x軸
canvas.drawLine(-mRadarRadius, 0, mRadarRadius, 0, mRadarPaint);
//繪制雷達(dá)基線 y軸
canvas.drawLine(0, mRadarRadius, 0, -mRadarRadius, mRadarPaint);
//設(shè)置顏色漸變從透明到不透明
mRadarBg.setShader(mRadarShader);
canvas.concat(mMatrix);
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
}
最后來(lái)個(gè)handler讓雷達(dá)動(dòng)起來(lái)吧
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mRotate += 3;
postInvalidate();
mMatrix.reset();
mMatrix.preRotate(mRotate, 0, 0);
mHandler.sendEmptyMessageDelayed(MSG_WHAT, DELAY_TIME);
}
};
效果圖長(zhǎng)這樣
3.4.RadialGradient
RadialGradient表示環(huán)形渲染,先來(lái)看下基本用法
private void RadialGradientTest(Canvas canvas) {
RadialGradient mRadialGradient = new RadialGradient(300, 300, 100, mColors, null, Shader.TileMode.REPEAT);
mPaint.setShader(mRadialGradient);
canvas.drawCircle(300, 300, 300, mPaint);
}
效果圖灯荧,正常人應(yīng)該是會(huì)暈的
3.4.1 RadialGradient水波紋效果
文章的最后礁击,終于輪到大名鼎鼎的水波紋效果了。
思路大致如下:使用RadialGradient響應(yīng)觸摸事件繪制出初始的圓形波紋逗载,接著使用屬性動(dòng)畫(huà)將圓形波紋變大哆窿。
由于屬性動(dòng)畫(huà)涉及到對(duì)屬性的設(shè)置,這里我們提供一個(gè)setRadius()
方法
public void setRadius(final int radius) {
mCurRadius = radius;
if (mCurRadius > 0) {
mRadialGradient = new RadialGradient(mX, mY, mCurRadius, 0x00FFFFFF, 0xFF58FAAC, Shader.TileMode.CLAMP);
mPaint.setShader(mRadialGradient);
}
postInvalidate();
}
重點(diǎn)都在onTouch()
方法中
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mX != event.getX() || mY != mY) {
mX = (int) event.getX();
mY = (int) event.getY();
setRadius(DEFAULT_RADIUS);
}
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_UP:
{
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
if (mAnimator == null) {
mAnimator = ObjectAnimator.ofInt(this,"radius",DEFAULT_RADIUS, getWidth());
}
mAnimator.setInterpolator(new AccelerateInterpolator());
mAnimator.addListener(new Animator.AnimatorListener() {
...
@Override
public void onAnimationEnd(Animator animation) {
setRadius(0);
}
...
});
mAnimator.start();
}
}
return super.onTouchEvent(event);
}
這里就通過(guò)屬性動(dòng)畫(huà)對(duì)radius值進(jìn)行了改變撕贞,關(guān)于屬性動(dòng)畫(huà)更耻,以后會(huì)從源碼入手好好講一講。最后只要在onDraw()里進(jìn)行簡(jiǎn)單的繪制即可捏膨。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mX, mY, mCurRadius, mPaint);
}
效果光看圖片不容易感受秧均,大伙將就著看食侮。
4.總結(jié)
這篇文章首先介紹了Paint的一些基礎(chǔ)用法,包括圖形目胡、路徑锯七、文字的繪制。接著將高級(jí)渲染進(jìn)行了細(xì)致講解誉己。其實(shí)還有最后一個(gè)ComposeShader沒(méi)有提眉尸,這是組合渲染,可以將前面說(shuō)的4種渲染方式隨意組合巨双,大家自由發(fā)揮即可噪猾。
Paint高級(jí)化妝技巧還包括Xfermode以及濾鏡的使用,下篇文章咱們就來(lái)懟他們倆筑累。
完結(jié)撒花~