按钮的大小和位置随机:意外行为

时间:2019-02-14 21:10:20

标签: android android-layout android-button android-constraintlayout

我的/usr

ConstraintLayout

我的简化代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginTop="0dp"
        android:layout_marginEnd="0dp"
        android:layout_marginBottom="0dp"
        android:minWidth="0dp"
        android:minHeight="0dp"
        android:text="@string/start"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

预期的行为:

在整个屏幕的开头出现一个按钮。按下该按钮后,将调整该按钮的大小(正方形,侧面至少为屏幕宽度的1/25,最大为屏幕宽度的1/2),并在屏幕上的任意位置移动。每次用户再次按下该按钮时,其行为都相同(新的随机位置和大小)。

我的问题:

import android.annotation.SuppressLint; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import java.util.Random; public class MainActivity extends AppCompatActivity { final Random rand = new Random(); private Button btn; private float widthPixels, heightPixels; private float minBtnSize, maxBtnSize; @SuppressLint("ClickableViewAccessibility") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /* Obtaining screen's display properties. */ final DisplayMetrics displaymetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displaymetrics); widthPixels = displaymetrics.widthPixels; heightPixels = displaymetrics.heightPixels; // includes BotNavBar heightPixels -= 162; // size of the "Virtual Nav Bar" at the bottom float minDim = Math.min(widthPixels, heightPixels); minBtnSize = minDim/25; maxBtnSize = minDim/2; /* Setting up the experiment's Button*/ btn = findViewById(R.id.btn); btn.setOnTouchListener(new View.OnTouchListener(){ public boolean onTouch(View view, MotionEvent event){ /* Touch Events. */ switch (event.getAction()) { case MotionEvent.ACTION_UP: /* Move and resize the button. */ launchNewAttempt(x, y); break; default: break; } return true; } }); /* Setting up the beginning Button's state. */ init(); } /** * Initializing the Button shown at the very beginning. */ private void init() { btn.setX(0); btn.setY(0); btn.setWidth((int)widthPixels); btn.setHeight((int)heightPixels); } /** * Called whenever the user releases the experiment's button (pressed "UP"). */ private void launchNewAttempt() { /* Random size for the "new" button. */ int size=(int)(minBtnSize+(maxBtnSize-minBtnSize)*rand.nextDouble()); btn.setWidth(size); btn.setHeight(size); /* Random position for the "new" button. */ float dx = rand.nextFloat() * (widthPixels - size); float dy = rand.nextFloat() * (heightPixels - size); btn.setX(dx); btn.setY(dy); } } btn.setX()的行为似乎不符合预期。我以为他们可以确定按钮在屏幕上的绝对位置,但是对btn.setY()使用以下示例代码会产生奇怪的行为:

init()

大小似乎还可以,但是位置似乎相对于屏幕中间(尽管它似乎也取决于按钮的大小,因为使用前面介绍的代码可以正确地填满整个屏幕)。我希望这样的位置相对于屏幕的左上角(不包括顶部的ActionBar / TitleBar,也不包括底部的NavigationBar)。

运行当前代码时,btn.setX(0); btn.setY(0); btn.setWidth(150); btn.setHeight(150); 实际上有时会出现在屏幕之外。怎么了?

2 个答案:

答案 0 :(得分:1)

我尝试了您的代码用LinearLayout替换布局的根节点,并且它正常工作,所以这意味着问题与ContraintLayout的内部逻辑有关。知道我试图改变按钮的位置时要考虑约束而不是绝对位置,尤其是关于按钮的顶部和开始边距。因此,launchNewAttempt()方法可以与此类似:

private void launchNewAttempt() {
    final int size = (int) (minBtnSize + (maxBtnSize - minBtnSize) * rand.nextDouble());
    final int left = (int) (rand.nextFloat() * (widthPixels - size));
    final int top = (int) (rand.nextFloat() * (heightPixels - size));
    final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) btn.getLayoutParams();

    layoutParams.width = size;
    layoutParams.height = size;
    layoutParams.leftMargin = left;
    layoutParams.topMargin = top;

    btn.setLayoutParams(layoutParams);
}

我还只保留了两个约束来将按钮放置在布局中,并删除了结尾和底部:

<Button
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="0dp"
    android:layout_marginTop="0dp"
    android:layout_marginEnd="0dp"
    android:layout_marginBottom="0dp"
    android:minWidth="0dp"
    android:minHeight="0dp"
    android:text="Start"
    android:visibility="visible"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

希望这对您有用。

答案 1 :(得分:0)

Wang 的答案是正确的(更简洁/优雅),但是由于我实际上是在他发布前几分钟就发现了这一点,因此我还将发布我的解决方案。

作为全局变量:

private ConstraintSet cSet = new ConstraintSet();
private ConstraintLayout cLay;
private float density;

onCreate方法中正确初始化变量。

/* To be able to manipulate the position and size of the Button. */
cLay = findViewById(R.id.main);
cSet.clone(cLay);
density = displaymetrics.density;

改编的launchNewAttempt方法:

private void launchNewAttempt(int x, int y) {

    /* Random size for the new button. */
    int size = (int)(minBtnSize+(maxBtnSize-minBtnSize)*rand.nextDouble());
    cSet.constrainWidth(btn.getId(), size);
    cSet.constrainHeight(btn.getId(), size);

    /* Random position for the new button. */
    float dx = rand.nextFloat() * (widthPixels  - size) / density;
    float dy = rand.nextFloat() * (heightPixels - size) / density;
    /* Possible negatives because position relative to center of screen. */
    final float middle = .5f;
    if(rand.nextFloat() < middle)
        dx = -dx;
    if(rand.nextFloat() < middle)
        dy = -dy;
    cSet.setTranslation(btn.getId(), dx, dy);

    /* Applying the changes. */
    cSet.applyTo(cLay);
}

尽管如此,我还是建议使用 Wang 的答案。