Android View 與 ViewGroup

初學(xué)Android時(shí),總是混淆View河胎、ViewGroup的父子關(guān)系,盡管在源碼中有標(biāo)明

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

但總是有個(gè)疑問虎敦,ViewGroup可以包含View游岳、ViewGroup,而View只能放在ViewGroup中其徙,從感覺上來說胚迫,ViewGroup是父才對(duì)。

但事實(shí)勝于熊便唾那, 那就看看為什么View是父吧

先從第一步看起
1.往ViewGroup中添加子View:addView
點(diǎn)到ViewGroup的addView方法中访锻,一直跟到最后,發(fā)現(xiàn)闹获,ViewGroup會(huì)將添加進(jìn)來的View 保存在一個(gè)數(shù)組中:

   // 保存子View的數(shù)組
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    private View[] mChildren;
//保存子View進(jìn)數(shù)組的方法 (具體細(xì)節(jié)可自行看源碼 下同)
private void addInArray(View child, int index) {
       //將子View保存到mChildren
    }

2.繪制子View
在View的繪制流程中期犬,有個(gè)空實(shí)現(xiàn)的方法至關(guān)重要:

    protected void dispatchDraw(Canvas canvas) {
       //空
    }

這個(gè)方法在View的渲染過程中會(huì)被調(diào)用,但在View中是空實(shí)現(xiàn)避诽,調(diào)用了也沒用哭懈, 然而在ViewGroup中重寫了此方法:

    protected void dispatchDraw(Canvas canvas) {
       //將畫布Canvas根據(jù)設(shè)定值裁剪
       //繪制子View(調(diào)用子View的 draw方法)
    }

這里兩個(gè)問題:
1.為什么要裁剪。
2.根據(jù)什么裁剪茎用。

先說第一個(gè)遣总, 在Android中,所有控件都是繪制在Canvas上得轨功,既然ViewGroup可以在內(nèi)部顯示其他View旭斥,那么其他View的Canvas是怎么來的呢? 其實(shí)ViewGroup和其他的子View都是用的同一張Canvas古涧, 只是在繪制時(shí)垂券,通過 移動(dòng)、裁剪 將畫布提供給對(duì)應(yīng)的子View使用, 不過在 移動(dòng)菇爪、裁剪前要 保存畫布算芯,繪制完一個(gè)子View后恢復(fù)畫布,如果有多個(gè)子View凳宙, 繼續(xù)重復(fù)以上過程熙揍, 那么Canvas的平移和裁剪是根據(jù)什么來確定的呢, 先看一下源碼:

            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                    mScrollX + mRight - mLeft - mPaddingRight,
                    mScrollY + mBottom - mTop - mPaddingBottom);

可以看到裁剪的大小氏涩,根據(jù) 滾動(dòng)值+邊距值+ 設(shè)定值(mLeft届囚, mTop, mRight是尖,mBottom)來確定的意系, 最主要的是設(shè)定值,
那么設(shè)定值是通過什么設(shè)置的呢饺汹, 其實(shí)在我們繼承ViewGroup的時(shí)候蛔添,它會(huì)強(qiáng)制要求我們繼承一個(gè)onLayout方法,用來給子View設(shè)置擺放位置兜辞,通過View.layout(mLeft, mTop, mRight, mBottom), 其實(shí)這四個(gè)參數(shù)作郭, 就是Canvas裁剪的設(shè)定值, 點(diǎn)進(jìn)layout源碼中最后會(huì)看到這個(gè)方法的賦值

   protected boolean setFrame(int left, int top, int right, int bottom) {
       ........
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
           .......
        return changed;
    }

那現(xiàn)在裁剪有了,沒平移還不行弦疮, 比如我想將子View放在ViewGroup的 (100夹攒, 100, 200胁塞, 200)的位置咏尝,
如果不算滾動(dòng)和邊距, 那么裁剪完畫布的位置就是(0, 0, 100, 100), 如果我們需要將子View顯示在設(shè)定的位置啸罢, 那么還需要在
x軸上移動(dòng)100编检,
y軸上移動(dòng)100。

接下來就到了移動(dòng)的環(huán)節(jié)扰才, 在繪制子View(調(diào)用子View的 draw方法)時(shí), 調(diào)用了View的draw方法(注意:是三個(gè)參數(shù)的draw方法)衩匣,在這個(gè)方法中可以看到Canvas調(diào)用了移動(dòng)方法:

canvas.translate(mLeft, mTop);

參數(shù)和裁剪時(shí)的參數(shù)一致, 是在layout時(shí)設(shè)置的參數(shù)生百,那么現(xiàn)在這個(gè)Canvas就移動(dòng)到了我們?cè)O(shè)定的位置,再往后draw方法中會(huì)調(diào)用單個(gè)參數(shù)的draw方法柄延, 在這個(gè)draw方法中會(huì)調(diào)用我們熟悉的onDraw(Canvas canvas)方法蚀浆,這就是我們?cè)趯懽远xView的時(shí)候經(jīng)常使用的方法,其中的參數(shù)Canvas就是經(jīng)過ViewGroup裁剪平移過后的Canvas

  if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
 public void draw(Canvas canvas) {
       ......
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
......
}

