【发布时间】:2012-01-24 01:31:45
【问题描述】:
我使用GridView 来显示一堆基本上是LinearLayouts 的视图。我希望LinearLayouts 都是方形的,但我也希望它们是动态大小的——也就是说,有两列,我希望LinearLayouts 根据屏幕的大小拉伸但保持方形。有没有办法通过xml 布局做到这一点,还是我必须以编程方式设置高度和宽度?
【问题讨论】:
标签: android
我使用GridView 来显示一堆基本上是LinearLayouts 的视图。我希望LinearLayouts 都是方形的,但我也希望它们是动态大小的——也就是说,有两列,我希望LinearLayouts 根据屏幕的大小拉伸但保持方形。有没有办法通过xml 布局做到这一点,还是我必须以编程方式设置高度和宽度?
【问题讨论】:
标签: android
方形GridView 项目的一个简洁解决方案是扩展RelativeLayout 或LinearLayout 并覆盖onMeasure,如下所示:
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
【讨论】:
随着 Android Studio 2.3 中引入的新 ConstraintLayout,现在构建响应式布局变得非常容易。
在父 ConstraintLayout 中,要使其任何子视图/布局动态成正方形,请添加此属性
app:layout_constraintDimensionRatio="w,1:1"
w 是指定宽度方向的约束,1:1 的比例确保方形布局。
【讨论】:
我是这样做的:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int size;
if(widthMode == MeasureSpec.EXACTLY && widthSize > 0){
size = widthSize;
}
else if(heightMode == MeasureSpec.EXACTLY && heightSize > 0){
size = heightSize;
}
else{
size = widthSize < heightSize ? widthSize : heightSize;
}
int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}
使用此实现,您的布局将是方形的,假设宽度和高度之间的尺寸较小。它甚至可以使用动态值进行设置,例如在 LinearLayout 中使用权重。
【讨论】:
xml 中没有任何内容可以让您链接宽度和高度属性。可能最简单的做法是继承LinearLayout 并覆盖onMeasure
@Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int size = width > height ? height : width;
setMeasuredDimension(size, size);
}
我用它来创建以前总是方形的视图。它仍然适用于LinearLayout。
有助于执行此操作的更多信息: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html
【讨论】:
super.onMeasure(widthMeasureSpec, widthMeasureSpec);!
super.onMeasure(widthMeasureSpec, widthMeasureSpec); 确实会给你一个正方形,但它总是宽度 x 宽度。如果给出的测量值比它们的高宽,你就会遇到问题。我的解决方案使用两个测量值中较小的一个,因此您始终可以获得适合给定矩形的最大正方形。
我们可以通过一种非常简单的方式来做到这一点 - 只需调用 super.onMeasure() 两次。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int squareLen = Math.min(width, height);
super.onMeasure(
MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY));
}
通过两次调用super.onMeasure(),这在绘图过程中效率较低,但它是解决其他答案可能导致的布局问题的简单方法。
【讨论】:
super.onMeasure() 两次可确保在给定新视图大小的情况下正确完成布局。如果没有这个,我们需要以不同的方式触发布局或处理不正确的布局。
就这么简单:
public class SquareRelativeLayout extends RelativeLayout {
public SquareRelativeLayout(Context context) {
super(context);
}
public SquareRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if (widthMeasureSpec < heightMeasureSpec)
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
else
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
}
}
【讨论】:
在 XML 中添加以下行:
app:layout_constraintDimensionRatio="1:1"
【讨论】:
这是一个适用于所有可以设置为视图或视图组的布局参数的解决方案:
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthDesc = MeasureSpec.getMode(widthMeasureSpec);
int heightDesc = MeasureSpec.getMode(heightMeasureSpec);
int size = 0;
if (widthDesc == MeasureSpec.UNSPECIFIED
&& heightDesc == MeasureSpec.UNSPECIFIED) {
size = DP(defaultSize); // Use your own default size, in our case
// it's 125dp
} else if ((widthDesc == MeasureSpec.UNSPECIFIED || heightDesc == MeasureSpec.UNSPECIFIED)
&& !(widthDesc == MeasureSpec.UNSPECIFIED && heightDesc == MeasureSpec.UNSPECIFIED)) {
//Only one of the dimensions has been specified so we choose the dimension that has a value (in the case of unspecified, the value assigned is 0)
size = width > height ? width : height;
} else {
//In all other cases both dimensions have been specified so we choose the smaller of the two
size = width > height ? height : width;
}
setMeasuredDimension(size, size);
干杯
【讨论】:
我的建议是创建一个继承自 FrameLayout 的自定义布局类。覆盖 OnMeasure() 方法并将您想要的任何控件放在 SquareFrameLayout 中。
这就是它在 Xamarin.Android 中的完成方式:
public class SquareFrameLayout : FrameLayout
{
private const string _tag = "SquareFrameLayout";
public SquareFrameLayout(Android.Content.Context context):base(context) {}
public SquareFrameLayout(IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer):base(javaReference, transfer) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs):base(context, attrs) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr):base(context, attrs, defStyleAttr) {}
public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes):base(context, attrs, defStyleAttr, defStyleRes) {}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
var widthMode = MeasureSpec.GetMode(widthMeasureSpec);
int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
var heightMode = MeasureSpec.GetMode(heightMeasureSpec);
int heightSize = MeasureSpec.GetSize(heightMeasureSpec);
int width, height;
switch (widthMode)
{
case MeasureSpecMode.Exactly:
width = widthSize;
break;
case MeasureSpecMode.AtMost:
width = Math.Min(widthSize, heightSize);
break;
default:
width = 100;
break;
}
switch (heightMode)
{
case MeasureSpecMode.Exactly:
height = heightSize;
break;
case MeasureSpecMode.AtMost:
height = Math.Min(widthSize, heightSize);
break;
default:
height = 100;
break;
}
Log.Debug(_tag, $"OnMeasure({widthMeasureSpec}, {heightMeasureSpec}) => Width mode: {widthMode}, Width: {widthSize}/{width}, Height mode: {heightMode}, Height: {heightSize}/{height}");
var size = Math.Min(width, height);
var newMeasureSpec = MeasureSpec.MakeMeasureSpec(size, MeasureSpecMode.Exactly);
base.OnMeasure(newMeasureSpec, newMeasureSpec);
}
}
如果您希望视图(或任何其他控件)为方形(并居中),只需按以下方式将其添加到您的布局中:
<your.namespace.SquareFrameLayout
android:id="@+id/squareContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center">
<View
android:id="@+id/squareContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</your.namespace.SquareFrameLayout>
【讨论】:
查看SquareLayout,这是一个Android 库,它为不同的布局提供了一个包装类,在不丢失任何核心功能的情况下将它们呈现为平方尺寸。
尺寸是在布局渲染之前计算的,因此一旦获得视图,就没有重新渲染或任何类似的调整。
要使用库,请将其添加到您的 build.gradle:
repositories {
maven {
url "https://maven.google.com"
}
}
dependencies {
compile 'com.github.kaushikthedeveloper:squarelayout:0.0.3'
}
您需要的是SquareLinearLayout。
【讨论】:
对于任何想要使用 Kotlin 解决方案的人,这就是我使用 FrameLayout 所做的。
package your.package.name
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
class SquareLayout: FrameLayout {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (widthMeasureSpec < heightMeasureSpec)
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
else
super.onMeasure(heightMeasureSpec, heightMeasureSpec)
}
}
【讨论】:
试试这个代码:
public class SquareRelativeLayout extends RelativeLayout {
public SquareRelativeLayout(Context context) {
super(context);
}
public SquareRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int size;
if (widthMode == MeasureSpec.EXACTLY && widthSize > 0) {
size = widthSize;
} else if (heightMode == MeasureSpec.EXACTLY && heightSize > 0) {
size = heightSize;
} else {
size = widthSize < heightSize ? widthSize : heightSize;
}
int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}
}
【讨论】:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>
【讨论】: