導航:首頁 > 操作系統 > android基礎控制項

android基礎控制項

發布時間:2023-04-14 19:27:04

android自定義控制項怎麼用

開發自定義控制項的步驟:
1、了解View的工作原理
2、 編寫繼承自View的子類
3、 為自定義View類增加屬性
4、 繪制控制項
5、 響應用戶消息
6 、自定義回調函數

一、View結構原理
Android系統的視圖結構的設計也採用了組合模式,即View作為所有圖形的基類,Viewgroup對View繼承擴展為視圖容器類。
View定義了繪圖的基本操作
基本操作由三個函數完成:measure()、layout()、draw(),其內部又分別包含了onMeasure()、onLayout()、onDraw()三個子方法。具體操作如下:
1、measure操作
measure操作主要用於計算視圖的大小,即視圖的寬度和長度。在view中定義為final類型,要求子類不能修改。measure()函數中又會調用下面的函數:
(1)onMeasure(),視圖大小的將在這里最終確定,也就是說measure只是對onMeasure的一個包裝,子類可以覆寫onMeasure()方法實現自己的計算視圖大小的方式,並通過setMeasuredDimension(width, height)保存計算結果。

2、layout操作
layout操作用於設置視圖在屏幕中顯示的位置。在view中定義為final類型,要求子類不能修改。layout()函數中有兩個基本操作:
(1)setFrame(l,t,r,b),l,t,r,b即子視圖在父視圖中的具體位置,該函數用於將這些參數保存起來;
(2)onLayout(),在View中這個函數什麼都不會做,提供該函數主要是為viewGroup類型布局子視圖用的;

3、draw操作
draw操作利用前兩部得到的參數,將視圖顯示在屏幕上,到這里也就完成了整個的視圖繪制工作。子類也不應該修改該方法,因為其內部定義了繪圖的基本操作:
(1)繪制背景;
(2)如果要視圖顯示漸變框,這里會做一些准備工作;
(3)繪制視圖本身,即調用onDraw()函數。在view中onDraw()是個空函數,也就是說具體的視圖都要覆寫該函數來實現自己的顯示(比如TextView在這里實現了繪制文字的過程)。而對於ViewGroup則不需要實現該函數,因為作為容器是「沒有內容「的,其包含了多個子view,而子View已經實現了自己的繪制方法,因此只需要告訴子view繪制自己就可以了,也就是下面的dispatchDraw()方法;
(4)繪制子視圖,即dispatchDraw()函數。在view中這是個空函數,具體的視圖不需要實現該方法,它是專門為容器類准備的,也就是容器類必須實現該方法;
(5)如果需要(應用程序調用了setVerticalFadingEdge或者setHorizontalFadingEdge),開始繪制漸變框;
(6)繪制滾動條;
從上面可以看出自定義View需要最少覆寫onMeasure()和onDraw()兩個方法。

二、View類的構造方法
創建自定義控制項的3種主要實現方式:
1)繼承已有的控制項來實現自定義控制項: 主要是當要實現的控制項和已有的控制項在很多方面比較類似, 通過對已有控制項的擴展來滿足要求。
2)通過繼承一個布局文件實現自定義控制項,一般來說做組合控制項時可以通過這個方式來實現。
注意此時不用onDraw方法,在構造廣告中通過inflater載入自定義控制項的布局文件,再addView(view),自定義控制項的圖形界面就載入進來了。
3)通過繼承view類來實現自定義控制項,使用GDI繪制出組件界面,一般無法通過上述兩種方式來實現時用該方式。

三、自定義View增加屬性的兩種方法:
1)在View類中定義。通過構造函數中引入的AttributeSet 去查找XML布局的屬性名稱,然後找到它對應引用的資源ID去找值。
案例:實現一個帶文字的圖片(圖片、文字是onDraw方法重繪實現)

public class MyView extends View {

private String mtext;
private int msrc;

public MyView(Context context) {
super(context);
}

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
int resourceId = 0;
int textId = attrs.getAttributeResourceValue(null, "Text",0);
int srcId = attrs.getAttributeResourceValue(null, "Src", 0);
mtext = context.getResources().getText(textId).toString();
msrc = srcId;
}

@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
InputStream is = getResources().openRawResource(msrc);
Bitmap mBitmap = BitmapFactory.decodeStream(is);
int bh = mBitmap.getHeight();
int bw = mBitmap.getWidth();
canvas.drawBitmap(mBitmap, 0,0, paint);
//canvas.drawCircle(40, 90, 15, paint);
canvas.drawText(mtext, bw/2, 30, paint);
}
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<com.example.myimageview2.MyView
android:id="@+id/myView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Text="@string/hello_world"
Src="@drawable/xh"/>

</LinearLayout>

屬性Text, Src在自定義View類的構造方法中讀取。

2)通過XML為View注冊屬性。與Android提供的標准屬性寫法一樣。
案例: 實現一個帶文字說明的ImageView (ImageView+TextView組合,文字說明,可在布局文件中設置位置)

public class MyImageView extends LinearLayout {

public MyImageView(Context context) {
super(context);
}

public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
int resourceId = -1;
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.MyImageView);
ImageView iv = new ImageView(context);
TextView tv = new TextView(context);
int N = typedArray.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.MyImageView_Oriental:
resourceId = typedArray.getInt(
R.styleable.MyImageView_Oriental, 0);
this.setOrientation(resourceId == 1 ? LinearLayout.HORIZONTAL
: LinearLayout.VERTICAL);
break;
case R.styleable.MyImageView_Text:
resourceId = typedArray.getResourceId(
R.styleable.MyImageView_Text, 0);
tv.setText(resourceId > 0 ? typedArray.getResources().getText(
resourceId) : typedArray
.getString(R.styleable.MyImageView_Text));
break;
case R.styleable.MyImageView_Src:
resourceId = typedArray.getResourceId(
R.styleable.MyImageView_Src, 0);
iv.setImageResource(resourceId > 0 ?resourceId:R.drawable.ic_launcher);
break;
}
}
addView(iv);
addView(tv);
typedArray.recycle();
}
}

attrs.xml進行屬性聲明, 文件放在values目錄下

<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="MyImageView">
<attr name="Text" format="reference|string"></attr>
<attr name="Oriental" >
<enum name="Horizontal" value="1"></enum>
<enum name="Vertical" value="0"></enum>
</attr>
<attr name="Src" format="reference|integer"></attr>
</declare-styleable>

</resources>

MainActivity的布局文件:先定義命名空間 xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2" (com.example.myimageview2為你
在manifest中定義的包名)
然後可以像使用系統的屬性一樣使用:uview:Oriental="Vertical"

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />

<com.example.myimageview2.MyImageView
android:id="@+id/myImageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
uview:Text="這是一個圖片說明"
uview:Src="@drawable/tw"
uview:Oriental="Vertical">
</com.example.myimageview2.MyImageView>

</LinearLayout>

四、控制項繪制 onDraw()

五、
六:自定義View的方法
onFinishInflate() 回調方法,當應用從XML載入該組件並用它構建界面之後調用的方法
onMeasure() 檢測View組件及其子組件的大小
onLayout() 當該組件需要分配其子組件的位置、大小時
onSizeChange() 當該組件的大小被改變時
onDraw() 當組件將要繪制它的內容時
onKeyDown 當按下某個鍵盤時
onKeyUp 當松開某個鍵盤時
onTrackballEvent 當發生軌跡球事件時
onTouchEvent 當發生觸屏事件時
onWindowFocusChanged(boolean) 當該組件得到、失去焦點時
onAtrrachedToWindow() 當把該組件放入到某個窗口時
onDetachedFromWindow() 當把該組件從某個窗口上分離時觸發的方法
onWindowVisibilityChanged(int): 當包含該組件的窗口的可見性發生改變時觸發的方法

