私は自分のAndroidプロジェクトのすべての要素を動的に作成しています。ボタンを回転させることができるように、ボタンの幅と高さを取得しようとしています。私はただAndroid言語で作業する方法を学ぼうとしています。ただし、0を返します。
私はいくつかの研究をしました、そしてそれがonCreate()
メソッド以外の場所で行われる必要があることを私は見ました。誰かが私にそのやり方の例を教えてくれるのなら、それは素晴らしいことです。
これが私の現在のコードです:
package com.animation;
import Android.app.Activity;
import Android.os.Bundle;
import Android.view.animation.Animation;
import Android.view.animation.LinearInterpolator;
import Android.view.animation.RotateAnimation;
import Android.widget.Button;
import Android.widget.LinearLayout;
public class AnimateScreen extends Activity {
//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout ll = new LinearLayout(this);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(30, 20, 30, 0);
Button bt = new Button(this);
bt.setText(String.valueOf(bt.getWidth()));
RotateAnimation ra = new RotateAnimation(0,360,bt.getWidth() / 2,bt.getHeight() / 2);
ra.setDuration(3000L);
ra.setRepeatMode(Animation.RESTART);
ra.setRepeatCount(Animation.INFINITE);
ra.setInterpolator(new LinearInterpolator());
bt.startAnimation(ra);
ll.addView(bt,layoutParams);
setContentView(ll);
}
任意の助けは大歓迎です。
あなたはgetWidth()
を早く呼び出しています。 UIはまだサイズ変更されておらず、画面にレイアウトされていません。
アニメーション化されているウィジェットはクリック可能な領域を変更しないので、ボタンは回転方向に関係なく、元の方向のクリックに反応します。
そうは言っても、 dimensionリソース を使用してボタンサイズを定義し、レイアウトファイルとソースコードからそのディメンションリソースを参照することで、この問題を回避できます。
基本的な問題は、実際の測定のために描画フェーズを待つ必要があることです(特にwrap_content
やmatch_parent
のような動的な値の場合)が、通常このフェーズはonResume()
まで終わっていません。したがって、このフェーズを待つための回避策が必要です。これにはさまざまな解決策があります。
ViewTreeObserverは、さまざまな描画イベントに対して起動されます。通常は OnGlobalLayoutListener
が測定値を取得するために必要なものなので、リスナー内のコードはレイアウトフェーズの後に呼び出されるので、測定値の準備は完了です。
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
view.getHeight(); //height is ready
}
});
注:リスナーは、レイアウトイベントが発生するたびに発生するため、すぐに削除されます。アプリケーションをサポートする必要がある場合は、{SDK Lv <16}を使用してリスナーの登録を解除します。
public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)
あまり知られていないし、私のお気に入りの解決策。基本的にはViewのpostメソッドをあなた自身のrunnableと一緒に使うだけです。 Romain Guy で述べられているように、これは基本的にあなたのコードafterビューのメジャー、レイアウトなどをキューに入れます。
UIイベントキューはイベントを順番に処理します。 setContentView()が呼び出された後、イベントキューには再レイアウトを要求するメッセージが含まれるため、キューに投稿したものはすべてレイアウトパス後に発生します。
例:
final View view=//smth;
...
view.post(new Runnable() {
@Override
public void run() {
view.getHeight(); //height is ready
}
});
ViewTreeObserver
に対する利点:
参考文献:
これは、ロジックをビュー自体にカプセル化できる場合にのみ実用的です。それ以外の場合は、これは非常に冗長で面倒な構文です。
view = new View(this) {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
view.getHeight(); //height is ready
}
};
また、onLayoutは何度も呼び出されることに注意してください。そのため、メソッドで何をするのかを検討するか、または初回の実行後にコードを無効にしてください。
UIの作成中に複数回実行されるコードがある場合は、次のsupport v4 libメソッドを使用できます。
View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
viewYouNeedHeightFrom.getHeight();
}
ビューがウィンドウに最後にアタッチされたとき、またはウィンドウからデタッチされたとき以降、少なくとも1つのレイアウトが表示されている場合はtrueを返します。
静的に定義された高さ/幅を取得するだけで十分な場合は、次のようにして実行できます。
ただし、描画後の実際の幅/高さとは異なる場合があります。 Javadocはその違いを完全に説明しています。
ビューのサイズは幅と高さで表されます。ビューは実際には2組の幅と高さの値を持っています。
最初のペアは、測定幅と測定高さとして知られています。これらのディメンションは、ビューがその親内にどのくらいの大きさになりたいかを定義します(詳細についてはレイアウトを参照してください)。測定されたディメンションは、getMeasuredWidth()およびgetMeasuredHeight()を呼び出すことによって取得できます。
2番目のペアは単純に幅と高さ、または描画幅と描画高さとして知られています。これらの寸法は、描画時およびレイアウト後の画面上のビューの実際のサイズを定義します。これらの値は、必ずしも測定された幅や高さとは異なる場合があります。幅と高さは、getWidth()およびgetHeight()を呼び出すことによって取得できます。
使用できます
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//Here you can get the size!
}
私はこのソリューションを使いました。onWindowFocusChanged()よりも優れていると思います。 DialogFragmentを開いてから電話を回転させると、onWindowFocusChangedは、ユーザーがダイアログを閉じたときにのみ呼び出されます。
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Ensure you call it only once :
yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// Here you can get the size :)
}
});
編集:removeGlobalOnLayoutListenerは廃止予定のため、次の手順を実行します。
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
// Ensure you call it only once :
if(Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.JELLY_BEAN) {
yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
else {
yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// Here you can get the size :)
}
Ianが このAndroid Developersスレッド で述べているように、
ともかく、ウィンドウの内容のレイアウトはの後ですべての要素が構築されて親ビューに追加されます。ビューに含まれるコンポーネントとその内容がわかるまで、このようにする必要があります。それらは含んでいます、そして、あなたがそれをレイアウトすることができる賢明な方法はありません。
つまり、コンストラクタでgetWidth()などを呼び出すと、ゼロが返されます。この手順では、コンストラクタ内にすべてのビュー要素を作成してから、ビューのonSizeChanged()メソッドが呼び出されるのを待ちます。つまり、実際のサイズを最初に見つけたときにGUI要素のサイズを設定します。
OnSizeChanged()はゼロのパラメータで呼び出されることがあるので注意してください。この場合は確認してすぐに戻ってください(レイアウトを計算するときなどにゼロ除算は行われません)。しばらくしてから、実際の値で呼び出されます。
ウィジェットが画面に表示される前にウィジェットの幅を取得する必要がある場合は、getMeasuredWidth()またはgetMeasuredHeight()を使用できます。
myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
OnPreDrawListener()
の代わりにaddOnGlobalLayoutListener()
を使うほうがいいでしょう。
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
{
@Override
public boolean onPreDraw()
{
if (view.getViewTreeObserver().isAlive())
view.getViewTreeObserver().removeOnPreDrawListener(this);
// put your code here
return true;
}
});
Kotlinの高さを動的に取得するための拡張。
使用法:
view.height { Log.i("Info", "Here is your height:" + it) }
実装:
fun <T : View> T.height(function: (Int) -> Unit) {
if (height == 0)
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
function(height)
}
})
else function(height)
}
RxJava & RxBindings を使用している場合は、1つのライナー。定型句なしの同様のアプローチ。これは Tim Autin による答えのように警告を抑制するためのハックも解決します。
RxView.layoutChanges(yourView).take(1)
.subscribe(aVoid -> {
// width and height have been calculated here
});
これです。たとえ呼び出されなかったとしても、購読を中止する必要はありません。
たぶんこれは誰かを助ける:
Viewクラスの拡張関数を作成します
ファイル名:ViewExt.kt
fun View.afterLayout(what: () -> Unit) {
if(isLaidOut) {
what.invoke()
} else {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
what.invoke()
}
})
}
}
これは、次のビューで使用できます。
view.afterLayout {
do something with view.height
}
Kotlinを使用している場合
leftPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if(Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.JELLY_BEAN) {
leftPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
else {
leftPanel.viewTreeObserver.removeGlobalOnLayoutListener(this)
}
// Here you can get the size :)
leftThreshold = leftPanel.width
}
})
高さと幅が要求された時点でビューが作成されていないため、高さと幅はゼロです。最も簡単な解決策は
view.post(new Runnable() {
@Override
public void run() {
view.getHeight(); //height is ready
view.getWidth(); //width is ready
}
});
この方法は、短くてさわやかであるため、他の方法と比較して優れています。
バックグラウンドでアプリが表示されている場合、表示が消えて高さとして0が返されます。この私のコード(1oo%が動作します)
fun View.postWithTreeObserver(postJob: (View, Int, Int) -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
measure(widthSpec, heightSpec)
postJob(this@postWithTreeObserver, measuredWidth, measuredHeight)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
@Suppress("DEPRECATION")
viewTreeObserver.removeGlobalOnLayoutListener(this)
} else {
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
}
ビューが描画されるのを待つ必要があります。この目的のためにOnPreDrawListenerを使用してください。コトリンの例:
val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
view.viewTreeObserver.removeOnPreDrawListener(this)
// code which requires view size parameters
return true
}
}
view.viewTreeObserver.addOnPreDrawListener(preDrawListener)