Android自定義View,你摸的透透的了?

前言

View,有不少的名稱。不管是你熟知的佈局,仍是控件,他們所有都繼承自Viewphp

View部分繼承圖

文內部分圖片轉載自Carson_Ho大佬的文章java

思惟導圖

工做流程

measure

其實經過layout中的第二張圖咱們已經知道了控件大小的計算了。android

  • height = bottom - top
  • width = right - left

對於ViewGroup而言,就是對容器內子控件的遍歷和計算了。canvas

由於直接繼承自View的控件使用wrap_cotentmatch_parent是顯示出來的效果是相同的。須要咱們使用MeasureSpec中的getMode()方法來對當前的模式進行區分和比較。性能優化

模式 狀態
UNSPECIFIED 未指定模式,View想多大就多大,父容器不作限制,通常用於系統內部的測量
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系統的座標系和平時畫的座標系並不相同。 app

Android屏幕座標軸

因此相對應的,咱們的位置計算方法天然和咱們原來的正好是相反的。 ide

View位置計算

4個頂點的位置分別由4個值決定:佈局

  • top:子View上邊界到所在容器上邊界的距離。
  • left:子View左邊界到所在容器左邊界的距離。
  • bottom:子View下邊界到所在容器上邊界的距離。
  • right:子View右邊界到所在容器左邊界的距離。

全部的計算都是相對於所在容器纔可以開始的。post

draw

一共有6個步驟:性能

  1. 若是須要,則繪製背景 -- drawBackground(canvas);
  2. 保存當前canvas層 -- saveCount = canvas.getSaveCount();
  3. 繪製View的內容 -- if (!dirtyOpaque) onDraw(canvas);
  4. 繪製子View -- dispatchDraw(canvas);
  5. 若是須要,則繪製View的褪色邊緣,相似於陰影效果 -- canvas.restoreToCount(saveCount);
  6. 繪製裝飾,好比滾動條 -- onDrawForeground(canvas);

關於開發者須要重寫的方法通常是第三步繪製View的內容對應的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

在平常項目的佈局文件中咱們常常會使用到xmlns:app="http://schemas.android.com/apk/res-auto"這樣標籤,其實他就是用來引入咱們自定義的標籤使用的。

  1. res/values目錄下建立attrs
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DefaultView">
        <attr name="color" format="color"/>
    </declare-styleable>
</resources>
複製代碼
  1. 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中獲取的名字是系統會生成的,通常是 類名_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);
        }
    }
}
複製代碼

基礎的性能優化

首先的話咱們先了解如何去知道一個View是否被過分繪製了?

其實在咱們手機中的開發模式已經存在這個選項了。

開啓前 開啓後

下方給出繪製的次數對應圖

那如何作到性能優化呢?

在這個問題以前,須要瞭解什麼是過分繪製,你能夠理解爲同一位置的控件不斷的疊加而產生的無用數據,那咱們就來講說集中解決方案吧。

方案1: 減小嵌套層數。

使用線性佈局 使用約束佈局

由於只是一個案例,想說的意思,若是多個LinearLayout嵌套實現的效果,若是能被一個ConstraintLayout直接實現,那麼就用後者替代,由於不會這樣在同一個區域重複出現

方案2: 去除默認的背景

這個解決方案其實針對的背景會被自動繪製的問題,若是咱們把這個層次消去,從繪製角度老說也是一種提高了。正如圖示通常直接減小了一層的繪製。

在代碼中的具體表現,經過對style.xml中的Theme進行修改:

<item name="android:windowBackground">@null</item>
複製代碼

總結

以上就是個人學習成果,若是有什麼我沒有思考到的地方或是文章內存在錯誤,歡迎與我分享。


相關文章推薦:

應用層中除了HTTP,你還知道點啥?

TCP層的那些知識,你掌握了多少?

Android必知必會的四大組件 -- ContentProvider篇

相關文章
相關標籤/搜索