❷ 深入理解android2-WMS,控制項

title: '深入理解android2-WMS,控制項-圖床版'
date: 2020-03-08 16:22:42
tags:
typora-root-url: ./深入理解android2-WMS-控制項
typora--images-to: ./深入理解android2-WMS-控制項

WMS主要負責兩個功能, 一是負責窗口的管理,如窗口的增加刪除,層級.二是負責全局事件的派發.如觸摸點擊事件.

先簡單介紹幾個重要的類

IWindowSession. 進程唯一的.是一個匿名binder.通過他向WMS請求窗口操作

surface 繪畫時,canvas會把內容繪制到surface里.surface是有surfaceFlinger提供給客戶端的.

WindowManager.LayoutParams 集成自ViewGroup.LayoutParams.用來指明client端的窗口的一些屬性.最重要的是type. 根據這屬性來對多個窗口進程ZOrder的排序.

windowToken.向WMS添加的窗口令牌.每個窗口都要有一個令牌.

IWindow. 是client提供給WMS的.繼承自binder.WMS通過IWindow對象來主動發起client端的事件.

窗口的本周就是進行繪制所使用的surface,客戶端向WMS添加窗口的過程,就是WMS為客戶端分配surface的過程.

ui框架層就是使用surface上繪制ui元素.及響應輸入事件

WMS負責surface的分配.窗口的層級順序

surfaceFlinger負責將多個Surface混合並輸出.

WMS有SystemServer 進程啟動.他和AMS其實是運行於一個進程中的.只是分別有各自的線程.

上邊傳入了兩個handler.這里就使用windowManager的handler來創建WMS.也就是在一個handerThread線程中創建

用來管理每個窗口的事件輸入.也就是把輸入事件轉發到正確的窗口

能獲取顯示系統的同步信號.用來驅動動畫的渲染

所有窗口動畫的總管,在mChoreographer的驅動下渲染所有動畫

只有PhoneWindowManager一個實現.定義了很多窗口相關的策略.是最重要的成員,比如負責窗口的zorder順序.

zorder就是各個窗口在z軸的值.越大越在屏幕上層.窗口就是根據zorder值一層一層堆在一起.

可以繪制的屏幕列表.默認是只有1個.

管理所以窗口的顯示令牌token,每個窗口都要屬於一個token.這里的IBinder 是

表示所有Activity的token. AppWindowToken是WindowToken的子類,這個list的順序和AMS中對mHistory列表中activity的順序是一樣的 .反應了系統中activity的疊加順序.也就是說.所有窗口都有WindowToken.而Activity對應的窗口則多了AppWindowToken.

每個窗口都對應一個WindowState.存儲改窗口的狀態信息(這就和AMS中對每個activity抽象成ActivityRecord一樣)

這里的iBinder 是IWIndow類.

Session 是WMS提供給客戶端來與WMS進行交互的,這是匿名binder.為了減輕WMS的負擔.客戶端通過IWindowManager.openSession 拿到他的代理.然後通過代理與WMS交互.每個進程唯一.

客戶端通過IWindowSession.add 來添加窗口. iWindowSession 是同aidl形成的.最終到了WMS.addWindow.

這里總的來說就是確立了客戶窗口的WindowToken.WindowState.和DisplayContent. 並都保存了起來.同時根據layoutparams.type進行了些窗口等級的判斷.

WindowToken將同一個應用組件的窗口安排在一起.一個應用組件可以是Activity,InputMethod.

WindowToken使應用組件在變更窗口時必須與自己的WindowToken匹配.

這里主要是為了處理窗口的層級關系而設立的.

只要是一個binder對象.都可以作為token向wms聲明.wms會把這個binder對應起一個WindowToken.其實就是把客戶端的binder和wms里的一個WindowToken對象進行了綁定.

因為Activity比較復雜,因此WMS為Activity實現了WindowToken的子類 appwindowtoken.同時在AMS啟動Activity的ActivityStack.startActivityLocked里聲明token.

然後在activityStack.realStartActivityLocked里在發給用戶進程,然後用戶在通過這個binder和WMS交互時帶過來.

activity則在 activityStack 線程的handleResumeActivity 里把Activity 對應的窗口,加入到wMS中

取消token 也是在AMS中 ,也就是說, AMS負責avtivity的token向WMS的添加和刪除.

當然.Activity的 r.appToken 是 IApplicationToken.Stub ,他里邊有一系列的窗口相關的通知回調.

這里總結下. AMS在創建Activity的ActivityRecord時,創建了他的appToken,有把appToken傳送給WMS.WMS對應匹配為APPWindowToken,最後還把這個appToken發送給activity.因此AMS就通過ActivityRecord就可有直接操作WMS對該窗口的繪制.如圖.

每個window在WMS里都抽象成了WindowState.他包含一個窗口的所有屬性.WindowState在客戶端對應的則是iWidow.stub類.iWidow.stub有很多窗口通知的回調.

WindowState被保存在mWindowMap里.這是整個系統所有窗口的一個全集.

HashMap<IBinder, WindowToken> mTokenMap .這里是 IApplicationToken(客戶端)和WindowToken的映射

HashMap<IBinder, WindowState> mWindowMap 這里是IWidow(客戶端)和WindowState的映射,並且WMS通過這個IWindow 來回調客戶端的方法.

上圖可以看出.每個activity 只有一個ActivityRecord.也只有一個AppToken,也就只有一個WindowToken.而一個acitvity可能有多個窗口.每個窗口對應一個WindowState.

WindowToken用來和AMS交換. 而WindowState對應的iWindow則是WMS來與客戶端交互的.

窗口顯示次序就是窗口在Z軸的排了.因為窗口是疊加在一起的.因此就需要知道哪些顯示在上邊,哪些在下邊.這個由WindowState構造時確定

可見.分配規則是由WindowManagerPolicy mPolicy來決定的.產生 mBaseLayer和mSubLayer. mBaseLayer決定該窗口和他的子窗口在所有窗口的顯示位置. mSubLayer決定子窗口在同級的兄弟窗口的顯示位置.值越高.顯示約靠上.

WindowState 產生了他自己這個窗口的layer值後.在添加窗口的時候就會把所有窗口按layer排序插入mWindows列表中,在通過 adjustWallpaperWindowsLocked();進行層級調整.

當客戶端通過IWindowsession.add後,客戶端還沒有獲得Surface.只有在執行IWindowsession.relayout後.客戶端才獲得了一塊Surface. IWindowsession.relayout根據客戶端提供的參數,為客戶端提供surface.具體實現是WMS.relayoutWindow

總的來說就是根據用戶傳入的參數,更新WindowState.然後遍歷所有窗口布局.在設置合適的Surface尺寸,在返回給用戶端

會循環調用6次.里邊的邏輯大概如下

這里主要下,因為之前加了鎖.requestTraversalLocked他又會重復執行();因此會重復循環執行布局.

布局這部分就記個原理吧

布局完成後.客戶端的尺寸和surface都得到了.就可以繪制 了.WMS會通知客戶端布局發送變化

