解析AndroidProject 可设置宽高比的Layout
-
效果演示
此时屏幕的宽度是固定的,通过屏幕的宽度计算FrameLayout的高度。除了单一的宽度为屏幕宽度外,还有其他宽度或者高度固定的比例显示。
-
实现方式
布局文件find_fragment.xml
<com.hjq.widget.layout.RatioFrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/common_accent_color" app:sizeRatio="2:1"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="这是一个宽高比 2:1 的FrameLayout" android:textColor="@color/white" /> </com.hjq.widget.layout.RatioFrameLayout>
RatioFrameLayout:自定义比例FrameLayout
自定义View的方式去实现:onMeasure重新计算控件宽高
package com.hjq.widget.layout; import android.content.Context; import android.content.res.TypedArray; import android.text.TextUtils; import android.util.AttributeSet; import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.hjq.widget.R; /** * author : Android 轮子哥 * github : https://github.com/getActivity/AndroidProject * time : 2019/08/23 * desc : 按照比例显示的 FrameLayout */ public final class RatioFrameLayout extends FrameLayout { /** 宽高比例 */ private float mWidthRatio; private float mHeightRatio; public RatioFrameLayout(Context context) { this(context, null); } public RatioFrameLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RatioFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public RatioFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RatioFrameLayout); String sizeRatio = array.getString(R.styleable.RatioFrameLayout_sizeRatio); if (!TextUtils.isEmpty(sizeRatio)) { String[] split = sizeRatio.split(":"); switch (split.length) { case 1: mWidthRatio = Float.parseFloat(split[0]); mHeightRatio = 1; break; case 2: mWidthRatio = Float.parseFloat(split[0]); mHeightRatio = Float.parseFloat(split[1]); break; default: throw new IllegalArgumentException("are you ok?"); } } array.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mWidthRatio != 0 && mHeightRatio != 0) { float sizeRatio = getSizeRatio(); ViewGroup.LayoutParams layoutParams = getLayoutParams(); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); // 一般情况下 LayoutParams.WRAP_CONTENT 对应着 MeasureSpec.AT_MOST(自适应),但是由于我们在代码中强制修改了测量模式为 MeasureSpec.EXACTLY(固定值) // 这样会有可能重新触发一次 onMeasure 方法,这个时候传入测量模式的就不是 MeasureSpec.AT_MOST(自适应) 模式,而是 MeasureSpec.EXACTLY(固定值)模式 // 所以我们要进行双重判断,首先判断 LayoutParams,再判断测量模式,这样就能避免因为修改了测量模式触发对宽高的重新计算,最终导致计算结果和上次计算的不同 if (layoutParams.width != LayoutParams.WRAP_CONTENT && layoutParams.height != LayoutParams.WRAP_CONTENT && widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { // 如果当前宽度和高度都是写死的 if (widthSpecSize / sizeRatio <= heightSpecSize) { // 如果宽度经过比例换算不超过原有的高度 heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (widthSpecSize / sizeRatio), MeasureSpec.EXACTLY); } else if (heightSpecSize * sizeRatio <= widthSpecSize) { // 如果高度经过比例换算不超过原有的宽度 widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (heightSpecSize * sizeRatio), MeasureSpec.EXACTLY); } } else if (layoutParams.width != LayoutParams.WRAP_CONTENT && widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode != MeasureSpec.EXACTLY) { // 如果当前宽度是写死的,但是高度不写死 heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (widthSpecSize / sizeRatio), MeasureSpec.EXACTLY); } else if (layoutParams.height != LayoutParams.WRAP_CONTENT && heightSpecMode == MeasureSpec.EXACTLY && widthSpecMode != MeasureSpec.EXACTLY) { // 如果当前高度是写死的,但是宽度不写死 widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (heightSpecSize * sizeRatio), MeasureSpec.EXACTLY); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public float getWidthRatio() { return mWidthRatio; } public float getHeightRatio() { return mHeightRatio; } /** * 获取宽高比 */ public float getSizeRatio() { return mWidthRatio / mHeightRatio; } /** * 设置宽高比 */ public void setSizeRatio(float widthRatio, float heightRatio) { mWidthRatio = widthRatio; mHeightRatio = heightRatio; invalidate(); } }
attrs.xml 控件属性:宽高比例
<!-- 按照比例显示的 FrameLayout --> <declare-styleable name="RatioFrameLayout"> <!-- 宽高比例 --> <attr name="sizeRatio" format="string" /> </declare-styleable>