1. 程式人生 > >自定義View(一)---View的基礎概念、工作流程以及生命週期的理解

自定義View(一)---View的基礎概念、工作流程以及生命週期的理解

不詩意的女程式猿不是好廚師~

:最近在工作中使用到了各種自定義控制元件,也更深刻的理解了自定義控制元件的重要性,所以就建了一個專欄來專門整理自定義控制元件的相關知識。我打算先從理論知識說起,然後再把專案中使用的自定義控制元件整理後寫為部落格發表,並且原始碼也會一併上傳。理論知識部分,個人覺得整理的還是很詳細的而且重點分明,無論是對面試還是對程式碼的理解都能起到很好的輔助作用。
:本文於2017/4/30號進行了更新。增加了一些新內容,又繪製了一些新的圖形,更有助於對知識點的理解。

View相關知識的理解
在說自定義控制元件之前讓我先來了解一下ViewViewGroup

整個View樹的結構圖


這裡寫圖片描述

關於View和ViewGroup我們需要注意以下幾點:

1. 手機螢幕上的整個介面只有一個根View

如何得到它:activity.getWindow().getDecorview() —>PhoneWindow$decorView
本質型別: FrameLayout
注意: setContentView():執行新增的檢視不是整個介面的根View

2.一個View只會有一個父View(ViewGroup),一個ViewGroup可以有多個子View

a.得到父檢視: view.getParent(),可以將返回的ViewParent強轉為指定的ViewGroup
b.不是所有的View都能新增子view

,只有ViewGroup 及其子類才能新增


View是什麼

  • View類是所有用來構建使用者介面的元件的基類
  • 一個View物件佔用螢幕上的一個矩形區域, 它負責介面的繪製和事件處理
  • 手機螢幕上所有看得見摸得著的都是View
  • 常見的View:TextView,EditText,Button,ImageView,ProgressBar…

ViewGroup是什麼

  • ViewGroup類是View的一個子類, 是各種佈局(五大布局)的基類
  • 一個ViewGroup可以包含多個子View(ViewGroup)
  • 作用: 控制子View的佈局,view.layout(left, top, right, bottom)
  • ViewManager及相關方法:
    ① addView():新增子View
    ② removeView():刪除子View
    ③ updateViewLayout():更新子View
  • 常見的ViewGroup:LinearLayout,RelativeLayout,FrameLayout,ListView…

View的位置怎麼確定

  • Veiw的位置是由它(左上右下)四個頂點確定的。
    top:左上角縱座標
    bottom:右下角縱座標
    left:左上角橫座標
    right:右下角橫座標
    它們都是相對座標,都是相對於View的父容器來說的
    這裡寫圖片描述
    如上圖可得View的寬高與座標的關係:
    width= right - left
    height= bottom - top

  • 那麼,How to 得到View的這四個引數?
    View原始碼中它們對應mLeft,mTop,mRight,mBottom四個成員變數,獲取方式:
    left = getLeft();
    top = getTop();
    right = getRight();
    bottom = getBottom();

  • 注:Android 3.0開始額外增加了幾個引數: x ,y —是View左上角的座標
    translationX,translationY—是View左上角相對於父容器的偏移量(預設值為0)
    這幾個引數也是相對於父容器的相對座標。
    它們的換算關係:
    x = left +translationX;
    y = top + translationY;
    需注意,View在平移過程中,top,left表示的是原始坐上角的位置資訊,其值不會發生變化。
    此時發生改變的是x,y,translationX,translationY這四個引數。

View(及其子類)的生命週期及其他知識點回顧

1. 建立物件

  • 建立方式(2種):
    new MyView(context)
    載入佈局檔案,即自定義View必須使用全類名標籤
  • 流程方法
    ①構造器
       Xxx(Context context)
      Xxx(Context context, AttributeSet set)
    ②onFinishInflate()
      只有佈局的方式才會呼叫
      重寫的目的: 得到子View –>getChildAt(int index):index按照載入順序排列
      onAttachedToWindow()–>重寫: 得到子View

  • 補充 Activity的onResume()執行之後才會進入後面的流程

2.View的工作流程

  • View的工作流程主要是指measure、layout、draw這三大流程,即測量、佈局和繪製。無圖不歡,這裡給大家畫了一個圖:
    這裡寫圖片描述
    通過上面的圖形,相信你就可以很明白的知道,各個環節是幹嘛的,可以得到什麼,以及它的意義所在了。下面再讓我們進行具體的分析。