總結,WMS 負責管理所有的窗口.包括系統窗口和APP窗口,而窗口必須有一個WindowToken所為標識符.同時WMS為每個窗口創建一個WindowState類,這是窗口在服務端的抽象.WindowState則綁定了一個客戶端的IWindow類,WMS通過這個IWindow 向APP發送消息.

AMS在啟動Activity的時候.把ActivityRecord.token 通過wms.addtoken 注冊到WMS.又把這個token發送到APP端.因此三方可以通過這個token正確找到對應的數據.

WMS負責給所以窗口按ZOrder排序,確定窗口的尺寸,提供繪畫用的surface.

Activity的窗口是先wms.addtoken 建立windowToken關系 . wms.addWindow. 添加串口, WMS.relayout獲取surface. 完成 .

一個windowToken對應一個Activity. 但是可能對應多個windowSatate.也就是對應多個窗口.

是view樹的根實現類是viewRootImpl.但是他不是view.他是用來和WMS進行交流的管理者.viewRootImpl內部有個IWindowSession,是WMS提供的匿名binder,同時還有個iWindow子類,用來讓WMS給viewr發消息. view通過ViewRoot向WMS發消息.WMS在通過IWIndow 向APP發消息. 每個View樹只有一個ViewRoot,每個Activity也只有一個ViewRoot. UI繪制,事件傳遞.都是通過ViewRoot.

.實現類是PhoneWindow . Activity和View的溝通就是通過Window.Activity實現window的各種回調.一個Activity也對應一個PhoneWindow.也對應一個View樹.

Docerview 就是View樹的根.這是一個View. 他由PhoneWindow管理. 下文的WindowManager也由phoneWindow管理.

他還管理window的屬性 WindowManager.layoutparams.

他是一個代理類.他集成自ViewManager.他的實現是WindowManagerImpl.這是每個Activity都有一個.但是他只是把工作委託給了 WindowManagerGlobal來實現. 他負責添加刪除窗口,更新窗口.並控制窗口的補件屬性WindowManager.Layoutparams.

是進程唯一的.負責這個進程的窗口管理.他里邊有三個集合.保存這個進程所有窗口的數據.這里的每個數據根據index得到的是同一個Activity屬性.所有的WindowManager的操作都轉到他這里來.

private final ArrayList<View> mViews 每個view是個跟節點

private final ArrayList<ViewRootImpl> mRoots view對應的viewRoot

private final ArrayList<WindowManager.LayoutParams> mParams 窗口的layoutparams屬性.每個窗口一個

對於一個acitivity對象永遠對應一個PhoneWindow,一個WindowManagerImpl,一個WMS端的APPWindowToken,一個AMS里的ActivityRecord(但是如果一個activity在棧里有多個對象,就有多個ActivityRecord和AppWindowToken),acitvity 的默認窗口的view樹是DocerView.

一個窗口 對應一個ViewRoot,一個View樹.一個WindowManager.LayoutParams,一IWindow(WMS回調app).一個WSM端的WindowSatate.

但是一個Activity可以有多個窗口,因此對應WMS里可能有多個WindowSatate.這些WindowState都對應一個AppWindowToken.

一個Activity可能被載入多次.因此在AMS中可能有多個ActivityRecord對應這個activit的多個對象.

但是一個進程則對應一個WindowManagerGlobal.一個ActivityThread(主線程).一個ApplicationThread(AMS調用app).一個iWindowSession(viewroot向WMS發消息)

這里的區別就是 app與AMS 的交互是以進程之間進行通信.而App與WMS的交互.則是以窗口作為通信基礎.

當Activity由AMS啟動時.ActivityThread 通過handleResumeActivity執行resume相關的操作.這個函數首先是執行activity.resume, 此時activity 對應的view樹已經建立完成(oncreate中建立,PhoneWindow也創建了).需要把activity的窗口添加到WMS中去管理.

這里的wm是WindowManager.是每個activity一個.他內部會調用WindowManagerGlobal.addView

WindowManagerGlobal.addView

這里會為窗口創建ViewRootImpl. 並把view.ViewRootImpl.WindowMa.LayoutParams都保存在WindowManagerGlobal中, 並通過ViewRootImpl向WMS添加窗口

如果這個窗口是子窗口(wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW)

就把子窗口的token設為父窗口的token否則就是所屬activity的token.

在來個圖

在這里我們看到.我們通過mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 拿到的並不是遠程的WMS.而是本地的WindowManagerImpl. 他又把請求轉發給WindowManagerGlobal ,而WindowManagerGlobal作為進程單實例.又是吧請求轉給對應窗口的ViewRootImpl.ViewRootImpl通過WMS的IWindowSession 把數據發給WMS.

ViewRootImpl用來溝通View和WMS.並接受WMS的消息.這是雙向的binder通信.作為整個空間樹的根部,控制項的測量,布局,繪制,輸入時間的派發都由ViewRootImpl來觸發.

ViewRootImpl由WindowManagerGlobal創建,是在activityThread.handleResumeActivity時,先執行activity.resume.在調用wm.addView. 就會執行WindowManagerGlobal.addView里.創建ViewRootImpl,此時是在ui線程中.

ViewRootImpl里的mView屬性.host屬性,就是view樹

添加窗口時通過requestLayout();向ui線程發送消息.最後回調到ViewRootImpl.performTraversals.他是整個ui控制項樹,measure.layout.draw的集合.

這里分為五個階段.

預測量階段.進行第一次測量,獲得view.getMeasuredWitdh/Height,此時是控制項樹期望的尺寸.會執行View的onMeasure

布局階段,根據預測量的結果,通過IWindowSession.relayout向WMS請求調整窗口的尺寸這會使WMS對窗口重新布局,並把結果返回給ViewRootImpl.

最終測量階段, 預測量的結果是view樹期望的結果.WMS可能會進行調整,在這里WMS已經把結果通知了ViewRootImpl.因此這里會窗口實際尺寸performTraversals進行布局.view及子類的onMeasure會被回調

布局階段. 測量完成後獲得空間的尺寸,布局要確定控制項的位置,View及子類的onLayout會被回調.

繪制階段,使用WMS提供的surface.進行繪制,View及子類的onDraw會被回調.

通常我們看到的都是 先measure,在layout在draw. 這里看到.其實measure先得到期望值,在和WMS溝通.WMS在調整後,返回確定值,在根據確定值進行mesure.

measureHierarchy里會通過三次協商.執行performMeasure 來確認合適的尺寸.

performMeasure 會調用view 的measure 優會調用onMeasure. 我們可重寫onMeasure來實現測量.而measure 方法是final的.onMeasure 的結果通過setMeasuredDimension方法保存.

對於view. onMeasure.比較容易. 對於ViewGroup.則還要遍歷調用他所以子view的measure. 並且需要考慮padding和子view 的margin. padding是控制項外內邊距. margin 是控制項外邊距.

ViewGroup需要先測量完子view.在根據子view的測量值得到自己的寬高.舉例,如果只有一個子view.那麼ViewGroup的寬= 子view的寬+子view的margin+viewg的padding. 至少是這個值.

繼續回到performTraversals

這里就是提前測量了一下.得到控制項樹希望的尺寸大小,

通過relayoutWindow來布局窗口. ViewRootImpl 通過IWindowSession 來通知WMS進行窗口布局.

這里主要下. 調用WMS後.WMS會調整窗口的尺寸. 同時會生成surface返回給ViewRootImpl. 因此後續的繪畫就有了畫布了.可以看到最後的參數是mSurface.這是本地的surface. 這里會和wms的進行綁定.

