[轉(zhuǎn)]Android自定義View,你摸的透透的了?

[轉(zhuǎn)] Android自定義View,你摸的透透的了?

本文轉(zhuǎn)載自ClericYi的Android自定義View,你摸的透透的了?

前言

View,有很多的名稱。不論是你熟知的布局,還是控件,他們?nèi)慷祭^承自View。

文內(nèi)部分圖片轉(zhuǎn)載自Carson_Ho大佬的文章

思維導(dǎo)圖

思維導(dǎo)圖

工作流程

measure

其實(shí)通過layout中的第二張圖我們已經(jīng)知道了控件大小的計(jì)算了。

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

對(duì)于ViewGroup而言,就是對(duì)容器內(nèi)子控件的遍歷和計(jì)算了。

因?yàn)橹苯永^承自View的控件使用wrap_cotentmatch_parent是顯示出來的效果是相同的。需要我們使用MeasureSpec中的getMode()方法來對(duì)當(dāng)前的模式進(jìn)行區(qū)分和比較。

模式 狀態(tài)
UNSPECIFIED 未指定模式,View想多大就多大,父容器不做限制,一般用于系統(tǒng)內(nèi)部的測(cè)量
AT_MOST 最大模式,對(duì)應(yīng)wrap_content,View的大小不大于SpecSize的值
EXACTLY 精確模式,對(duì)應(yīng)match_parent,View的大小為SpecSize的值
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //用于獲取設(shè)定的模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        // 用于獲取設(shè)定的長(zhǎng)度
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // 類似這樣的判斷,后面不過多復(fù)述
        // 用于判斷是不是wrap_content
        // 如果不進(jìn)行處理,效果會(huì)是match_parent
        if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(20, 20);
        }
    }
復(fù)制代碼

layout

在確定位置時(shí),我們有一個(gè)非常需要主要的地方—— 坐標(biāo)系。Android系統(tǒng)的坐標(biāo)系和平時(shí)畫的坐標(biāo)系并不相同。

坐標(biāo)系

所以相對(duì)應(yīng)的,我們的位置計(jì)算方法自然和我們?cè)瓉淼恼檬窍喾吹摹?/p>

4個(gè)頂點(diǎn)的位置分別由4個(gè)值決定:

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

所有的計(jì)算都是相對(duì)于所在容器才能夠開始的。

draw

一共有6個(gè)步驟:

  1. 如果需要,則繪制背景 -- drawBackground(canvas);
  2. 保存當(dāng)前canvas層 -- saveCount = canvas.getSaveCount();
  3. 繪制View的內(nèi)容 -- if (!dirtyOpaque) onDraw(canvas);
  4. 繪制子View -- dispatchDraw(canvas);
  5. 如果需要,則繪制View的褪色邊緣,類似于陰影效果 -- canvas.restoreToCount(saveCount);
  6. 繪制裝飾,比如滾動(dòng)條 -- onDrawForeground(canvas);

關(guān)于開發(fā)者需要重寫的方法一般是第三步繪制View的內(nèi)容對(duì)應(yīng)的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();
        // 在畫布上進(jìn)行類似這樣的操作
        canvas.drawLine(0, height/2, width,height/2, paint);
    }
復(fù)制代碼

入門自定義View

在日常項(xiàng)目的布局文件中我們經(jīng)常會(huì)使用到xmlns:app="http://schemas.android.com/apk/res-auto"這樣標(biāo)簽,其實(shí)他就是用來引入我們自定義的標(biāo)簽使用的。

  1. 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>
復(fù)制代碼
  1. DefaultView(Context context, @Nullable AttributeSet attrs)中獲取。以下是整個(gè)完整代碼。
/**
 * 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)會(huì)生成的,一般是 類名_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);
        }
    }
}
復(fù)制代碼

基礎(chǔ)的性能優(yōu)化

首先的話我們先了解如何去知道一個(gè)View是否被過度繪制了?

其實(shí)在我們手機(jī)中的開發(fā)模式已經(jīng)存在這個(gè)選項(xiàng)了。

開啟前 開啟后
開啟前
開啟后

下方給出繪制的次數(shù)對(duì)應(yīng)圖

過度繪制

那如何做到性能優(yōu)化呢?

在這個(gè)問題之前,需要了解什么是過度繪制,你可以理解為同一位置的控件不斷的疊加而產(chǎn)生的無用數(shù)據(jù),那我們就來說說集中解決方案吧。

方案1: 減少嵌套層數(shù)。

使用線性布局 使用約束布局

因?yàn)橹皇且粋€(gè)案例,想說的意思,如果多個(gè)LinearLayout嵌套實(shí)現(xiàn)的效果,如果能被一個(gè)ConstraintLayout直接實(shí)現(xiàn),那么就用后者替代,因?yàn)椴粫?huì)這樣在同一個(gè)區(qū)域重復(fù)出現(xiàn)

方案2: 去除默認(rèn)的背景

這個(gè)解決方案其實(shí)針對(duì)的背景會(huì)被自動(dòng)繪制的問題,如果我們把這個(gè)層次消去,從繪制角度老說也是一種提升了。正如圖示一般直接減少了一層的繪制。

在代碼中的具體表現(xiàn),通過對(duì)style.xml中的Theme進(jìn)行修改:

<item name="android:windowBackground">@null</item>
復(fù)制代碼

總結(jié)

總結(jié)

以上就是我的學(xué)習(xí)成果,如果有什么我沒有思考到的地方或是文章內(nèi)存在錯(cuò)誤,歡迎與我分享。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容