2.1 測量

  • 作用:計算並確定檢視的大小(測量的寬/高)

  • 流程方法:
      ①.mesure() :系統在此方法中測量計算出當前檢視的寬高,此方法不能重寫
      ②.onMesure()
        當mearure()中 計算出的檢視的寬高就會呼叫此方法, 在此方法預設儲存的
        檢視測量的寬高
        注意:檢視測量的寬高不等同於檢視的寬高。獲取的時機不同
        重寫的意義:得到當前檢視/子檢視測量的寬高;儲存我們自己指定的寬高

2.2 佈局

  • 作用:確定檢視顯示的座標(left, top, right, bottom)以及View最終的寬/高

  • 流程方法:
      ①.layout() :layout(l, t, r, b),不會重寫此方法, 只會呼叫檢視物件的此方法, 指
      定其新的顯示位置
      ②.onLayout()
        在layout()的過程中 如果①檢視的位置change或②強制重新佈局就會調
        此方法
        重寫它: 可以對子View進行重新佈局,呼叫childView.layout(left, top, right,
        bottom)

  • 強制重新佈局: view.requestLayout()

2.3.繪製

  • 作用:畫出檢視的樣子,決定View的顯示

  • 流程方法:
    draw(),繪製檢視通用的部分,確定繪製的流程,一般不會重寫此方法
    onDraw(),重寫此方法,繪製自己需要的樣子,一些具體的View類(如:TextView,ImageView)都重寫了此方法

  • 強制重繪:
    invalidate():只能在主執行緒執行
    postInvalidate():可以在主執行緒或分執行緒執行

注意!

  • 細心的朋友可能注意到了,我這裡提到了measure過程中我們得到的是View的測量的寬/高,layout過程我們得到的是View的最終的寬/高。那這測量的寬/高和最終的寬/高它們有什麼區別呢?
    我們可以這麼理解,由於兩者的賦值時機不同,即一個measure過程,一個layout過程,這就導致了一個先後的問題,即測量的寬/高先出生,最終的寬/高後出生。在這裡我舉一個具體點的例子,可能不是非常的合理,但確實有助於理解:
    這裡寫圖片描述
    如圖,在佈局中你可能在外部設定了一個ScrollerView,並且設定為match_parent,然後它的內部的內容如虛線內所示,比螢幕的高度要長,那麼這時候我們在measure過程中的到的測量高/寬最大也只能為螢幕的高,而在layout過程中得到的實際寬/高,確是比螢幕的高要長的。
    當然大多數情況下,測量的寬/高和最終實際的寬/高是相同的,但是當遇到一些特殊情境時我們還是要多多小心。

  • 這裡我們還要注意一點:那就是View的measure過程和Activity的生命週期方法不是同步執行的,因此無法保證Activity執行了onCreate,onStart, onResume時某個View就測量完畢了。如果View還沒有測量完畢,那麼獲得的寬/高就是0.

    所以如果我們想在Activity已啟動的時候就做一件任務,但是這一件任務需要獲取某個View的寬/高。那麼這個時候你就不要在天真地告訴我:在onCreate或者onResume裡面去獲取這個View的寬/高不就行了?真是too young too simple,不可以的哈!

    這裡給大家幾個解決辦法以供參考:
    ①我首推的是ViewTreeObserver,沒錯檢視樹。大家可以看下我高仿各大商城引導頁面的那篇文章,我在處理下部的小圓點,需要獲得它的間距的時候,我就使用了檢視樹的OnGlobalLayoutListener這個介面,當View樹的狀態發生改變或者Veiw樹內部的View的可見性發生改變是,onGlobalLayout方法就會被呼叫,因此我在這裡面去獲取小圓點間的間距就是一個很好的時機,對應部分的程式碼如下:

 //獲取樹形檢視,每次頁面佈局完成時會呼叫,獲取點間的距離
        ivWhitePoint.getViewTreeObserver().addOnGlobalLayoutListener(new MyOnGlobalLayoutListener());
 private class MyOnGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {

        @Override
        public void onGlobalLayout() {
            //預設會呼叫倆次,只需要一次,第一次進入就移除
            ivWhitePoint.getViewTreeObserver().removeGlobalOnLayoutListener(MyOnGlobalLayoutListener.this);
            //間距 = 第1個點距離左邊距離 - 第0個點距離左邊距離
            leftmax = llPointGroup.getChildAt(1).getLeft() - llPointGroup.getChildAt(0).getLeft();
        }
    }

    private class MyOnPageChangeListener implements ViewPager.OnPageChangeListener {
        /**
         * 當頁面滑動回撥會呼叫此方法
         *
         * @param position             當前頁面位置
         * @param positionOffset       當前頁面滑動百分比
         * @param positionOffsetPixels 滑動的畫素數
         */
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            //紅點移動的距離 = ViewPager頁面的百分比* 間距
            //座標 = 起始位置 + 紅點移動的距離;
            int leftmagin = (int) (position * leftmax + (positionOffset * leftmax));
            ...

        }