接下來繼續performTraversals,綁定WMS返回的surface.然後更新尺寸.

最後進行最終測量. 上邊過程太亂了. 了解下就行.還是看常見的控制項繪制流程.

繪制由viewRootImpl.performTraversals觸發, 抽取出來後,就是這樣

就是直接調用view樹的根的measure方法. 傳入到View

該方法是final .意味著無法重寫.這里又會調用onMeasure.

因此.對於view.在onMeasure中調整好高度,通過setMeasuredDimension設置好自己的測量寬高就可以了.

對應ViewGroup.則在onMeasure中,先要遍歷子view.調用他們的measure(注意一定是調用子類的measure,measure又會調用onMeasure), 子view寬高都知道後,在根據子view的寬高來設置自己.也就是ViewGroup的寬高受子view影響.

可以看到view的measure又調用了onMeasure, 如果是view 則可以直接重新onMeasure來設定大小.而對於ViewGroup, 則需要重寫onMeasure來先遍歷子view.設定大小.然後再設定viewGroup的大小. ViewGroup並沒有重寫onMeasure.因為每個ViewGroup要實現的效果不同,需要自己完成.但ViewGroup提供了幾個方法供ViewGroup的繼承類來遍歷子view.

view的寬高由自己的layoutParams和父view提供的 widthMeasureSpec|heightMeasureSpec共同決定.

View 自己的寬高,是保存在LayoutParams中對,以寬舉例 LayoutParams.width 有三種情況,精確值(就是指定大小),MATCH_PARENT. WRAP_CONTENT,模式則有fuview提供.有 unspecified,exactly,at_most三種.

匹配如下.

其實這個很好理解. 如果子view自己指定了寬高.就用他的值就可以.如果子view是match_parent.那就使用父view提供的寬高. 如果子view是wrap_content,那就不能超過父view的值.

看下ViewGroup為子view繪制而提供的方法,可以看到.ViewGroup會減去padding和margin,來提供子view的寬高.

上步measure過程未完成後,整個view書的 測量寬高都得到了.也就是view.getMeasuredWidth()和getMeasuredHeight()

performLayout中會調用mView.layout. 這樣就把事件從ViewRootImpl傳遞到了view.而layout中又會調用onLayout.ViewGroup需要重寫onLayout為子view進行布局,遍歷調用子view的layout.因此就完成整個view樹的laylut過程.

豎向的實現, 豎向的就行把view從上到下一次排開

這里注意區分.measure過程是先得到子view的測量值,在設定父ViewGroup的值.而layout過程則是先傳入父view的左上右下值,來計運算元view的左上右下的位置值.這里應該具有普遍性.但不知道是否絕對.

performDraw 中的調用draw.又調用mView.draw.然後就進入view樹的繪制了.

view的draw 又會調用onDraw ,viewGroup又調用dispatchDraw()把draw分發到子view里 繪制的畫布就是canvas. 這是從surface.lockCanvas中獲得的一個區域.

而在ViewGroup.dispatchDraw中.重要的一點是getChildDrawingOrder 表示子view的繪制順序.默認是與ziview的添加順序一樣.我們也可以改變他.最後繪制的會顯示在最上邊,而這也影響view的事件傳遞順序.

view.draw. 就是一層一層的畫內容.先畫北京,在onDraw.在畫裝飾什麼的.

canvas.translate(100,300)通過平移坐標系.使之後的內容可以直接在新坐標系中繪制.

這就是ViewGroup在向子view傳遞canvas的時候.方便多了. 會之前先對其ziview的左上角.那麼子view就可以直接從自己坐標軸的(0,0)開始繪制, 繪制完成後ViewGroup在還原原有坐標系.

canvas.save. canvas.restore 用來保存還原坐標系.

view.invalidate.

當某個view發送變化需要重繪時,通過view.invalidate向上通知到ViewRootImpl.從這個view到ViewRootImpl的節點都標記為藏區域.dirty area. ViewRootimpl再次從上到下重繪時,只繪制這些臟區域.效率高.

本來安卓兼容使用鍵盤,也支持,觸摸.二者的輸入事件派發不一樣.使用鍵盤時會有個控制項處於獲得焦點狀態.處於觸摸模式則由用戶決定. 因此控制項分為兩類.任何情況下都能獲得焦點.如輸入文本框.只有在鍵盤操作時才能獲得焦點.如菜單,按鈕.

安卓里有觸摸模式.當發送任意觸摸時進入觸摸模式.當發送方向鍵和鍵盤或者執行View.requestRocusFromTouch時,退出觸摸模式.

獲取焦點. view.request.

先檢查是否能獲取焦點,

然後設置獲取簡單的標記,

向上傳遞到ViewRootimpl.保證只能有一個控制項獲取焦點.

通知焦點變化的監聽者.

更新view的drawable狀態,

requestChildFocus會把焦點事件層層上報取消原來有焦點的控制項.最後的效果就是從viewrootimpl中.到最終有焦點的view.構成一條 mFoucued 標識的鏈條.來個圖就明白了.每個view的mFocused總是指向他的直接下級.

獲取focus的傳遞是從底層view到頂層的ViewRootImpl.而取消focus測試從頂層的ViewRootimpl到底層原來那個獲得焦點的view.

而如果是ViewGroup請求獲取焦點,會根據FLAG_MASK_FOCUSABILITY特性來做不同方式,分別有先讓自己獲取焦點,或者安卓view的索引遞增或者遞減來匹配view.

ViewRootImpl 中的.WindowInputEventReceiver接受輸入事件.他會把事件包裝成一個QueuedInputEvent.然後追加到一個單鏈表的末尾.接著重頭到尾的處理輸入事件,並通過deliverInputEvent完成分發.這里會把單鏈表所有事件都處理完.

deliverInput中又會把觸摸事件執行到通過 ViewPreImeInputStage.processKeyEvent. 轉入mView.dispatchPointerEvent(event).這里又進入 dispatchTouchEvent

MotionEvent是觸摸事件的封裝.getAction可以拿到動作的類型和觸控點索引號.

getX(),getY().拿到動作的位置信息.通過getPointID拿到觸控點的id. 動作以down 開頭.跟多個move.最後是up.

,當事件返回true.表示事件被消費掉了.

❸ Android 新控制項之ConstraintLayout(約束布局)

ConstraintLayout (約束布局) 繼承於ViewGroup 允許開發者以靈活的方式定位和調整小部件的大小

ConstraintLayout 可讓開發者使用扁平視圖層次結構(無嵌套視圖組)創建復雜的大型布局。它與 RelativeLayout 相似,其中所有的視圖均根據同級視圖與父布局之間的關系進行布局,但其靈活性要高於 RelativeLayout ,並且更易於與 Android Studio 的布局編輯器配合使用。我理解為ConstraintLayout是一個更加靈活且減少嵌套的 RelativeLayout 的布局

ConstraintLayout作為支持庫提供,開發者可以在從 API 級別 9 (Gingerbread) 開始的 Android 系統上使用。

相信在面對一些復雜的UI頁面,咱們都是使用 RelativeLayout , LinearLayout 層層嵌套實現的.雖然能實現效果.但是層層嵌套層層解析載入View 無疑會耗費載入時間,耗費手機性能.這是時候ConstraintLayout(約束布局),就應運而生了,它出現的目的就是減少嵌套,優化層層嵌套狀況帶來的弊端

