我正在动态创建我的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);
}
感谢任何帮助。
答案 0 :(得分:710)
基本问题是,你必须等待实际测量的绘制阶段(特别是动态值,如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 Lvl< 16 使用它取消注册监听器:
public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)
不是很有名,也是我最喜欢的解决方案。基本上只需使用View的post方法和您自己的runnable。这基本上按照Romain Guy所述的视图的度量,布局等对您的代码进行排队:
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时有多次执行代码,可以使用以下支持v4 lib方法:
View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
viewYouNeedHeightFrom.getHeight();
}
如果视图已经通过至少一个布局,则返回true 最后附在窗户上或从窗户上拆下。
如果只需获得静态定义的高度/宽度就足够了,您可以使用以下方法执行此操作:
但请注意,这可能与绘图后的实际宽度/高度不同。 javadoc完美地描述了这种差异:
视图的大小用宽度和高度表示。一个看法 实际上拥有两对宽度和高度值。
第一对称为测量宽度和测量高度。这些 维度定义视图在其父级中的大小(请参阅 布局了解更多细节。)测量尺寸可以通过获得 调用getMeasuredWidth()和getMeasuredHeight()。
第二对简称为宽度和高度,有时也称为宽度和高度 绘图宽度和绘图高度。这些尺寸定义了实际尺寸 屏幕上,绘图时和布局后视图的大小。这些 值可以但不必与测量的宽度不同 和高度。可以通过调用getWidth()获得宽度和高度 和getHeight()。
答案 1 :(得分:258)
我们可以使用
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//Here you can get the size!
}
答案 2 :(得分:178)
您过早地致电getWidth()
。用户界面尚未调整大小并在屏幕上显示。
我怀疑你想要做你正在做的事情,无论如何 - 动画小工具不会改变他们的可点击区域,因此按钮仍然会响应原始方向的点击,无论它如何旋转。
话虽这么说,您可以使用dimension resource来定义按钮大小,然后从布局文件和源代码中引用该维度资源,以避免此问题。
答案 3 :(得分:106)
我使用了这个解决方案,我认为它比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 :)
}
答案 4 :(得分:75)
正如伊恩在this Android Developers thread中所述:
无论如何,这笔交易就是布局了 窗口的内容发生 之后构造所有元素并将其添加到其父元素中 观点。它必须是这样的,因为 直到你知道View的组件 包含,以及它们包含的内容,以及 等等,你没有明智的方法 把它打好。
底线,如果你调用getWidth() 在构造函数中,它将返回 零。程序是创建所有 构造函数中的视图元素, 然后等待你的View 要调用的onSizeChanged()方法 - 那是你第一次发现你的时候 实际尺寸,所以当你设置 GUI元素的大小。
也要注意onSizeChanged()是 有时用参数调用 零 - 检查这种情况,并且 马上回来(所以你没有得到 计算你的时候除以零 布局等)。一段时间后呢 将被称为真实值。
答案 5 :(得分:62)
如果您需要在屏幕上显示某些小部件的宽度之前获取其宽度,则可以使用getMeasuredWidth()或getMeasuredHeight()。
myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
答案 6 :(得分:16)
我宁愿使用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;
}
});
答案 7 :(得分:5)
扩展以在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)
}
答案 8 :(得分:3)
如果您使用的是RxJava&amp; RxBindings。没有样板的类似方法。这也解决了黑客在Tim Autin的答案中抑制警告的问题。
RxView.layoutChanges(yourView).take(1)
.subscribe(aVoid -> {
// width and height have been calculated here
});
就是这样。即使从未打过电话,也无需取消订阅。
答案 9 :(得分:3)
使用post
的答案是错误的,因为可能不会重新计算大小。
另一个重要的事情是,视图及其所有祖先必须可见。为此,我使用属性View.isShown
。
这是我的kotlin函数,可以将其放置在utils中:
fun View.onInitialized(onInit: () -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (isShown) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
onInit()
}
}
})
}
用法是:
myView.onInitialized {
Log.d(TAG, "width is: " + myView.width)
}
答案 10 :(得分:2)
也许这可以帮助某人:
为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
}
答案 11 :(得分:1)
如果您使用的是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
}
})
答案 12 :(得分:1)
AndroidX在androidx.core.view
您需要为此使用Kotlin。
最适合这里的是doOnLayout
:
布局此视图时,执行给定的操作。如果视图已被布局并且尚未请求布局,则将立即执行该操作,否则,将在下一个视图被布局后执行该操作。
该动作只会在下一个布局上调用一次,然后删除。
在您的示例中:
bt.doOnLayout {
val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
... more code
}
相关性:androidx.core:core-ktx:1.0.0
答案 13 :(得分:1)
高度和宽度为零,因为在您请求视图的高度和宽度时尚未创建视图。 一种最简单的解决方案是
view.post(new Runnable() {
@Override
public void run() {
view.getHeight(); //height is ready
view.getWidth(); //width is ready
}
});
与其他方法相比,该方法短而明快。
答案 14 :(得分:0)
我们需要等待视图将被绘制。为此,请使用OnPreDrawListener。 Kotlin示例:
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)
答案 15 :(得分:0)
发生这种情况是因为视图需要更多的时间来膨胀。因此,应该在主线程上调用view.width
来确保view.height
已经膨胀,而不是在主线程上调用view.post { ... }
和view
。在科特林:
view.post{width}
view.post{height}
在Java中,您还可以在getWidth()
中调用getHeight()
和Runnable
方法,并将Runnable
传递给view.post()
方法。
view.post(new Runnable() {
@Override
public void run() {
view.getWidth();
view.getHeight();
}
});
答案 16 :(得分:0)
如果应用程序在后台运行,则视图消失时返回高度为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)
}
}
})
}
答案 17 :(得分:0)
这有点旧,但是我自己却遇到了麻烦(创建片段时需要动画化片段中的对象)。这种解决方案对我有用,我相信这是不言而喻的。
class YourFragment: Fragment() {
var width = 0
var height = 0
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val root = inflater.inflate(R.layout.fragment_winner_splash, container, false)
container?.width.let {
if (it != null) {
width = it
}
}
container?.height.let {
if (it != null) {
height = it
}
}
return root
}
答案 18 :(得分:0)
如果你担心 onDraw 方法过度工作,你可以在构造过程中将维度设置为 null,然后只在 onDraw 内部设置维度,如果它为空。
那样你就不用在 onDraw 里面做任何工作了
class myView(context:Context,attr:AttributeSet?):View(context,attr){
var height:Float?=null
override fun onDraw(canvas:Canvas){
if (height==null){height=this.height.toFloat()}
}
}
答案 19 :(得分:0)
好吧,你可以使用 addOnLayoutChangeListener
您可以在 onCreate
或 onCreateView
中使用它
myView.addOnLayoutChangeListener { view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
Log.i(TAG, "view : ${view.width}")
}