到此市俊,就算理清了ViewGroup為什么作為View的子類還能容納其他View了,歸納一下:
1.ViewGroup通過addView方法保存子View到集合中摆昧。
2.在OnLayout時(shí)設(shè)置子View的位置信息(mLeft撩满, mTop, mRight据忘,mBottom)。
3.在渲染時(shí)勇吊,根據(jù)設(shè)置的位置信息裁剪、移動(dòng)畫布到對(duì)應(yīng)位置汉规。
4.View調(diào)用OnDraw方法繪制自身信息驹吮。

既然已經(jīng)分析出來了, 那么就來實(shí)戰(zhàn)一下吧碟狞, 通過繼承View, 實(shí)現(xiàn)addView添加子View的功能:

1.自定義ParentView繼承自View:

public class ParentView extends View {

2.聲明一個(gè)保存子View的集合族沃, 并提供addView的方法:

private ArrayList<ViewBean> mViews = new ArrayList<>();
    /**
     * 添加子View
     */
    public void addView(View view, int left, int top, int right, int bottom){
        mViews.add(new ViewBean(left, top, right, bottom, view));
        invalidate();
    }

3.在dispatchDraw中遍歷子View, 并繪制:

  @Override
    protected void dispatchDraw(Canvas canvas) {
        for (ViewBean mView : mViews) {
            canvas.save();
            drawChildView(canvas, mView);
            canvas.restore();
        }
    }
    /**
     * 繪制子View
     */
    private void drawChildView(Canvas canvas, ViewBean viewBean) {
        //移動(dòng)畫布到 mLeft  mTop的位置
        canvas.translate(viewBean.getmLeft(), viewBean.getmTop());
        //移動(dòng)畫布大小為  寬:mRight-mLeft  高:mBottom - mTop
        canvas.clipRect(0, 0, viewBean.getmRight() - viewBean.getmLeft(), viewBean.getmBottom() - viewBean.getmTop());
        //繪制紅色背景 方便區(qū)分
        canvas.drawColor(Color.RED);
        //添加TextView時(shí)它內(nèi)部需要設(shè)定位置值常空, 自己寫的View可不用layout
        viewBean.getmView().layout(viewBean.getmLeft(), viewBean.getmTop(), viewBean.getmRight(), viewBean.getmBottom());
        //調(diào)用子View的draw方法 將此canvas提供給子View使用
        viewBean.getmView().draw(canvas);
    }

5.在布局中使用ParentView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.viewgroupdemo.ParentView
        android:id="@+id/pv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

6.在ParentView中添加子View漓糙, 并設(shè)定位置:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ParentView parentView = findViewById(R.id.pv);

        CustomTextView tvOne = new CustomTextView(this);
        tvOne.setText("I am tvOne");
        tvOne.setTextColor(Color.WHITE);

        CustomTextView tvTwo = new CustomTextView(this);
        tvTwo.setText("I am tvTwo");
        tvTwo.setTextColor(Color.WHITE);

        //第一個(gè)textview
        parentView.addView(tvOne, 100, 100, 600, 600);
        //第二個(gè)textview
        parentView.addView(tvTwo, 300, 700, 600, 1000);
    }
}

預(yù)覽:

001.jpg

可以看到兩個(gè)TextView已經(jīng)被繪制到 ParentView 上了烘嘱。

。蝇庭。。完

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辐棒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子泰涂,更是在濱河造成了極大的恐慌,老刑警劉巖辐怕,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異是牢,居然都是意外死亡陕截,警方通過查閱死者的電腦和手機(jī)驳棱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門社搅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乳规,“玉大人形葬,你說我怎么就攤上這事暮的。” “怎么了源织?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵微猖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我凛剥,道長(zhǎng),這世上最難降的妖魔是什么逻炊? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任犁享,我火速辦了婚禮,結(jié)果婚禮上炊昆,老公的妹妹穿的比我還像新娘威根。我一直安慰自己视乐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布佑淀。 她就那樣靜靜地躺著,像睡著了一般谎砾。 火紅的嫁衣襯著肌膚如雪捧颅。 梳的紋絲不亂的頭發(fā)上景图,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天症歇,我揣著相機(jī)與錄音谭梗,去河邊找鬼。 笑死激捏,一個(gè)胖子當(dāng)著我的面吹牛凄吏,可吹牛的內(nèi)容都是我干的远舅。 我是一名探鬼主播痕钢,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼任连,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蚤吹!你這毒婦竟也來了随抠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤二驰,失蹤者是張志新(化名)和其女友劉穎秉沼,沒想到半個(gè)月后矿酵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坏瘩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年倔矾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哪自。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壤巷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胧华,到底是詐尸還是另有隱情宙彪,我是刑警寧澤矩动,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布悲没,位于F島的核電站男图,受9級(jí)特大地震影響示姿,放射性物質(zhì)發(fā)生泄漏逊笆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一子檀、第九天 我趴在偏房一處隱蔽的房頂上張望差牛。 院中可真熱鬧命锄,春花似錦偏化、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骗污。三九已至,卻和暖如春需忿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涕烧。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工汗洒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溢谤。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像阀参,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子结笨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355