要在 ConstraintLayout 中定義某個視圖的位置, 您必須為該視圖添加至少一個水平約束條件和一個垂直約束條件 。每個約束條件均表示與其他視圖、父布局或隱形引導線之間連接或對齊方式。每個約束條件均定義了視圖在豎軸或者橫軸上的位置;因此每個視圖在每個軸上都必須至少有一個約束條件,但通常情況下會需要更多約束條件。

當您將視圖拖放到布局編輯器中時,即使沒有任何約束條件,它也會停留在您放置的位置。不過,這只是為了便於修改;當您在設備上運行布局時,如果視圖沒有任何約束條件,則會在位置 [0,0](左上角)處進行繪制。

在圖 1 中,布局在編輯器中看起來很完美,但視圖 C 上卻沒有垂直約束條件。在設備上繪制此布局時,雖然視圖 C 與視圖 A 的左右邊緣水平對齊,但由於沒有垂直約束條件,它會顯示在屏幕頂部

請注意,約束中不能有循環依賴。

相對定位是在 ConstraintLayout 中創建布局的基本構建塊之一。這些約束允許您相對於另一個小部件定位給定的小部件。您可以在水平和垂直軸上約束一個小部件:

如下圖,這告訴系統我們希望按鈕 B 的左側被約束到按鈕 A 的右側。這樣的位置約束意味著系統將嘗試讓兩側共享相同的位置。

這是可用約束的列表:

app:layout_constraintLeft(自身)_toLeftOf(相對於的控制項)="相對的控制項ID"

1.2 layout_constraintBaseline_toBaselineOf 基線對齊

如果設置了側邊距,它們將應用於相應的約束(如果存在)(圖 ),將邊距強制為目標端和源端之間的空間。通常的布局邊距屬性可用於此效果

2.1屬性:

請注意,邊距只能為正數或等於零,並且取Dimension.

2.2. 約束目標View.GONE的時候 的邊距

3.1 居中定位,就是把定位控制項的左邊對應目標的左邊 右邊對應目標的右邊,上邊對應目標的上邊

app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"

如上圖,Button的左邊位於父布局的左邊,右邊位於父布局的右邊就做到了水平居中的效果

3.2 偏移 : 有時候居中展示還需要做出偏移效果

可以以一定角度和距離約束一個小部件中心相對於另一個小部件中心。這允許您將一個小部件定位在一個圓圈上

ConstraintLayout對標記為 的小部件進行了特定處理View.GONE。

GONE像往常一樣,小部件不會被顯示並且不是布局本身的一部分(即,如果標記為 ,它們的實際尺寸不會改變GONE)。
但就布局計算而言,GONE小部件仍然是其中的一部分,但有一個重要區別:

注意:
使用的邊距將是 B 在連接到 A 時定義的邊距(參見圖 7 示例)。在某些情況下,這可能不是您想要的邊距(例如,A 到其容器的一側有 100dp 的邊距,B 到 A 的邊距只有 16dp,將 A 標記為已消失,B 到容器的邊距為 16dp)。出於這個原因,您可以指定在連接到被標記為已消失的小部件時使用的備用邊距值(請參閱 上面有關已消失的邊距屬性的部分 )

1.1 您可以為自身定義最小和最大尺寸ConstraintLayout

1.2 控制項尺寸約束
android:layout_width可以通過 3 種不同方式設置和 android:layout_height屬性 來指定控制項的尺寸:

重要提示:
MATCH_PARENT不建議用於ConstraintLayout. 可以通過MATCH_CONSTRAINT將相應的左/右或上/下約束設置為來定義類似的行為"parent"。

WRAP_CONTENT (添加在 1 . 1中):強制約束
如果維度設置為WRAP_CONTENT,則在 1.1 之前的版本中,它們將被視為文字維度——也就是說,約束不會限制結果維度。雖然通常這已經足夠(並且更快),但在某些情況下,您可能希望使用WRAP_CONTENT,但繼續強制執行約束以限制結果維度。在這種情況下,您可以添加相應的屬性之一:

MATCH_CONSTRAINT維度(添加在 1 . 1中)
當維度設置為MATCH_CONSTRAINT時,默認行為是讓結果大小佔用所有可用空間。有幾個額外的修飾符可用:

layout_constraintWidth_min和layout_constraintHeight_min: 將設置此維度的最小尺寸
layout_constraintWidth_max和layout_constraintHeight_max: 將設置此維度的最大尺寸
layout_constraintWidth_percent和layout_constraintHeight_percent: 將此維度的大小設置為父維度的百分比

比率: 寬高比
您還可以將小部件的一個維度定義為另一個維度的比率。為此,您需要將至少一個約束維度設置為0dp(即MATCH_CONSTRAINT),並將屬性設置layout_constraintDimensionRatio為給定的比率。例如:

除此之外,在設置寬高比的值的時候,還可以在前面加W或H,分別指定寬度或高度限制。 例如:
app:layout_constraintDimensionRatio="H,2:3"指的是 高:寬=2:3
app:layout_constraintDimensionRatio="W,2:3"指的是 寬:高=2:3

...

Guildline的主要屬性:

Constraint 約束布局為了解決嵌套布局的弊端,更快的載入頁面而出現,但是約束布局需要整體架構頁面要有明確的構建頁面的思維,故而學習以及思維模式要有的.所以個人感覺是簡單頁面還是用相對布局,線性布局就夠了,對於復雜布局約束布局是你優化頁面載入的不二之選.
*寫作不容易,且贊且珍惜!!!*

❹ Android基礎知識

一、activity

1.一個activity就是一個類,繼承activity;

2.需要復寫onCreate()方法;

3.每一個activity都需要在AndroidMainfest.xml清單上進行配置;

4.為activity添加必要的控制項。

二、布局

線性布局:LinearLayout

1.填滿父空間:fill_parent、match_parent

2.文本多大空間就有多大:warp_content

3.文字對齊方式:gravity

4.占屏幕的比例:layout_weight="1"  水平方向,則width=0,垂直方向,則height=0

5.一行顯示,空間不夠會省略:singleLine="ture"  false會換行

6.背景:background="#ffffff"

7.水平布局:orientation="horizontal"

垂直布局:orientation="vertivcal"

表格布局:TableLayout

1.內邊距:padding

2.外邊距:marginLeft\Start、Right\End、Top、Bottom

三、RelativeLayout相對布局

layout_above 將該控制項的底部置於給定ID控制項之上

layout_below 將該控制項的頂部置於給定ID控制項之下

layout_toLeftOf 將該控制項的右邊緣和給定ID控制項的左邊緣對齊

layout_toRightOf 將該控制項的左邊緣和給定ID控制項的右邊緣對齊

layout_alignBaseline 該控制項的baseline和給定ID的控制項的Baseline對齊

layout_alignBottom 該控制項的底部邊緣和給定ID的控制項的底部邊緣對齊

layout_alignLeft 該控制項的左邊緣和給定ID的控制項的左邊緣對齊

layout_alignRight 該控制項的右邊緣和給定ID的控制項的右邊緣對齊

layout_alignTop 該控制項的頂部邊緣和給定ID的控制項的頂部邊緣對齊

layout_alignparentBottom 如果該值為true,則該控制項的底部和父控制項的底部對齊layout_alignParentLeft 如果該值為true,則該控制項的左邊和父控制項的左邊對齊

layout_alignParentRight 如果該值為true,則該控制項的右邊和父控制項的右邊對齊

layout_alignParentTop 如果該值為true,則該控制項的上邊和父控制項的上邊對齊

