[轉(zhuǎn)] Android自定義View傍妒,你摸的透透的了幔摸?
本文轉(zhuǎn)載自ClericYi的Android自定義View,你摸的透透的了颤练?
前言
View
既忆,有很多的名稱。不論是你熟知的布局嗦玖,還是控件患雇,他們?nèi)慷祭^承自View
。
文內(nèi)部分圖片轉(zhuǎn)載自Carson_Ho大佬的文章
思維導圖
工作流程
measure
其實通過layout
中的第二張圖我們已經(jīng)知道了控件大小的計算了宇挫。
-
height
=bottom
-top
-
width
=right
-left
對于ViewGroup而言苛吱,就是對容器內(nèi)子控件的遍歷和計算了。
因為直接繼承自View
的控件使用wrap_cotent
和match_parent
是顯示出來的效果是相同的器瘪。需要我們使用MeasureSpec
中的getMode()
方法來對當前的模式進行區(qū)分和比較翠储。
模式 | 狀態(tài) |
---|---|
UNSPECIFIED | 未指定模式绘雁,View想多大就多大,父容器不做限制援所,一般用于系統(tǒng)內(nèi)部的測量 |
AT_MOST | 最大模式庐舟,對應wrap_content,View的大小不大于SpecSize的值 |
EXACTLY | 精確模式任斋,對應match_parent继阻,View的大小為SpecSize的值 |
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//用于獲取設定的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 用于獲取設定的長度
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 類似這樣的判斷,后面不過多復述
// 用于判斷是不是wrap_content
// 如果不進行處理废酷,效果會是match_parent
if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
setMeasuredDimension(20, 20);
}
}
復制代碼
layout
在確定位置時,我們有一個非常需要主要的地方—— 坐標系抹缕。Android系統(tǒng)的坐標系和平時畫的坐標系并不相同澈蟆。
所以相對應的,我們的位置計算方法自然和我們原來的正好是相反的卓研。
4個頂點的位置分別由4個值決定:
-
top
:子View上邊界到所在容器上邊界的距離趴俘。 -
left
:子View左邊界到所在容器左邊界的距離。 -
bottom
:子View下邊界到所在容器上邊界的距離奏赘。 -
right
:子View右邊界到所在容器左邊界的距離寥闪。
所有的計算都是相對于所在容器才能夠開始的。
draw
一共有6個步驟:
- 如果需要磨淌,則繪制背景 -- drawBackground(canvas);
- 保存當前canvas層 -- saveCount = canvas.getSaveCount();
- 繪制View的內(nèi)容 -- if (!dirtyOpaque) onDraw(canvas);
- 繪制子View -- dispatchDraw(canvas);
- 如果需要疲憋,則繪制View的褪色邊緣,類似于陰影效果 -- canvas.restoreToCount(saveCount);
- 繪制裝飾梁只,比如滾動條 -- onDrawForeground(canvas);
關于開發(fā)者需要重寫的方法一般是第三步繪制View的內(nèi)容對應的
onDraw()
缚柳。
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
// 在畫布上進行類似這樣的操作
canvas.drawLine(0, height/2, width,height/2, paint);
}
復制代碼
入門自定義View
在日常項目的布局文件中我們經(jīng)常會使用到xmlns:app="http://schemas.android.com/apk/res-auto"
這樣標簽,其實他就是用來引入我們自定義的標簽使用的搪锣。
- 在
res/values
目錄下創(chuàng)建attrs
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DefaultView">
<attr name="color" format="color"/>
</declare-styleable>
</resources>
復制代碼
- 在
DefaultView(Context context, @Nullable AttributeSet attrs)
中獲取秋忙。以下是整個完整代碼。
/**
* author: ClericYi
* time: 2020-01-30
*/
public class DefaultView extends View {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mColor = Color.RED;
public DefaultView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
initDraw();
}
private void initAttrs(Context context, @Nullable AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefaultView);
// 從styleable中獲取的名字是系統(tǒng)會生成的构舟,一般是 類名_name 的形式
mColor = array.getColor(R.styleable.DefaultView_color, Color.GREEN);
// 獲取完資源后即使回收
array.recycle();
}
private void initDraw() {
paint.setColor(mColor);
paint.setStrokeWidth(3f);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
canvas.drawLine(0, height/2, width,height/2, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthMode == MeasureSpec.AT_MOST){
setMeasuredDimension(20, 20);
}
}
}
復制代碼
基礎的性能優(yōu)化
首先的話我們先了解如何去知道一個View
是否被過度繪制了灰追?
其實在我們手機中的開發(fā)模式已經(jīng)存在這個選項了。
開啟前 | 開啟后 |
---|---|
下方給出繪制的次數(shù)對應圖
那如何做到性能優(yōu)化呢狗超?
在這個問題之前弹澎,需要了解什么是過度繪制,你可以理解為同一位置的控件不斷的疊加而產(chǎn)生的無用數(shù)據(jù)抡谐,那我們就來說說集中解決方案吧裁奇。
方案1: 減少嵌套層數(shù)。
使用線性布局 | 使用約束布局 |
---|---|
因為只是一個案例麦撵,想說的意思刽肠,如果多個LinearLayout
嵌套實現(xiàn)的效果溃肪,如果能被一個ConstraintLayout
直接實現(xiàn),那么就用后者替代音五,因為不會這樣在同一個區(qū)域重復出現(xiàn)
方案2: 去除默認的背景
這個解決方案其實針對的背景會被自動繪制的問題惫撰,如果我們把這個層次消去,從繪制角度老說也是一種提升了躺涝。正如圖示一般直接減少了一層的繪制厨钻。
在代碼中的具體表現(xiàn),通過對style.xml
中的Theme
進行修改:
<item name="android:windowBackground">@null</item>
復制代碼
總結(jié)
以上就是我的學習成果坚嗜,如果有什么我沒有思考到的地方或是文章內(nèi)存在錯誤夯膀,歡迎與我分享。