【问题标题】:Fixed aspect ratio View固定纵横比视图
【发布时间】:2011-10-26 20:59:54
【问题描述】:

我将如何实现固定纵横比View?我想在GridView 中拥有纵横比为 1:1 的项目。我认为子类比GridView更好?

编辑:我认为这需要以编程方式完成,这没问题。另外,我不想限制大小,只限制纵横比。

【问题讨论】:

标签: android view aspect-ratio viewgroup


【解决方案1】:

您可能会找到第三方库。不要使用它们,而是使用约束布局。 以下代码将 ImageView 的纵横比设置为 16:9,无论屏幕大小和方向如何。

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:scaleType="fitXY"
        android:src="@drawable/mat3"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintDimensionRatio="H,16:9"/>

</androidx.constraintlayout.widget.ConstraintLayout>

app:layout_constraintDimensionRatio="H,16:9"。这里,高度是相对于布局的宽度设置的。

对于您的问题,请设置android:layout_width="match_parent" 并在您的视图中使用app:layout_constraintDimensionRatio="H,1:1"

【讨论】:

    【解决方案2】:

    Google 的 ExoPlayer 带有一个 AspectRatioFrameLayout,您可以这样使用:

    <com.google.android.exoplayer2.ui.AspectRatioFrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:resize_mode="fixed_width">
        <!-- https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.html#RESIZE_MODE_FIXED_WIDTH -->
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
    

    那么你必须以编程方式设置纵横比:

    aspectRatioFrameLayout.setAspectRatio(16f/9f)
    

    请注意,您还可以使用setResizeMode 以编程方式设置调整大小模式。


    由于您显然不会为这个单一类获取整个 ExoPlayer 库,您可以简单地将文件从 GitHub 复制粘贴到您的项目(它是开源的):

    https://github.com/google/ExoPlayer/blob/release-v2/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java

    别忘了也获取属性resize_mode

    https://github.com/google/ExoPlayer/blob/release-v2/library/ui/src/main/res/values/attrs.xml#L18-L25

    <attr name="resize_mode" format="enum">
      <enum name="fit" value="0"/>
      <enum name="fixed_width" value="1"/>
      <enum name="fixed_height" value="2"/>
      <enum name="fill" value="3"/>
      <enum name="zoom" value="4"/>
    </attr>
    

    【讨论】:

      【解决方案3】:

      我使用TalL 的回答创建了一个布局库。随意使用。

      RatioLayouts

      安装

      将此添加到文件顶部

      repositories {
          maven {
              url  "http://dl.bintray.com/riteshakya037/maven" 
          }
      }
      
      
      dependencies {
          compile 'com.ritesh:ratiolayout:1.0.0'
      }
      

      用法

      在布局的根视图上定义“app”命名空间

      xmlns:app="http://schemas.android.com/apk/res-auto"
      

      在你的布局中包含这个库

      <com.ritesh.ratiolayout.RatioRelativeLayout
              android:id="@+id/activity_main_ratio_layout"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              app:fixed_attribute="WIDTH" // Fix one side of the layout 
              app:horizontal_ratio="2" // ratio of 2:3
              app:vertical_ratio="3">
      

      更新

      通过引入 ConstraintLayout,您不必编写任何一行代码或使用第三方或依赖于 26.0.0 中已弃用的 PercentFrameLayout

      以下是如何使用ConstraintLayout 为布局保持 1:1 纵横比的示例:

      <android.support.constraint.ConstraintLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <LinearLayout
              android:layout_width="0dp"
              android:layout_height="0dp"
              android:layout_marginEnd="0dp"
              android:layout_marginStart="0dp"
              android:layout_marginTop="0dp"
              android:background="@android:color/black"
              app:layout_constraintDimensionRatio="H,1:1"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintTop_toTopOf="parent">
      
          </LinearLayout>
      
      </android.support.constraint.ConstraintLayout>
      

      【讨论】:

        【解决方案4】:

        为了不使用第三方解决方案并考虑到 PercentFrameLayout 和 PercentRelativeLayout 在 26.0.0 中已被弃用,我建议您考虑使用 ConstraintLayout 作为网格项目的根布局。

        您的 item_grid.xml 可能如下所示:

        <android.support.constraint.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        
            <ImageView
                android:id="@+id/imageview_item"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:scaleType="centerCrop"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintDimensionRatio="H,1:1" />
        
        </android.support.constraint.ConstraintLayout>
        

        结果你会得到这样的结果:

        【讨论】:

        • 谢谢,这就像一个魅力,但是,Android Studio 抱怨 "H,1:1" 是一个无效的浮点数。如果您设置 app:layout_constraintDimensionRatio="1.0" 它的工作方式相同,但没有警告。
        【解决方案5】:

        我实现了 FixedAspectRatioFrameLayout,因此我可以重用它并使任何托管视图具有固定的纵横比:

        public class FixedAspectRatioFrameLayout extends FrameLayout
        {
            private int mAspectRatioWidth;
            private int mAspectRatioHeight;
        
            public FixedAspectRatioFrameLayout(Context context)
            {
                super(context);
            }
        
            public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs)
            {
                super(context, attrs);
        
                init(context, attrs);
            }
        
            public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs, int defStyle)
            {
                super(context, attrs, defStyle);
        
                init(context, attrs);
            }
        
            private void init(Context context, AttributeSet attrs)
            {
                TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FixedAspectRatioFrameLayout);
        
                mAspectRatioWidth = a.getInt(R.styleable.FixedAspectRatioFrameLayout_aspectRatioWidth, 4);
                mAspectRatioHeight = a.getInt(R.styleable.FixedAspectRatioFrameLayout_aspectRatioHeight, 3);
        
                a.recycle();
            }
            // **overrides**
        
            @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
            {
                int originalWidth = MeasureSpec.getSize(widthMeasureSpec);
        
                int originalHeight = MeasureSpec.getSize(heightMeasureSpec);
        
                int calculatedHeight = originalWidth * mAspectRatioHeight / mAspectRatioWidth;
        
                int finalWidth, finalHeight;
        
                if (calculatedHeight > originalHeight)
                {
                    finalWidth = originalHeight * mAspectRatioWidth / mAspectRatioHeight; 
                    finalHeight = originalHeight;
                }
                else
                {
                    finalWidth = originalWidth;
                    finalHeight = calculatedHeight;
                }
        
                super.onMeasure(
                        MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), 
                        MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY));
            }
        }
        

        【讨论】:

        • Init 方法应该以小写字母开头。 Java 约定。
        • 我刚刚根据相机 AOSP 应用程序中的 PreviewFrameLayout 实现了一些类似的东西。但是,我注意到重力似乎不再受到尊重 - 在高度受到限制的情况下,所有额外的空白似乎都在底部,即使是 Gravity.CENTERGravity.BOTTOM,例如。你见过这个吗?如果是这样,您是否碰巧想出了解决方案?谢谢!
        • @CommonsWare 将FixedAspectRatioFrameLayout 放入RelativeLayout 并将android:layout_centerInParent="true" 添加到FixedAspectRatioFrameLayout
        • @Oliv:是的,我最终做了一些类似的事情:github.com/commonsguy/cwac-layouts
        • 粘贴到 res/values/attrs.xml: ` `
        【解决方案6】:

        对于新用户,这里有一个更好的非代码解决方案:

        Android SDK v22 中提供了一个名为 Percent Support Library 的新支持库(我认为 MinAPI 是 7,不确定):

        源代码:android-developers.blogspot.in

        百分比支持库提供基于百分比的尺寸和边距,并且在此版本中新增了通过 app:aspectRatio 设置自定义纵横比的功能。通过仅设置单个宽度或高度并使用 aspectRatio,PercentFrameLayout 或 PercentRelativeLayout 将自动调整其他尺寸,以便布局使用设置的宽高比。

        要将其添加到您的 build.gradle 中:

        compile 'com.android.support:percent:23.1.1'
        

        现在用PercentRelativeLayout / PercentFrameLayout 包裹你的视图(需要是方形的):

        <android.support.percent.PercentRelativeLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
             <ImageView
                 app:layout_aspectRatio="100%"
                 app:layout_widthPercent="100%"/>
         </android.support.percent.PercentRelativeLayout>
        

        你可以看一个例子here

        【讨论】:

        • 虽然 PercentRelativeLayout 适用于某些用例,但它不适用于 M 之前的滚动视图,并且由于它支持子大小调整,因此更加复杂。如果您只需要一个固定的纵横比视图,那么接受的答案就是最好的。
        • 百分比库从 26-rc1 开始被弃用,取而代之的是 ConstraintLayout... 这两者之间的关系并不大。
        【解决方案7】:

        我已经使用并喜欢 Jake Wharton 的 ImageView 实现(其他视图应该类似),其他人可能也会喜欢它:

        AspectRatioImageView.java - ImageView 尊重应用于特定测量的纵横比

        好东西它已经可以在 xml 中设置样式了。

        【讨论】:

          【解决方案8】:

          只需覆盖onSizeChanged 并在那里计算比率。

          长宽比的公式是:

          newHeight =  original_height / original_width x new_width
          

          这会给你类似的东西:

           @Override
          protected void onSizeChanged(int w, int h, int oldw, int oldh) {
              super.onSizeChanged(w, h, oldw, oldh);
          
              //3:5 ratio
              float RATIO = 5/3;
              setLayoutParams(new LayoutParams((int)RATIO * w, w));
          
          }
          

          希望这会有所帮助!

          【讨论】:

            【解决方案9】:

            我最近为这个问题创建了一个帮助类并写了一个blog post about it

            代码肉如下:

            /**
             * Measure with a specific aspect ratio<br />
             * <br />
             * @param widthMeasureSpec The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
             * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
             * @param aspectRatio The aspect ratio to calculate measurements in respect to 
             */
            public void measure(int widthMeasureSpec, int heightMeasureSpec, double aspectRatio) {
                int widthMode = MeasureSpec.getMode( widthMeasureSpec );
                int widthSize = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( widthMeasureSpec );
                int heightMode = MeasureSpec.getMode( heightMeasureSpec );
                int heightSize = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( heightMeasureSpec );
            
                if ( heightMode == MeasureSpec.EXACTLY && widthMode == MeasureSpec.EXACTLY ) {
                    /* 
                     * Possibility 1: Both width and height fixed
                     */
                    measuredWidth = widthSize;
                    measuredHeight = heightSize;
            
                } else if ( heightMode == MeasureSpec.EXACTLY ) {
                    /*
                     * Possibility 2: Width dynamic, height fixed
                     */
                    measuredWidth = (int) Math.min( widthSize, heightSize * aspectRatio );
                    measuredHeight = (int) (measuredWidth / aspectRatio);
            
                } else if ( widthMode == MeasureSpec.EXACTLY ) {
                    /*
                     * Possibility 3: Width fixed, height dynamic
                     */
                    measuredHeight = (int) Math.min( heightSize, widthSize / aspectRatio );
                    measuredWidth = (int) (measuredHeight * aspectRatio);
            
                } else {
                    /* 
                     * Possibility 4: Both width and height dynamic
                     */
                    if ( widthSize > heightSize * aspectRatio ) {
                        measuredHeight = heightSize;
                        measuredWidth = (int)( measuredHeight * aspectRatio );
                    } else {
                        measuredWidth = widthSize;
                        measuredHeight = (int) (measuredWidth / aspectRatio);
                    }
            
                }
            }
            

            【讨论】:

            • 基于此代码的 FixedAspectRatioFrameLayout:github.com/triposo/barone/blob/master/src/com/triposo/barone/…
            • 我不确定你是否必须做所有这些事情,只需调用 onMeasure 的基类来完成繁重的工作,并在返回之前从测量的宽度*纵横比修复测量的高度。
            • 取决于你想做什么。我认为您在上面的代码中描述了可能性 3(宽度固定,高度动态)。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-06-29
            • 1970-01-01
            • 2013-07-07
            • 2017-02-13
            • 1970-01-01
            • 2013-10-31
            • 2015-05-06
            相关资源
            最近更新 更多