layout_centerHorizontal 如果該值為true,則該控制項將被置於水平方向的中央

layout_centerInParent 如果該值為true,則該控制項將被置於父控制項水平和垂直方向的中央

layout_centerVertival 如果該值為true,則該控制項將被置於垂直方向的中央

四、一個Intent對象包含一組信息

1.Component name

2.Action

3.Data

4.Category

5.Extras

6.Flags

Intent intent = new Intent(this, SecondActivity.class);

startActivity(intent);  //startActivity方法

intent.putExtra("Key", "Value");  //鍵值對

intent = getIntent();

String value = intent.getStringExtra("Key");    //通過鍵提取數據

五、初級控制項:EditText、TextView、Button

1.獲取EditText的值

String value = EditText.getText().toString();

2.將值放到Intent對象中

Intent intent = new Intent();

intent.putExtra("one",value )

intent.setCalss(Activity.this, OtherActivity.class);

3.使用這個Intent對象來啟動Otheractivity

Activity.this.startActivity(intent);

4.將監聽器的對象綁定到按鈕對象上

button.setOnclickListener(new Listener());

5.得到Intent對象當中的值

Intent intent = getIntent();

String value1 = intent.getStringExtra("one");

int value2 = Integer.parseInt(value);

六、其他初級控制項使用

①ImageView

②RadioGroup和RadioButton

setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener())

③Checkbox

setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener())

④Menu

1.當客戶點擊MENU按鈕的時候,調用onCreateOptionMenu()方法

public boolean onCreateOptionMenu(Menu menu){

menu.add(0,1,1,R.string.id);

}

2.當客戶點擊MENU內部的具體某一個選項時,調用onOptionItemSelected()方法

public boolean onOptionItemSelected(MenuItem item){

if(item.getItemId() == 1){

finish();

}

return super.onOptionItemSelected(item);

}

七、Activity的生命周期

1.第一次創建時調用

protected void onCreat(Bundle saveInstanceState);

2.顯示出來時調用

protected void onStrat();

3.獲得用戶焦點時調用(可操作)

protected void onResume();

4.點擊彈出第二個Activity時調用

protected void onPause();

5.當第一個Activity不可見時調用

protected void onStop();

6.當返回第一個Activity時調用,代替OnCreate,因為沒被銷毀

protected void onRestart();

7.當返回第一個Activity時調用(先執行onStop,在執行,因為第二個Activity被銷毀,不能返回獲取,只能通過onCreat,onStart,onResume再創建)

protected void onDestory();

八、Task

1.Task是存放Activity的Stack棧。當點擊啟動第二個Activiry時,第一個Activtiy會被壓入Stack棧當中,第二個Activity會位於棧的頂部;當返回第一個Activtiy時,第二個Activity會被彈出Stack,第一個Activity會位於棧的頂部,以此類推。

注釋:當調用finish()時,當前的Activity會被Destory掉,棧中的Activity會消失。

2.當Activity都從Stack退出後,則就不存在Task。

九、高級控制項

①進度條ProgressBar

水平進度條style="?android:attr/progressBarStyleHorizontal"

圓圈進度條style="?android:attr/progressBarStyle"

用戶可視的visibility="gone"

②列表ListView

十、其他控制項

A.下拉菜單Spinner

1.創建一個ArrayAdapter:

ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(

this, //指上下文對象

R.array.plant_array, //引用了在文件中定義的String數組

android.R.layout.simple_spinner_item);//用來指定Spinner的樣式,可替換自定義

adapter.setDropDownViewResource(

android.R.layout.simple_spinner_dropdown_item);//設置Spinner當中每個條目的樣式

2.得到Spinner對象,並設置數據:

spinner=(spinner)findViewById(R.id.spinnerId);

spinner.setAdapter(adapter);

spinner.setPrompt("測試");//標題

3.創建監聽器

class SpinnerOnSelectListener implements OnItemSelectedListener{

@override

public void onItemSelected(

AdapterView<?> adapterView,//整個列表對象

View view,//被選中的具體條目對象

int position,//位置

long id){ //id

String selected = adapterView.getItemAtPosition(position).toString();

}

@override

public void onNothingSelected(AdapterView<?> adapterView){

S.o.p("nothingSelected");

}

}

4.綁定監聽器

spinner.setOnItemSelectedListener(new SpinnerOnSelectListener());

註:第二種動態設計

1.創建ArrayList對象

List<String> list = new ArrayList<String>();

list.add("test1");

2. 調用方法

ArrayAdapter adapter = new ArrayAdapter(

this, //指上下文對象

R.layout.item, //引用了指定了下拉菜單的自定義布局文件

R.id.textViewId,//id

list);//數據

3.得到Spinner對象,並設置對象

spinner.setAdapter(adapter);

spinner.setPrompt("測試");//標題

3.創建監聽器

class SpinnerOnSelectListener implements OnItemSelectedListener{

@override

public void onItemSelected(

AdapterView<?> adapterView,//整個列表對象

View view,//被選中的具體條目對象

int position,//位置

long id){ //id

String selected = adapterView.getItemAtPosition(position).toString();

}

@override

public void onNothingSelected(AdapterView<?> adapterView){

S.o.p("nothingSelected");

}

}

4.綁定監聽器

spinner.setOnItemSelectedListener(new SpinnerOnSelectListener());

B.DatePicker和DatePickerDialog

1.聲明一個監聽器,使用匿名內部類

DatePickerDialog.OnDateSetListener onDateSetListener

= new DatePivkerDialog.OnDateSetListener(){

public void onDateSet(

DatePicker view,

int year,

int monthOfYear,

int dayOfMonth){

S.o.p(year+"-"+motnOfYear+"-"+dayOfMonth)

}

}

2.復寫onCreateDialog(int id)方法:

@override

protected Dialog onCreateDialog(int id){

switch(id){

case DATE_PICKER_ID:

return new DatePickerDialog(this,onDateSetListener,2019,11,25);

}

return null;

}

3.使用時調用showDialog()方法

showDialog(DATE_PICKER_ID);

C.AutoCompleteTextView

B.Widget

C.Animatin

十一、實現ContentProvider過程

1.定義一個CONTENT_URI常量

2.定義一個類,繼承ContentProvider

3.實現query、insert、update、delete、getType和onCreate方法

4.在AndroidManifest.xml當中進行聲明

❺ Android中的自定義控制項

        組合控制項:即由原生控制項拼裝而成,不需要自己實現或者繪制具體的頁面內容和效果,常用於標題欄TitlleView

        eg:

        繼承控制項的意思就是,我們並不需要自己重頭去實現一個控制項,只需要去繼承一個現有的控制項,然後在這個控制項上增加一些新的功能,就可以形成一個自定義的控制項了。這種自定義控制項的特點就是不僅能夠按照我們的需求加入相應的功能,還可以保留原生控制項的所有功能。

熟悉view的繪制原理

1.measure用來測量View的寬和高。 

2.layout用來確定View在父容器中放置的位置。 

3.draw用來將view繪制在屏幕上

❻ Android自定義控制項總結

每個view的坐標系原點為左上角那個點,水平方向為x軸,右正左負,豎直方向為y軸,下正上負。

canvas.drawColor //繪制區域塗上顏色(設置底色/蒙層)

canvas.drawCircle(float centerX(圓心X坐標),float centerY(圓心Y坐標),float radius(圓的半徑,單位像素),Paint paint)

canvas.drawBitmap

canvas.drawRect(float left,float top,float right,float bottom,Paint paint) //畫矩形