...
    }

②當然你也可以使用onWindowFocusChanged這個方法,這個方法的含義是:View已經初始化完畢了,寬/高已經準備好了,這個時候去獲取寬/高是沒有問題的。
③view.post(runnable),通過psot可以將一個runnable投遞到訊息佇列的尾部,然後等待Looper呼叫此runnable的時候,View也已經初始化好了。

3.事件處理

  • 流程方法:
    dispatchTouchEvent():
        分發事件,從外向裡一層一層分發, 分發到事件發生的最裡面的檢視物件
    boolean onInterceptTouchEvent():
        攔截請求, 只有return true才攔截成功,如果事件被攔截,事件不會再向內層分
        發, 交給當前的檢視處理
    boolean onTouchEvent():
        處理事件:消費事件的條件: return true
    requestDisallowInterceptTouchEvent(true)
        反攔截–>view.getParent().requestDisallowInterceptTouchEvent(true)

  • 事件機制:
    ①分發: 將TouchEvent物件從Activity物件開始, 由外向內分發給對應的佈局和子View物件(由外向內分發)。

    ②處理: 回撥OnTouchListener的boolean onTouch()
             回撥View的boolean onTouchEvent()

    ③消費: 回撥方法返回true

    ④攔截: onInterceptTouchEvent()執行返回true
             如果返回true, TouchEvent就不會再傳入子View物件

    ⑤反攔截: view.getParent().requestDisallowInterceptTouchEvent(true)
               使父View不能再攔截, 事件就會分發到當前View物件
               攔截與反攔截,都是在分發的時候就要決定的。

4.死亡

  • 什麼時候死亡:
    Activity死亡之前
    檢視物件被移除

  • 流程方法
    onDetachedFromWindow()

相關推薦

定義View---View基礎概念工作流程以及生命週期理解

不詩意的女程式猿不是好廚師~ 序:最近在工作中使用到了各種自定義控制元件,也更深刻的理解了自定義控制元件的重要性,所以就建了一個專欄來專門整理自定義控制元件的相關知識。我打算先從理論知識說起,然後再把專案中使用的自定義控制元件整理後寫為部落格發表,並

定義ViewView工作原理之測量 measure

在Android中,一個View繪製出來要經過三大流程,分別用measure來測量View的寬高,用layout來確定View在父容器中的位置,最終用draw將View繪製到螢幕上。本章節主要,通過自己的理解來講解一下第一個流程measure的相關知識點。 measure方法在View類中,

Android:從繪製張流程圖來體驗View定義過程

概述         自定義View向來不是一個輕鬆的話題。雖然對於android原生開發而言,這隻能算是一項基本的技能,但真正當我們拿到一份需要我們自定義去實現的需求的時候,多少還會有點手足無措。具體是什麼原因,我也不知道,也許,“菜,是原罪”。‘’“業精於勤

js基礎定義屬性

