-
View的分類
視圖View主要分為兩類:
單一視圖
即一個(gè)View稻薇,不含子View
視圖組
即多個(gè)View組成的View淑际,比如LinerLayout奔穿,包含子View
- View類的簡介
- View類是Android中所有組件的基類闻鉴,如View是ViewGroup的基類
- View的構(gòu)造函數(shù)
自定義View必須重寫至少一個(gè)構(gòu)造函數(shù)
public XiaoCaiView(Context context) {
super(context);
}
// 如果View是在.xml里聲明的,則調(diào)用第二個(gè)構(gòu)造函數(shù)
// 自定義屬性是從AttributeSet參數(shù)傳進(jìn)來的
public XiaoCaiView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 不會(huì)自動(dòng)調(diào)用
// 一般是在第二個(gè)構(gòu)造函數(shù)里主動(dòng)調(diào)用
// 如View有style屬性時(shí)
public XiaoCaiView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//API21之后才使用
// 不會(huì)自動(dòng)調(diào)用
// 一般是在第二個(gè)構(gòu)造函數(shù)里主動(dòng)調(diào)用
// 如View有style屬性時(shí)
public XiaoCaiView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
- 測量View大小(onMeasure)
- 自定義View的測量方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
setMeasuredDimension(measureWidth, measureHeight);
//布局的寬高都是由這個(gè)方法指定
//指定控件的寬高芒篷,需要測量
//獲取寬高的模式
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
//控件的寬
int width=MeasureSpec.getSize(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
//控件的高
int height=MeasureSpec.getSize(heightMeasureSpec);
//
if(widthMode==MeasureSpec.AT_MOST){
//在布局中定義warp_content
}
if(widthMode==MeasureSpec.EXACTLY){
//在布局中制定了具體的值:100dp
}
if(widthMode==MeasureSpec.UNSPECIFIED){
//盡可能的大
}
}
private int measureHeight(int heightMeasureSpec) {
int defaultHeight=0;
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
switch (heightMode) {
case MeasureSpec.UNSPECIFIED:
defaultHeight = height;
break;
case MeasureSpec.AT_MOST:
defaultHeight = height / 4;
break;
case MeasureSpec.EXACTLY:
defaultHeight = height / 5;
break;
default:
break;
}
return defaultHeight;
}
模式 | 二進(jìn)制數(shù)值 | 描述 |
---|---|---|
UNSPECIFIED | 00 | 默認(rèn)值搜变,父控件沒有給子View任何限制,子View可以設(shè)置為任意大小 |
EXACTLY | 01 | 父控件給子View限制了具體的數(shù)值 |
AT MOST | 10 | 表示子View具體大小沒有尺寸限制针炉,但是存在上限挠他,上限一般為父View大小。 |
- setMeasuredDimension
onMeasure方法最后需要調(diào)用setMeasuredDimension方法來保存測量的寬高值篡帕,如果不調(diào)用這個(gè)方法绩社,可能會(huì)產(chǎn)生不可預(yù)測的問題摔蓝。
-
確定View大小(onSizeChanged)
這個(gè)函數(shù)在視圖發(fā)生改變的時(shí)候調(diào)用
因?yàn)閂iew的大小不僅由View本身控制,而且受父控件的影響愉耙,所以我們在確定View大小的時(shí)候最好使用系統(tǒng)提供的onSizeChanged回調(diào)函數(shù)贮尉。
onSizeChanged()函數(shù)如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
可以看出,它又四個(gè)參數(shù)朴沿,分別為 寬度猜谚,高度,上一次寬度赌渣,上一次高度魏铅。
這個(gè)函數(shù)比較簡單,我們只需關(guān)注 寬度(w), 高度(h) 即可坚芜,這兩個(gè)參數(shù)就是View最終的大小览芳。
- 確定子View布局位置(onLayout)
-
自定義一個(gè)可以滑動(dòng)的ViewGroup來體會(huì)一下OnLayout()吧
1.onMeasure()方法告訴子View去測量自己的大小/** * * 使用遍歷的方式通知子view進(jìn)行自測 * * */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int childCount=getChildCount(); for(int i=0;i<childCount;i++){ View child=getChildAt(i); measureChild(child,widthMeasureSpec,heightMeasureSpec); } }
- onLayout():放置每個(gè)子View的位置
@Override protected void onLayout(boolean b, int left, int up, int right, int down) { // LinearLayout root=new LinearLayout(getContext()); // View child=new View(getContext()); // LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams // .WRAP_CONTENT); // lp.setMargins(10,10,10,10); // root.addView(child,lp); int childCout = getChildCount(); MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); mlp.height = childCout * mScreenHeight; //設(shè)置ViewGroup的高 setLayoutParams(mlp); for (int i = 0; i < childCout; i++) { View child = getChildAt(i); if (child.getVisibility() != View.GONE) { //設(shè)置每個(gè)child在ViewGroup中的位置 child.layout(left, i * mScreenHeight, right, (i + 1) * mScreenHeight); } } }
- OnTouchEvent()實(shí)現(xiàn)滑動(dòng)邏輯:
@Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 記錄觸摸起點(diǎn) mStart = getScrollY(); mLastY = y; break; case MotionEvent.ACTION_MOVE: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } //滑動(dòng)的距離 int dy = mLastY - y; if (getScrollY() < 0 || getScrollY() > getHeight() - mScreenHeight) { dy = 0; } scrollBy(0, dy); mLastY = y; break; case MotionEvent.ACTION_UP: // 記錄觸摸終點(diǎn) mEnd = getScrollY(); int dScrollY = mEnd - mStart; if (dScrollY > 0) { if (dScrollY < mScreenHeight / 3) { mScroller.startScroll( 0, getScrollY(), 0, -dScrollY); } else { mScroller.startScroll( 0, getScrollY(), 0, mScreenHeight - dScrollY); } } else { if (-dScrollY < mScreenHeight / 3) { mScroller.startScroll( 0, getScrollY(), 0, -dScrollY); } else { mScroller.startScroll( 0, getScrollY(), 0, -mScreenHeight - dScrollY); } } break; default: break; } postInvalidate(); return true; }
6. onDraw()(繪制內(nèi)容)
這個(gè)是自定義控件最核心的一個(gè)類,需要畫的東東都在這個(gè)里面鸿竖。平時(shí)我們畫什么東西的時(shí)候是不是需要一個(gè)本子沧竟,一個(gè)畫筆。那么這個(gè)onDraw里面也需要畫布(Canvas)和畫筆(Paint)接下來就介紹一下Canvas和Paint:
-
Canvas簡介
Canvas我們可以稱之為畫布缚忧,能夠在上面繪制各種東西悟泵,是安卓平臺2D圖形繪制的基礎(chǔ),非常強(qiáng)大闪水。 - Canvas的常用操作速查表
操作類型 | 相關(guān)API | 備注 |
---|---|---|
繪制顏色 | drawColor, drawRGB, drawARGB | 使用單一顏色填充整個(gè)畫布 |
繪制基本形狀 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次為 點(diǎn)糕非、線、矩形球榆、圓角矩形朽肥、橢圓、圓持钉、圓弧 |
繪制圖片 | drawBitmap, drawPicture | 繪制位圖和圖片 |
繪制文本 | drawText, drawPosText, drawTextOnPath | 依次為 繪制文字衡招、繪制文字時(shí)指定每個(gè)文字位置、根據(jù)路徑繪制文字 |
繪制路徑 | drawPath | 繪制路徑右钾,繪制貝塞爾曲線時(shí)也需要用到該函數(shù) |
頂點(diǎn)操作 | drawVertices, drawBitmapMesh | 通過對頂點(diǎn)操作可以使圖像形變蚁吝,drawVertices直接對畫布作用旱爆、 drawBitmapMesh只對繪制的Bitmap作用 |
畫布剪裁 | clipPath, clipRect | 設(shè)置畫布的顯示區(qū)域 |
畫布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次為 保存當(dāng)前狀態(tài)舀射、 回滾到上一次保存的狀態(tài)、 保存圖層狀態(tài)怀伦、 回滾到指定狀態(tài)脆烟、 獲取保存次數(shù) |
畫布變換 | translate, scale, rotate, skew | 依次為 位移、縮放房待、 旋轉(zhuǎn)邢羔、錯(cuò)切 |
Matrix(矩陣) | getMatrix, setMatrix, concat | 實(shí)際畫布的位移驼抹,縮放等操作的都是圖像矩陣Matrix,只不過Matrix比較難以理解和使用拜鹤,故封裝了一些常用的方法框冀。 |
-
簡要介紹Paint
繪制的基本形狀由Canvas確定,但繪制出來的顏色,具體效果則由Paint確定敏簿。
畫筆有三種模式明也,如下:
STROKE //描邊
FILL //填充
FILL_AND_STROKE //描邊加填充
為了區(qū)分三者效果我們做如下實(shí)驗(yàn):
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(40); //為了實(shí)驗(yàn)效果明顯,特地設(shè)置描邊寬度非常大
// 描邊
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200,200,100,paint);
// 填充
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,500,100,paint);
// 描邊加填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(200, 800, 100, paint);
7.Path簡介
很多時(shí)候我們的一些API方法畫出的達(dá)不到我們想要的一些酷炫效果惯裕,這時(shí)候就需要用到Path路徑了温数,Path封裝了由直線和曲線(二次,三次貝塞爾曲線)構(gòu)成的幾何路徑蜻势。你能用Canvas中的drawPath來把這條路徑畫出來(同樣支持Paint的不同繪制模式)撑刺,也可以用于剪裁畫布和根據(jù)路徑繪制文字。我們有時(shí)會(huì)用Path來描述一個(gè)圖像的輪廓握玛,所以也會(huì)稱為輪廓線(輪廓線僅是Path的一種使用方法够傍,兩者并不等價(jià))。
- 最常用的方法:moveTo败许、 setLastPoint王带、 lineTo 和 close
moveTo:將點(diǎn)移動(dòng)到相應(yīng)的位置
setLastPoint:設(shè)置之前操作的最后一個(gè)點(diǎn)位置
lineTo:連接上一個(gè)點(diǎn)和該點(diǎn)構(gòu)成線
close:形成閉合曲線
8.貝塞爾曲線簡介
- 貝塞爾曲線主要是找到三個(gè)點(diǎn),起始點(diǎn)市殷,控制點(diǎn)愕撰,終止點(diǎn)。其中醋寝,起始點(diǎn)和終止點(diǎn)稱為數(shù)據(jù)點(diǎn)搞挣。
類型 | 作用 |
---|---|
數(shù)據(jù)點(diǎn) | 確定曲線的起始和結(jié)束位置 |
控制點(diǎn) | 確定曲線的彎曲程度 |
- 貝塞爾曲線的應(yīng)用:
- QQ小紅點(diǎn)拖拽效果
- 一些炫酷的下拉刷新控件
- 閱讀軟件的翻書效果
- 一些平滑的折線圖的制作
- 很多炫酷的動(dòng)畫效果
實(shí)例講解
簡單的QQ拖拽效果