canvas.drawRect(RecF rect,Paint paint)

canvas.drawRect(Rect rect,Paint paint)

canvas.drawPoint(float x(點X軸坐標),float y(點Y軸坐標),Paint paint)//畫點

點的大小 ->paint.setStrokeWidth(width)

點的形狀 ->paint.setStrokeCap(cap)

ROUND(圓形),BUTT(平頭),SQUARE(方頭)

canvas.drawPoints()//批量畫點

canvas.drawOval(float left(左邊界點),float top(上邊界點),float right(右邊界點),float bottom(下邊界點),Paint paint) //畫橢圓

canvas.drawLine(float startX(起點X軸坐標),float startY(起點Y軸坐標),float stopX(終點X軸坐標),float stopY(終點X軸坐標),Paint paint) (setStyle對直線沒有影響)

canvas.drawLines(批量畫線)

canvas.drawRoundRect(float left,float top,float right,float bottom,float rx(圓角的橫向半徑),float ry(圓角的縱向坐標),Paint paint)//畫圓角矩形

canvas.drawRoundRect(RectF rect,float rx, float ry,Paint paint)

canvas.drawArc(float left, float top, float right, float bottom, float startAngle(起始角度,順時針為正,逆時針為負), float sweepAngle(弧形劃過角度), boolean useCenter(是否連接到圓心), Paint paint) //繪制弧形或扇形 根據弧形所在橢圓進行繪制

canvas.drawPath() //通過描述路徑的方式來繪制圖形

path.addXxx() —添加子圖形

path.addCircle(x,y,radius,dir(路徑方向:順時針/逆時針))

path.xxxTo —畫線

path.lineTo()

path.rLineTo()

path.close() —封閉當前圖形

path.setFillType(Path.FillType ft) //設置填充模式

canvas.drawBitmap(Bitmap bitmap,float left,float top,Paint paint);//畫bitmap

canvas.drawBitmap(Bitmap bitmap,Rect src,RectF dst,Paint paint)

canvas.drawBitmap(Bitmap bitmap,Rect src,Rect dst,Paint paint)

canvas.drawBitmap(Bitmap bitmap,Matrix matrix,Paint paint)

canvas.drawText(String text,float x(起點x坐標),float y(起點y坐標),Paint paint) //繪制文字

Paint.setStyle //設置繪制模式

FILL 填充模式(默認)

STROKE 畫線模式

FILL_AND_STROKE 既畫線又填充

Paint.setStrokeWidth //設置線條寬度 (僅在style:Stroke、FILL_AND_STROLE下有效)

Paint.setTextSize //設置文字大小

Paint.setAntiAlias //設置抗鋸齒開關

Paint.setTextSize(float textSize)//設置文字大小

Paint.setStrokeJoin(Paint.Join join) //設置拐角的形狀

MITER//尖角(默認)

BEVEL//平角

ROUND//圓角

Paint.setStokeMiter(float miter)//設置MITER型拐角的延長線的最大值

設置顏色

直接設置顏色

Paint.setColor(int color)

Paint.setARGB(int a,int r,int g,int b)

Paint.setShader(Shader shader) //設置shader

LinearGradient 線性漸變

RadialGradient 輻射漸變

SweepGradient 掃描漸變

BitmapShader 用bitmap的像素來作為圖形或文字的填充

ComposeShader 混合著色器,多個shader混合使用

Paint.setColorFilter(ColorFilter colorFilter) //設置顏色過濾

Paint.setXfermode(Xfermode xfermode) //以要繪制的內容為源圖像,以View中已有內容作為目標圖像,選取一個PorterDuff.Mode作為繪制內容的顏色處理方案。

色彩優化

Paint.setDither(boolean dither) //設置抖動來優化色彩深度降低時的繪制效果

Paint.setFilterBitmap(boolean filter) //設置雙線性過濾優化Bitmap放大繪制的效果

可以理解為 由馬賽克變成模糊狀態

Paint.setPathEffect(PathEffect effect)//使用PathEffect設置形狀的輪廓效果

CornerPathEffect//把所有的拐角變成圓角

DiscretePathEffect//把線條進行隨機的偏離

DashPathEffect//使用虛線

PathDashPathEffect//使用一個Path來繪制虛線

SumPathEffect//組合效果

ComposePathEffect//組合效果,組合有先後順序

Paint.setShadowLayer(float radius,float dx,float dy,int shadowColor)//添加陰影

Paint.setMaskFilter(MaskFilter maskfilter)//在繪制層上方的附加效果

BlurMaskFilter //模糊效果

new BlurMaskFilter(float radius(模糊范圍),BlurMaskFilter.Blur style(模糊類型))

EmbossMaskFilter//浮雕效果

new EmbossMaskFilter(float[] direction(光源的方向),float ambient(環境光強度),float specular(炫光系數),float blurRadius(光線范圍))

獲取繪制的Path

getFillPath(Path src,Path dst)//實際path

getTextPath(Stirng text,int start,int end,float x,float y,Path)/getTextPath(char[] text,int index,int count,float x,float y,Path path)//文字的path

drawTextOnPath()//沿一條Path來繪制文字

StaticLayout //繪制文字,支持換行

paint.setFakeBoldText(booleab fakeBoldText)//是否使用偽粗體

paint.setStrikeThruText()//是否加刪除線

paint.setUnderLineText(boolean underlineText)//是否加下劃線

paint.setTextSkewX(float skewX)//設置文字橫向錯切角度

paint.setTextScaleX(float scaleX)//設置文字橫向放縮

paint.setLetterSpacing(float letterSpacing)//設置字元間距,默認為0

paint.setTextAlign(Paint.Align align)//LEFT、CENTER、RIGHT默認為LEFT

paint.setTextLocale(Locale locale)/paint.setTextLocales(LocaleList locales) //設置繪制所用的地域

paint.setHinting(int mode)//是否啟用字體微調

測量文字尺寸類:

paint.getFontSpacing();//獲取推薦的行距

paint.getFontMetrics();//獲取point的FontMetrics

baseline:基準線

ascent/descent:普通字元的頂部和底部范圍

top/bottom:限制字型的頂部和底部

leading:行的額外間距,即上一行字的bottm與下一行字的top距離

paint.getTextBounds(String text(測量的文字),int start(文字的起始位置),int end(文字的結束位置),Rect bounds(文字顯示範圍的對象))//獲取文字的顯示範圍

paint.measureText(String text)//測量文字佔用的寬度

measureText()>getTextBounds()

paint.getTextWidths(String text,float[] widths)//獲取字元串中每個字元的寬度,並把結果填入參數widths

paint.breakText(String text((要測量的文字),boolean measureForwards(測量的方向),float maxWidth(寬度上限(超出上限會截斷文字)),float[] measuredWidth(用於接受數據))//測量完成後會把文字寬度賦給measureWidth[0]

paint.getRunAdvance(CharSequence text,int start(文字的起始坐標),int end(文字的結束坐標),int contextStart(上下文的起始坐標),int ContextEnd(上下文的結束坐標),boolean isRtl(文字的方向),int offset(字數的偏移))//計算某個字元處游標的x坐標

paint.getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)//計算出文字中最接近這個位置的字元偏移量

paint.hasGlyph(String s)//檢查指定的字元串是否是一個單獨的字型

canvas.clipRect()//范圍裁剪

canvas.clipPath()//根據范圍裁剪

canvas.translate(float dx,float dy)//位移

