Paint
Paint類包含關(guān)于如何繪制幾何圖形枣宫,文本和位圖的樣式和顏色信息。
//設(shè)置抗鋸齒存炮,如果不設(shè)置隶校,加載位圖的時(shí)候可能會(huì)出現(xiàn)鋸齒狀的邊界仔涩,如果設(shè)置忍坷,邊界就會(huì)變的稍微有點(diǎn)模糊,鋸齒就看不到了
mPaint.setAntiAlias(boolean aa);
設(shè)置繪畫的風(fēng)格,用于控制原始圖形的幾何解釋(除了drawBitmap佩研,它總是假定為Fill)柑肴。
//設(shè)置繪制的風(fēng)格,用于控制原語(yǔ)的幾何體是如何解釋的(除了drawBitmap旬薯,它總是假定為Fill)晰骑。
mPaint.setStyle(Style style);
//設(shè)置描邊的寬度。 在發(fā)線模式下將0傳遞給筆劃绊序。 細(xì)線總是繪制一個(gè)獨(dú)立于Canva矩陣的像素硕舆。設(shè)置繪畫的筆畫寬度,每當(dāng)繪畫時(shí)使用風(fēng)格是Stroke或StrokeAndFill
mPaint.setStrokeWidth(3);
//用這種風(fēng)格繪制的幾何和文本將被填充骤公,忽略所有
中風(fēng)相關(guān)的設(shè)置在油漆抚官。
Paint.Style.FILL
//用這種風(fēng)格繪制的幾何和文本將描邊,尊重油漆上的筆畫相關(guān)的領(lǐng)域阶捆。
Paint.Style.STROKE
//用這種風(fēng)格繪制的幾何和文字將同時(shí)填充和描邊耗式,尊重油漆上的筆觸相關(guān)的領(lǐng)域。 如果幾何形狀逆時(shí)針?lè)较虺煤铮四J娇赡軙?huì)產(chǎn)生意想不到的結(jié)果。 此限制不適用于FILL或STROKE彪见。
Paint.Style.FILL_AND_STROKE
mPaint.setColor(mColorBg);
path
Path類封裝了由直線段儡司,二次曲線和三次曲線組成的復(fù)合(多個(gè)輪廓)幾何路徑∮嘀福可以使用canvas.drawPath(path捕犬,paint)繪制,可以是填充或描邊(基于繪制的樣式) 酵镜,或者它可以用于剪輯或在路徑上繪制文本碉碉。
//清除路徑中的任何線條和曲線,使其為空淮韭。這不會(huì)改變填充類型的設(shè)置垢粮。
animPath.reset();
//從最后一個(gè)點(diǎn)到指定點(diǎn)(x,y)添加一條線靠粪。 如果此輪廓未進(jìn)行moveTo()調(diào)用蜡吧,則第一個(gè)點(diǎn)將自動(dòng)設(shè)置為(0,0)。
x一行結(jié)尾的x坐標(biāo)--y一行結(jié)尾的y坐標(biāo)
animPath.lineTo(0, 0);
將下一個(gè)輪廓的起點(diǎn)設(shè)置為點(diǎn)(x占键,y)昔善。
@參數(shù)x新輪廓起點(diǎn)的x坐標(biāo)
@參數(shù)y新輪廓起點(diǎn)的y坐標(biāo)
sPath.moveTo
向路徑添加一個(gè)封閉的圓形輪廓
參數(shù)x要添加到路徑的圓的中心的x坐標(biāo)
參數(shù)y要添加到路徑中的圓的中心的y坐標(biāo)
radius要添加到路徑的圓的半徑
dir旋轉(zhuǎn)圓的輪廓的方向
sPath.addCircle(50, 50, 60, Path.Direction.CW);
順時(shí)針
Path.Direction.CW
逆時(shí)針
Path.Direction.CCW
創(chuàng)建一個(gè)空的PathMeasure對(duì)象。 要使用它來(lái)測(cè)量路徑的長(zhǎng)度畔乙,和/或沿著它找到位置和切線君仆,請(qǐng)調(diào)用setPath。
請(qǐng)注意,一旦一個(gè)路徑與度量對(duì)象相關(guān)聯(lián)返咱,如果路徑隨后被修改并且使用度量對(duì)象钥庇,那么它是未定義的。 如果修改路徑洛姑,則必須使用路徑調(diào)用setPath上沐。
PathMeasure pathMeasure = new PathMeasure();
指定一個(gè)新的路徑,或者為null楞艾,否則為空参咙。
pathMeasure.setPath(sourcePath, false);
//返回當(dāng)前輪廓的總長(zhǎng)度,如果沒(méi)有路徑與此度量對(duì)象關(guān)聯(lián)硫眯,則返回0蕴侧。
pathMeasure.getLength()
移動(dòng)到路徑中的下一個(gè)輪廓。 如果存在則返回true两入,否則返回false净宵。
pathMeasure.nextContour();
//經(jīng)過(guò)上面這段計(jì)算duration代碼的折騰 需要重新初始pathMeasure
pathMeasure.setPath(sourcePath, false);
//每段path走完后,要補(bǔ)一下 某些情況會(huì)出現(xiàn) animPath不滿的情況(當(dāng)然也可以設(shè)置一段裹纳,就可以達(dá)到進(jìn)度條的效果)
*給定起止距離择葡,返回到中間段。 如果段是零長(zhǎng)度剃氧,則返回false敏储,否則返回true。 startD和stopD被固定為合法值(0..getLength())朋鞍。 如果startD> = stopD已添,則返回false(并保持dst不變)。如果startWithMoveTo為true滥酥,則使用moveTo開(kāi)始該段更舞。
* <p>在{@link android.os.Build.VERSION_CODES#KITKAT}和更早的發(fā)行版上,得到的路徑可能不會(huì)顯示在硬件加速的畫布上坎吻。 一個(gè)簡(jiǎn)單的解決方法是向這個(gè)路徑添加一個(gè)單獨(dú)的操作缆蝉,比如<code> dst.rLineTo(0,0)</ code>禾怠。</ p>
pathMeasure.getSegment(0, pathMeasure.getLength(), animPath, true);
給定一個(gè)起點(diǎn)和終點(diǎn)距離返奉,返回中間段。 如果段是零長(zhǎng)度吗氏,則返回false芽偏,否則返回true。 startD和stopD被固定為合法值(0..getLength())弦讽。
如果startD> = stopD污尉,則返回false(并保持dst不變)膀哲。
如果startWithMoveTo為true,則使用moveTo開(kāi)始片段被碗。
在{@link android.os.Build.VERSION_CODES#KITKAT}和更早版本上某宪,結(jié)果路徑可能不會(huì)顯示在硬件加速畫布上。 一個(gè)簡(jiǎn)單的解決方法是向這個(gè)路徑添加一個(gè)單獨(dú)的操作锐朴,比如<code> dst.rLineTo(0兴喂,0)</ code>。</ p>
//animPath替換成mStonePath
animPath.set(mStonePath);
view
當(dāng)這個(gè)視圖的大小改變時(shí)焚志,這在layout期間被調(diào)用衣迷。 如果剛剛添加到視圖層次結(jié)構(gòu)中,則使用舊值0調(diào)用酱酬。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mPaddingLeft = getPaddingLeft();
mPaddingTop = getPaddingTop();
}
返回這個(gè)視圖的左邊填充壶谒。 如果有插入和啟用滾動(dòng)條,則此值可能還包括顯示滾動(dòng)條所需的空間膳沽。
mPaddingLeft = getPaddingLeft();
mPaddingTop = getPaddingTop();
invalidate
invalidate汗菜,請(qǐng)求重新draw,只會(huì)繪制調(diào)用者本身挑社。
* <p>這必須從UI線程調(diào)用陨界。 要從非UI線程調(diào)用,請(qǐng)調(diào)用{@link #postInvalidate()}痛阻。
public void invalidate() {
invalidate(true);
}
這是invalidate()工作實(shí)際發(fā)生的地方普碎。 一個(gè)完整的invalidate()將導(dǎo)致繪圖緩存失效,但是可以使用invalidateCache設(shè)置為false來(lái)調(diào)用此函數(shù)录平,以跳過(guò)不需要它的情況下的失效步驟(例如,保持與 相同的內(nèi)容)缀皱。
*
* @param invalidateCache此視圖的繪圖緩存是否也應(yīng)該失效斗这。 對(duì)于完全無(wú)效,這通常是正確的啤斗,但是如果視圖的內(nèi)容或維度沒(méi)有改變表箭,則可以將其設(shè)置為假。
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
Canvas
Canvas類保存“draw”調(diào)用钮莲。 要繪制東西免钻,你需要4個(gè)基本組件:一個(gè)位圖來(lái)保存像素,一個(gè)Canvas來(lái)承載繪制調(diào)用(寫入到位圖)崔拥,一個(gè)繪制基元(例如Rect极舔,Path,文本链瓦,位圖)和一個(gè)繪制 描述繪圖的顏色和樣式)拆魏。
//用指定的轉(zhuǎn)換對(duì)當(dāng)前矩陣進(jìn)行預(yù)處理
canvas.translate(mPaddingLeft, mPaddingTop);
*使用指定的繪圖繪制指定的路徑盯桦。 該路徑將根據(jù)畫筆中的樣式進(jìn)行填充或框定。
*
* @param path要繪制的路徑
* @param paint用于繪制路徑的油漆
canvas.drawPath(mSourcePath, mPaint);
canvas.drawPath(mAnimPath, mPaint);
繪制前景渤刃,mAnimPath不斷變化拥峦,不斷重繪View的話,就會(huì)有動(dòng)畫效果卖子。
PathAnimView
一個(gè)路徑動(dòng)畫的View,利用源Path繪制“底”,利用動(dòng)畫Path 繪制 填充動(dòng)畫
一個(gè)SourcePath 內(nèi)含多段Path略号,循環(huán)取出每段Path,并做一個(gè)動(dòng)畫
需要做動(dòng)畫的源Path
用于繪制動(dòng)畫的Path
背景色
前景色
PathAnimHelper
介紹:一個(gè)自定義View Path動(dòng)畫的工具類
一個(gè)SourcePath 內(nèi)含多段(一段)Path洋闽,循環(huán)取出每段Path玄柠,并做一個(gè)動(dòng)畫,
/**
* 一個(gè)SourcePath 內(nèi)含多段Path,循環(huán)取出每段Path喊递,并做一個(gè)動(dòng)畫
* 自定義動(dòng)畫的總時(shí)間
* 和是否循環(huán)
*
* @param view 需要做動(dòng)畫的自定義View
* @param sourcePath 源Path
* @param animPath 自定義View用這個(gè)Path做動(dòng)畫
* @param totalDuaration 動(dòng)畫一共的時(shí)間
* @param isInfinite 是否無(wú)限循環(huán)
*/
protected void startAnim(View view, Path sourcePath, Path animPath, long totalDuaration, boolean isInfinite) {
if (view == null || sourcePath == null || animPath == null) {
return;
}
PathMeasure pathMeasure = new PathMeasure();
//pathMeasure.setPath(sourcePath, false);
//先重置一下需要顯示動(dòng)畫的path
animPath.reset();
animPath.lineTo(0, 0);
pathMeasure.setPath(sourcePath, false);
//這里僅僅是為了 計(jì)算一下每一段的duration
int count = 0;
while (pathMeasure.getLength() != 0) {
pathMeasure.nextContour();
count++;
}
//經(jīng)過(guò)上面這段計(jì)算duration代碼的折騰 需要重新初始化pathMeasure
pathMeasure.setPath(sourcePath, false);
loopAnim(view, sourcePath, animPath, totalDuaration, pathMeasure, totalDuaration / count, isInfinite);
}
/**
* 循環(huán)取出每一段path 随闪,并執(zhí)行動(dòng)畫
*
* @param animPath 自定義View用這個(gè)Path做動(dòng)畫
* @param pathMeasure 用于測(cè)量的PathMeasure
*/
protected void loopAnim(final View view, final Path sourcePath, final Path animPath, final long totalDuaration, final PathMeasure pathMeasure, final long duration, final boolean isInfinite) {
//動(dòng)畫正在運(yùn)行的話,先stop吧骚勘。萬(wàn)一有人要使用新動(dòng)畫呢铐伴,(正經(jīng)用戶不會(huì)這么用。)
stopAnim();
mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.setDuration(duration);
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//Log.i("TAG", "onAnimationUpdate");
//增加一個(gè)callback 便于子類重寫搞事情
onPathAnimCallback(view, sourcePath, animPath, pathMeasure, animation);
//通知View刷新自己
view.invalidate();
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
//Log.w("TAG", "onAnimationRepeat: ");
//每段path走完后俏讹,要補(bǔ)一下 某些情況會(huì)出現(xiàn) animPath不滿的情況
pathMeasure.getSegment(0, pathMeasure.getLength(), animPath, true);
//繪制完一條Path之后当宴,再繪制下一條
pathMeasure.nextContour();
//長(zhǎng)度為0 說(shuō)明一次循環(huán)結(jié)束
if (pathMeasure.getLength() == 0) {
if (isInfinite) {//如果需要循環(huán)動(dòng)畫
animPath.reset();
animPath.lineTo(0, 0);
pathMeasure.setPath(sourcePath, false);
} else {//不需要就停止(因?yàn)閞epeat是無(wú)限 需要手動(dòng)停止)
animation.end();
}
}
}
});
mAnimator.start();
}
根據(jù)ArrayList<float[]> path 解析
/**
* 根據(jù)ArrayList<float[]> path 解析
*
* @param path
* @return
*/
public static Path getPathFromArrayFloatList(ArrayList<float[]> path) {
Path sPath = new Path();
for (int i = 0; i < path.size(); i++) {
float[] floats = path.get(i);
sPath.moveTo(floats[0], floats[1]);
sPath.lineTo(floats[2], floats[3]);
}
return sPath;
}
/**
* 從R.array.xxx里取出點(diǎn)陣,
*
* @param context
* @param arrayId
* @param zoomSize
* @return
*/
public static Path getPathFromStringArray(Context context, int arrayId, float zoomSize) {
Path path = new Path();
String[] points = context.getResources().getStringArray(arrayId);
for (int i = 0; i < points.length; i++) {
String[] x = points[i].split(",");
for (int j = 0; j < x.length; j = j + 2) {
if (j == 0) {
path.moveTo(Float.parseFloat(x[j]) * zoomSize, Float.parseFloat(x[j + 1]) * zoomSize);
} else {
path.lineTo(Float.parseFloat(x[j]) * zoomSize, Float.parseFloat(x[j + 1]) * zoomSize);
}
}
}
return path;
}
float[][] LETTERS = new float[][]{
new float[]{
// A
24, 0, 1, 22,
1, 22, 1, 72,
24, 0, 47, 22,
47, 22, 47, 72,
1, 48, 47, 48
},
new float[]{
// B
0, 0, 0, 72,
0, 0, 37, 0,
37, 0, 47, 11,
47, 11, 47, 26,
47, 26, 38, 36,
38, 36, 0, 36,
38, 36, 47, 46,
47, 46, 47, 61,
47, 61, 38, 71,
37, 72, 0, 72,
},....
};
進(jìn)度條效果
@Override
public void onPathAnimCallback(View view, Path sourcePath, Path animPath, PathMeasure pathMeasure, ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
//獲取一個(gè)段落
float end = pathMeasure.getLength() * value;
float begin = (float) (end - ((0.5 - Math.abs(value - 0.5)) * pathMeasure.getLength()));
animPath.reset();
animPath.lineTo(0, 0);
pathMeasure.getSegment(begin, end, animPath, true);
}