android獲取view
❶ android 怎麼在oncreate中獲得view的坐標
這個問題大家肯定遇到過不止一次,其實很簡單,解決它也很容易,但是咱們追求的畢竟不是解決它,而是找到幾種方法去解決,並且這么解決的原理是什麼。
這里列出4種解決方案:
Activity/View#onWindowFocusChanged
這個函數的含義是:view已經初始化完畢了,寬/高已經准備好了,這個時候去獲取寬高是可以成功獲取的。但是需要注意的是onWindowFocusChanged函數會被調用多次,當Activity的窗口得到焦點和失去焦點時均會被調用一次,如果頻繁地進行onResume和onPause,那麼onWindowFocusChanged也會被頻繁地調用。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}123456
view.post(runnable)
通過post可以將一個runnable投遞到消息隊列的尾部,然後等待UI線程Looper調用此runnable的時候,view也已經初始化好了。
v_view1.post(new Runnable() {
@Override
public void run() {
L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});1234567
ViewTreeObserver
使用ViewTreeObserver的眾多回調可以完成這個功能,比如使用OnGlobalLayoutListener這個介面,當view樹的狀態發生改變或者view樹內部的view的可見性發生改變時,onGlobalLayout方法將被回調,因此這是獲取view的寬高一個很好的時機。需要注意的是,伴隨著view樹的狀態改變等,onGlobalLayout會被調用多次。
v_view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});1234567
再來詳細介紹一下ViewTreeObserver這個類,這個類是用來注冊當view tree全局狀態改變時的回調監聽器,這些全局事件包括很多,比如整個view tree視圖的布局,視圖繪制的開始,點擊事件的改變等等。還有千萬不要在應用程序中實例化ViewTreeObserver對象,因為該對象僅是由視圖提供的。
ViewTreeObserver類提供了幾個相關函數用來添加view tree的相關監聽器:
public void addOnDrawListener (ViewTreeObserver.OnDrawListener listener)
該函數為api 16版本中添加,作用是注冊在該view tree將要繪制時候的回調監聽器,注意該函數和相關的remove函數不能在監聽器回調的onDraw()中調用。
public void (ViewTreeObserver.OnGlobalFocusChangeListener listener)
該函數用來注冊在view tree焦點改變時候的回調監聽器。
public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)
該函數用來注冊在該view tree中view的全局布局屬性改變或者可見性改變時候的回調監聽器。
public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)
該函數用來注冊當view tree將要被繪制時候(view 的 onDraw 函數之前)的回調監聽器。
public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)
該函數用來注冊當view tree滑動時候的回調監聽器,比如用來監聽ScrollView的滑動狀態。
public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)
該函數用來注冊當view tree的touch mode改變時的回調監聽器,回調函數onTouchModeChanged (boolean isInTouchMode)中的isInTouchMode為該view tree的touch mode狀態。
public void addOnWindowAttachListener (ViewTreeObserver.OnWindowAttachListener listener)
api 18添加,該函數用來注冊當view tree被附加到一個window上時的回調監聽器。
public void (ViewTreeObserver.OnWindowFocusChangeListener listener)
api 18添加,該函數用來注冊當window中該view tree焦點改變時候的回調監聽器。
而且對應每一個add方法都會有一個remove方法用來刪除相應監聽器。
view.measure(int widthMeasureSpec, int heightMeasureSpec)
通過手動對view進行measure來得到view的寬/高,這種情況比較復雜,這里要分情況處理,根據view的layoutparams來分:
match_parent
直接放棄,無法measure出具體的寬/高。原因很簡單,根據view的measure過程,構造此種MeasureSpec需要知道parentSize,即父容器的剩餘空間,而這個時候我們無法知道parentSize的大小,所以理論上不可能測量處view的大小。
wrap_content
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);123
注意到(1<<30)-1,我們知道MeasureSpec的前2位為mode,後面30位為size,所以說我們使用最大size值去匹配該最大化模式,讓view自己去計算需要的大小。
具體的數值(dp/px)
這種模式下,只需要使用具體數值去measure即可,比如寬/高都是100px:
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);123
源碼和結果
demo代碼如下
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/v_view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>
<View
android:id="@+id/v_view2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@mipmap/ic_launcher"/>
</LinearLayout>12345678910111213141516171819
activity:
public class MainActivity extends BaseActivity{
private View v_view1;
private View v_view2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
v_view1 = findViewById(R.id.v_view1);
v_view2 = findViewById(R.id.v_view2);
L.i("normal: v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
v_view1.post(new Runnable() {
@Override
public void run() {
L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});
v_view1.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);
L.i("measure : v_view1.getMeasuredWidth():" + v_view1.getMeasuredWidth()
+ " v_view1.getMeasuredHeight():" + v_view1.getMeasuredHeight());
v_view2.measure(widthMeasureSpec, heightMeasureSpec);
L.i("measure : v_view2.getMeasuredWidth():" + v_view2.getMeasuredWidth()
+ " v_view2.getMeasuredHeight():" + v_view2.getMeasuredHeight());
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
}
041424344454647484950
log日誌:
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:28): normal: v_view1.getWidth():0 v_view1.getHeight():0
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:50): measure : v_view1.getMeasuredWidth():144 v_view1.getMeasuredHeight():144
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:53): measure : v_view2.getMeasuredWidth():16777215 v_view2.getMeasuredHeight():16777215
I/[PID:2659]: [TID:1] 2.onGlobalLayout(line:42): ViewTreeObserver : v_view1.getWidth():144 v_view1.getHeight():144
I/[PID:2659]: [TID:1] 1.run(line:34): post(Runnable) : v_view1.getWidth():144 v_view1.getHeight():144
I/[PID:2659]: [TID:1] MainActivity.onWindowFocusChanged(line:61): onWindowFocusChanged : v_view1.getWidth():144 v_view1.getHeight():144123456
界面:
小的為view_1,大的為view_2,從log日誌中就發現有問題了:view_2視圖使用measure之後計算出來的寬高是錯誤的,所以View類的視圖使用measure計算出來的結果是不準確的,這點需要特別特別注意。
❷ Android View何時能拿到寬高的值
一個View或ViewGroup中什麼什麼時候能拿到寬高的值?
width 表示 View 在屏幕上可顯示的區域大小;
measuredWidth 表示 View 的實際大小,包括超出屏幕范圍外的尺寸;
甚至有這樣的公式總結到:
getMeasuredWidth() = visible width + invisible width
getMeasuredWidth() 在執行setMeasuredDimension(一般在onMeasure方法中執行)後才有值;
getWidth()在onLayout方法執行後才有值。
Constructor : 構造方法,View初始化的時候調用,在這里是無法獲取其子控制項的引用的.更加無法獲取寬高了.
onFinishInflate : 當布局初始化完畢後回調,在這里可以獲取所有直接子View的引用,但是無法獲取寬高.
onMeasure : 當測量控制項寬高時回調,當調用了requestLayout()也會回調onMeasure.在這里一定可以通過getMeasuredHeight()和getMeasuredWidth()來獲取控制項的高和寬,但不一定可以通過getHeight()和getWidth()來獲取控制項寬高,因為getHeight()和getWidth()必須要等onLayout方法回調之後才能確定.
onSizeChanged : 當控制項的寬高發生變化時回調,和onMeasure一樣,一定可以通過getMeasuredHeight()和getMeasuredWidth()來獲取控制項的高和寬,因為它是在onMeasure方法執行之後和onLayout方法之前回調的.
onLayout : 當確定控制項的位置時回調,當調用了requestLayout()也會回調onLayout.在這里一定可以通過getHeight()和getWidth()獲取控制項的寬高,同時由於onMeasure方法比onLayout方法先執行,所以在這里也可以通過getMeasuredHeight()和getMeasuredWidth()來獲取控制項的高和寬.
addOnGlobalLayoutListener : 當View的位置確定完後會回調改監聽方法,它是緊接著onLayout方法執行而執行的,只要onLayout方法調用了,那麼addOnGlobalLayoutListener的監聽器就會監聽到.在這里getMeasuredHeight()和getMeasuredWidth()和getHeight()和getWidth()都可以獲取到寬高.
onWindowFocusChanged : 當View的焦點發送改變時回調,在這里getMeasuredHeight()和getMeasuredWidth()和getHeight()和getWidth()都可以獲取到寬高.Activity也可以通過重寫該方法來判斷當前的焦點是否發送改變了;需要注意的是這里View獲取焦點和失去焦點都會回調.
(部分內容參考於網路,如有不妥,請聯系刪除~)
❸ android 是怎麼通過在XML中配置的控制項屬性得到具體的view對象
我可能表達的不是很清楚,那就拿個具體的例子來說明吧
比如說,在Activity中我們需要用到一個ProgressBar控制項,我們一般先在layout下的main.xml中進行配置
Xml代碼
<ProgressBar
android:id="@+id/pb1"
android:layout_width="fill_parent"
android:layout_height="20dip"
<span style="color: #ff0000;"> android:indeterminateOnly="false"</span>
android:layout_gravity="center_vertical"
android:progressDrawable="@android:drawable/progress_horizontal"
android:indeterminateDrawable="@android:drawable/progress_indeterminate_horizontal"
android:minHeight="20dip"
android:maxHeight="20dip"
/>
我們看 android:indeterminateOnly="false" 這行代碼的
一般我們如果要將在代碼中創建一個ProgressBar,但是不通過配置文件得到。
代碼如下:
java代碼
ProgressBar mProgressBar=new ProgressBar(context);
<span style="color: #ff0000;">mProgressBar.setIndeterminate(false); </span>
mProgressBar.setProgressDrawable(getResources().getDrawable
(android.R.drawable.progress_horizontal));
mProgressBar.setIndeterminateDrawable(getResources().getDrawable
(android.R.drawable.progress_indeterminate_horizontal));
本來我們是希望創建一個普通的能顯示進度的橫條ProgressBar
但是我們發現progressBar中的進度無法更新。
我們來看進度更新的源代碼setProgress():
Java代碼
@android.view.RemotableViewMethod
synchronized void setProgress(int progress, boolean fromUser) {
<span style="color: #ff0000;"> if (mIndeterminate) {
return;
}</span>
if (progress < 0) {
progress = 0;
}
if (progress > mMax) {
progress = mMax;
}
if (progress != mProgress) {
mProgress = progress;
refreshProgress(R.id.progress, mProgress, fromUser);
}
}
關鍵是: if (mIndeterminate) { return; }
原來mIndeterminate 的值為true的話,函數直接返回了,也就是我們不能設置progressBar的進度(mProgress)
現在我們的任務是將mIndeterminate 屬性設置為false
但是我們發現 mProgressBar.setIndeterminate(false); 這行代碼並沒有設置mIndeterminate 屬性為false
我們看ProgressBar中的源代碼:
Java代碼
@android.view.RemotableViewMethod
public synchronized void setIndeterminate(boolean indeterminate) {
<span style="color: #ff0000;"> if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {</span>
mIndeterminate = indeterminate;
if (indeterminate) {
// swap between indeterminate and regular backgrounds
mCurrentDrawable = mIndeterminateDrawable;
startAnimation();
} else {
mCurrentDrawable = mProgressDrawable;
stopAnimation();
}
}
}
看這行代碼: if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
我們發現當mOnlyIndeterminate 和mIndeterminate 之前都為true時,我們並不能將mIndeterminate 從true改變為false
google後,有人通過反射機制將ProgressBar中的mOnlyIndeterminate 設置為false(具體請看:關於使用代碼來創建ProgressBar )
我現在我就在想 既然 ProgressBar中的mOnlyIndeterminate 和mIndeterminate屬性都是private,而且都不能通過get和set方法來對其進行操作,那麼 android 通過在XML中配置的控制項屬性,是怎麼被轉換成真正的java類呢?
求大神解惑!
問題補充
over140 寫道
你應該看一下他源碼里關於這個的構造函數部分的代碼,注意父類裡面可能也有代碼,他並不是轉換成java類,只是讀取從XML屬性讀取想要的參數。
你指的是ProgressBar類中的構造函數嗎? 我之前就仔細看了這些東西
ProgressBar中構造函數源碼:
Java代碼
/**
* Create a new progress bar with range 0...100 and initial progress of 0.
* @param context the application environment
*/
public ProgressBar(Context context) {
this(context, null);
}
public ProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.progressBarStyle);
}
public ProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mUiThreadId = Thread.currentThread().getId();
initProgressBar();
TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyle, 0);
mNoInvalidate = true;
Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
if (drawable != null) {
drawable = tileify(drawable, false);
setProgressDrawable(drawable);
}
mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
final int resID = a.getResourceId(
com.android.internal.R.styleable.ProgressBar_interpolator,
android.R.anim.linear_interpolator); // default to linear interpolator
if (resID > 0) {
setInterpolator(context, resID);
}
setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
setSecondaryProgress(
a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable);
if (drawable != null) {
drawable = tileifyIndeterminate(drawable);
setIndeterminateDrawable(drawable);
}
mOnlyIndeterminate = a.getBoolean(
R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
mNoInvalidate = false;
setIndeterminate(mOnlyIndeterminate || a.getBoolean(
R.styleable.ProgressBar_indeterminate, mIndeterminate));
a.recycle();
}
看以上代碼,發現ProgressBar類並沒有使用其父類的構造方法, 它的三個構造方法最終都需要進入到第三個構造方法內,
再看這一句
Java代碼
initProgressBar();
其中具體源碼如下:
Java代碼
private void initProgressBar() {
mMax = 100;
mProgress = 0;
mSecondaryProgress = 0;
mIndeterminate = false;
mOnlyIndeterminate = false;
mDuration = 4000;
mBehavior = AlphaAnimation.RESTART;
mMinWidth = 24;
mMaxWidth = 48;
mMinHeight = 24;
mMaxHeight = 48;
}
看 這兩句:
Java代碼
mIndeterminate = false;
mOnlyIndeterminate = false;
奇怪 在這個初始化ProgressBar的過程中,明明將mIndeterminate和mOnlyIndeterminate屬性設置為false
但是我們初始化後的進度條,顯示後同樣是不會進度的(圖如下)
也就是說 mIndeterminate屬性依然是true(具體請看ProgressBar中的setProgress()方法)
繼續看構造方法中的其他內容時,發現關鍵所在:
Java代碼
mOnlyIndeterminate = a.getBoolean(
R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
mNoInvalidate = false;
setIndeterminate(mOnlyIndeterminate || a.getBoolean(
R.styleable.ProgressBar_indeterminate, mIndeterminate));
這兩句將mOnlydeterminate和mIndeterminate屬性都設置為true,
看到這里,發現這兩句都用到 a 這個對象,a 具體是什麼呢?
Java代碼
TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyle, 0);
那我們能不能通過改變傳入 構造函數 的參數 來使 a 發生相應的變化,進而使mOnlyIndeterminate和mIndeterminate屬性設置為 true 呢?
再往下深入,發現看的我那是一頭霧水,完全找不到方向,所以只得放棄這個猜想。
到最後,我發現 如果要通過正常的java訪問機制(當然排除使用反射機制),來操作mOnlyIndeterminate和mIndeterminate屬性是不可能的,
你說: 他並不是轉換成java類,只是讀取從XML屬性讀取想要的參數。
我們一般是通過findViewById()來根據layout中的XML文件設置的控制項屬性得到 具體的View控制項對象(關於轉換成java類,我錯了,其實我想說的是怎麼得到的這個對象),
這個過程是不是也是通過反射機制來完成的?假如是的話,能給我提供些具體過程的資料呢?
❹ android 如何獲取一個界面最頂層的view並處理單擊事件的分發機制
android事件分發機制 就是一個觸摸事件發生了,從一個窗口傳遞到一個視圖,再傳遞到另外一個視圖,最後被消費的過程,在android中還是比較復雜的傳遞流程如下:
(1) 事件從Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的View(ViewGroup)開始一直往下(子View)傳遞。子View可以通過onTouchEvent()對事件進行處理。
(2) 事件由父View(ViewGroup)傳遞給子View,ViewGroup可以通過onInterceptTouchEvent()對事件做攔截,停止其往下傳遞。