canvas.rotate(float degrees,float px,float py)//旋轉

canvas.scale(float sx(橫向縮放倍數),float sy(縱向縮放倍數),float px,float py)//縮放

canvas.skew(float sx(x軸的錯切系數),float sy(y軸的錯切系數))//錯切

canvas.setMatrix(matrix)//用Matrix直接替換Canvas當前的變換矩陣

canvas.concat(matrix)//用Canvas當前的變換矩陣和Matrix相乘

Camera.rotate*()//三維旋轉

1、super.draw()//總調度方法

2、super.onDraw()

3、dispatchDraw()//繪制子View的方法

繪制順序:

draw()總調度方法,view的繪制過程都發生在draw()方法里

1、背景(drawBackground()不能重寫)-------android:background:/View.setBackgroundXxx()

2、主體(onDraw())

3、子View(dispatchDraw())

4、滑動邊緣漸變和滑動條(onDrawForeground())-------android:scrollbarXxx/View.setXXXScrollBarXXX()

5、前景(onDrawForeground())-------android:foreground/View.setForeground()

view.animate().translationX()//x軸偏移

1、如果是自定義控制項,需要添加setter、getter方法

2、ObjectAnimator.ofXXX()創建ObjectAnimator對象

3、用start()方法執行動畫

setDuration(int ration)//設置動畫時長

setInterpolator(Interpolator interpolator)//設置插值器

ViewPropertyAnimator.setListener()/ObjectAnimator.addListener()

ViewPropertyAnimator.setUpdateListener()/ObjectAnimator.addUpdateListener()

ObjectAnimator.addPauseListener()

ViewPropertyAnimator.withStartAction/EndAction()

ArgbEvaluator//顏色漸變動畫

PropertyValuesHolder//同一個動畫中改變多個屬性

PropertyValuesHolders.ofKeyframe()//把同一個屬性拆分

AnimatorSet//多個動畫配合執行

targetSdkVersion>=14,硬體加速默認開啟

view.setLayerType()

LAYER_TYPE_SOFTWARE:使用軟體來繪制View Layer,繪制到Bitmap,並順便關閉硬體加速

LAYER_TYPE_HARDWARE:使用GPU來繪制View Layer,繪制到OpenGL texture(如果硬體加速關閉,那麼行為和LAYER_TYPE_SOFTWARE一致)

LAYER_TYPE_NONE:關閉View Layer

View Layer可以加速無invalidate()(例如動畫)時的刷新效率,但對於需要調用invalidate()的刷新無法加速

硬體加速並不支持所有的繪制操作

1、測量(measure)

View:View在onMeasuer中會計算自己的尺寸然後保存

ViewGroup:ViewGroup在onMeasure中會調用所有子View的measure讓它們進行自我測量,並根據子View

計算出的期望尺寸來計算他們的事跡尺寸和位置然後保存。

2、布局(layout)

View:無子View所以onLayout不做任何處理

ViewGroup:ViewGroup在onLayout中會調用自己所有子View的layout方法,把他們的尺寸、位置傳給他們, 讓他們完成自我布局。

MeasureSpec = mode + size :父類傳遞過來給當前View的一個建議值

MeasureSpec.getMode(int spec)//獲取模式

MeasureSpec.getSize(int spec)//獲取數值

限制分類:

UNSPECIFIED(不限制)

AT_MOST(限制上限)->wrap_content

EXACTLY(限制固定值)->match_parent/具體值

1、重寫onMeasure來修改已有的View尺寸

(1)、重寫onMeasure方法,調用super.onMeasure觸發原有的自我測量。

(2)、在super.onMeasure下用getMeasureWidth與getMeasureHeigh獲取之前測量的結果,使用自己的演算法計算新結果。

(3)、調用setMeasureDimension保存新結果。

2、重寫onMeasure來全新定製自定義View的尺寸

與1區別,保證計算的同時,保證結果滿足父View給出的尺寸限制

(1)重寫onMeasure,計算出View的尺寸

(2)使用resolve讓子View的計算結果符合父View的限制,也可不使用該方法自己定義

3、重寫onMeasure和onLayout來全新定製自定義ViewGroup的內部布局

兩個注意點:

子控制項間的margin值

1、重寫generateLayoutParams()和generateDefaultLayoutParams()

2、獲取margin值 MarginLayoutParams lp = (MarginLayoutParams )child.getLayoutParams()

子控制項間的padding值

1、測量後直接getPaddingLeft、getPaddingTop、getPaddingRight、getPaddingBottom

重寫onMeasure來計算內部布局

(1)調用每個子View的measure來計運算元View的尺寸

結合layout_xxx和自己可用空間

(2)計運算元View的位置並保存子View的尺寸和位置

(3)計算自己的尺寸並用setMeasureDimension保存

重寫onLayout來擺放子View

(1)調用每個子View的layout,讓他們保存自己的位置和尺寸

view工作原理

觸摸事件

1、ACTION_DOWN:手指剛接觸屏幕,按下去的那一瞬間

2、ACTION_MOVE:手指在屏幕上移動

3、ACTION_UP:手指從屏幕上松開的瞬間

事件序列:從ACTION_DOWN -> ACTION_UP

ViewGroup:
DispatchTouchEvent
• return true:表示該View內部消化掉了所有事件
• return false:表示事件在本層不再繼續進行分發,並交由上層控制項的onTouchEvent方法進行消費
• return super.dispatchTouchEvent(ev):默認事件將分發給本層的事件攔截onInterceptTouchEvent方法 進行處理
OnInterceptTouchEvent
• return true:表示將事件進行攔截,並將攔截到的事件交由本層控制項的onTouchEvent進行處理
• return false:表示不對事件進行攔截,事件得以成功分發到子View
• return super.onInterceptTouchEvent(ev):默認表示不攔截該事件,並將事件傳遞給下一層View的 dispatchTouchEvent
OnTouchEvent 默認false
• return true:表示onTouchEvent處理完事件後消費了此次事件
• return fasle:表示不響應事件,那麼該事件將會不斷向上層View的onTouchEvent方法傳遞,直到某個View的 onTouchEvent方法返回true
• return super.dispatchTouchEvent(ev):表示不響應事件,結果與return false一樣
子View不存在分發:
• DispatchTouchEvent 事件分發
• OnTouchEvent 默認true

如下圖為事件分發流程圖:

---------------------- 以上總結部分源自Hencoder教程 ------------------------------

閱讀全文

與android基礎控制項相關的資料

熱點內容
linux用戶密碼忘記 瀏覽:240
gb壓縮天然氣 瀏覽:633
圖片拼接不壓縮app 瀏覽:668
我的世界如何編程 瀏覽:84
vue反編譯代碼有問題 瀏覽:948
linuxshell字元串連接字元串 瀏覽:51
androidviewpager刷新 瀏覽:438
python編程計算平均分 瀏覽:678
加密數字貨幣市值查詢 瀏覽:692
時尚商圈app怎麼樣 瀏覽:584
stacklesspython教程 瀏覽:138
用命令行禁用135埠 瀏覽:212
linux防火牆編程 瀏覽:627
pdf閱讀器刪除 瀏覽:979
考研人如何緩解壓力 瀏覽:822
買電暖壺哪個app便宜 瀏覽:505
洛克王國忘記伺服器了怎麼辦 瀏覽:782
為什麼cf登錄伺服器沒反應 瀏覽:695
伺服器如何獲取文件列表 瀏覽:674
creo五軸編程光碟 瀏覽:15