aLi[i].onclick=function(){if(this.onOff){this.style.background='url(img/normal.png)';this.onOff=false}else{this.style.background='url(img/active.png)';this

Android 手把手教您定義ViewGroup

最近由於工作的變動,導致的部落格的更新計劃有點被打亂,希望可以儘快脈動回來~今天給大家帶來一篇自定義ViewGroup的教程,說白了,就是教大家如何自定義ViewGroup,如果你對自定義ViewGroup還不是很瞭解,或者正想學習如何自定義,那麼你可以好好看看這篇部落格。1、概述在寫程式碼之前,我必須得

exports構建定義模組

exports可以向外部檔案暴露方法和屬性,同過載單獨js檔案內寫方法向外部暴露呼叫方法就能完成模組的定義。 demo1: exports_test1.js var name; exports.s

Ecshop二次開發:如何建立一個定義頁面

今天自己本機用wampserver搭建了一個環境,然後下載了Ecshop官方(UTF-8)最新版本,然後進行熟悉。 進入後臺簡單的操作了一下,想自己寫一個php頁面,並在模版中調用出來值。 第一步:在根目錄新建了一個test.php頁面。並寫上如下程式碼: 不過百度了一下

JSP定義標籤 樹形下拉選擇選單

<tag>       <name>selector</name>       <tag-class>com.moonNigh.tagSupport.SelectorTag</tag-class>       <body-content&

Android定義錄影之錄影功能實現(附demo原始碼)

引言 最近在做一個專案,是有關用手機攝像頭做影象實時識別的。所以裡面需要自定義一個錄影功能。該demo實現了錄影和錄影後文件的儲存檢視,錄影會實時自動對焦(AutoFocus)。根據功能分兩篇講述。這第一篇講述錄影基本實現思路和需要注意的點。後面附有githu

安卓定義Dialog

這個自定義Dialog主要是提醒使用者一些資訊:該環境沒有網,登入賬號是提示密碼錯誤.... 話不多說直接上程式碼: 一.實現功能的.java類 public static Dialog CreatDialog(Context context, String s,

Spring AOP學習筆記基礎概念

AOP產生背景 AOP(Aspect Oriented Programming),即面向切面程式設計,可以說是OOP(Object Oriented Programming,面向物件程式設計)的補充和完善。OOP引入封裝、繼承、多型等概念來建立一種物件層次結構,用於模擬公共行為的一個集合。不

MongoDB入門系列基礎概念和安裝

概述   MongoDB是目前非常流行的一種非關係型資料庫,作為入門系列的第一篇本篇文章主要介紹Mongdb的基礎概念知識包括命名規則、資料型別、功能以及安裝等。 環境: OS:Windows Version:3.4 一、安裝 1.下載解壓 在官網下載對應作業系統版本的安裝包,然後解壓;

Python學習筆記基礎語法變數型別運算子快速入門篇

Head First Python、Python基礎教程 下劃線的特殊意義 以下劃線開頭的識別符號是有特殊意義的。 以單下劃線開頭(_foo)的代表不能直接訪問的類屬性,需通過類提供的介面進行訪問,不能用”from xxx import *”而匯入

javase複習整理基礎要點重點易錯點多執行緒梳理總結

最近抽出時間從新回頭複習了一下javase基礎,把自己以前理解的不透徹和易錯的知識點重新梳理了一下,便於以後查閱。那麼接下來就開始複習總結! 一、java語言基礎 1、在java中,邏輯運算子“&

Netty基礎概念及訊息處理流程

1.  Netty是什麼?   Netty是由JBOSS提供的一個java開源網路通訊框架。Netty可以提供非同步的,非阻塞的,事件驅動的網路應用程式框架和工具,非常適合用來快速開發高效能、高可靠

Apache Flink 零基礎入門基礎概念解析

Apache Flink 的定義、架構及原理     Apache Flink 是一個

linux常用命令linux開關機重啟以及文本界面與圖形界面互換

調用 虛擬 界面切換 use entos span 常用 run 現在 1.開關機 reboot 重啟: shutdown [-efFhknr][-t 秒數][時間][警告信息] 關機: shutdown [-efFhknr][-t 秒數][時間][警告信息] 關機:

Android 定義View

前言:可是有時候我們總感覺官方定義的一些基本元件不夠用,自定義元件就不可避免了。那麼如何才能做到像官方提供的那些元件一樣用xml來定義他的屬性呢? 先總結下自定義View的步驟: 1、自定義View的屬性; 2、在View的構造方法中獲得自定義的屬性。 一、在re

Android學習—簡單定義View

最近手上不忙所以回顧了一下自己今年來所接觸和學習的東西,突然覺得寫部落格真是一個很好的方式,希望自己 可以堅持下去。 自定義View的流程 建立自定義類繼承View,並重寫構造方法,構造方法總共有四種,我們暫時只需要繼承前兩種 public CircleVi

android定義View正弦波水波紋

文章目錄 1、正弦曲線知識 2、靜態正弦曲線繪製 3、動態正弦曲線繪製 4、完整原始碼 1、正弦曲線知識 對這個初中知識遺忘了的可以先看看正弦曲線百度百科詞條方便加深理解。