需求:
实现响应式的LinearLayout,主要要求有两点
1、可以设置两套布局(一套主布局,一套子布局)根据屏宽度动态展示子布局
2、主布局上可以动态调整布局
1、界面大调整:java层开放调节接口
2、界面小调整:组件内部对子View进行调整
验证过程:
在一块屏上显示对应布局,然后将屏横向展示查看布局改变情况
验证效果:
原理:
在布局时设置需要宽度和调整类型,在组件测量时对布局进行调整。有几个值得注意的问题
1、在增加子布局或者做布局调整的时候都需要在原有的布局基础上增加布局,本人采用的是以原有的LinearLayout为根布局新增LinearLayout存放原有布局和调整以后的布局,之后将最终布局一起塞入到根LinearLayout中。但是存在问题,就是LinearLayout有些属性需要和根LinearLayout一样,这块还不知道如何复制。
2、在java回调处理的时候,因为组件测量的时候还没有完成回调的初始化,得做延时触发处理,这个地方会出现布局的闪动。
具体代码:
自定义属性部分:
<declare-styleable name="XCAutoLinearLayout">
<attr name="xc_auto_type">
<flag name="no_auto" value="0" />
<flag name="child_layout" value="1" />
<flag name="auto_match" value="2" />
<flag name="java_control" value="3" />
</attr>
<attr name="xc_auto_self_width" format="dimension" />
<attr name="xc_auto_child_layout" format="reference" />
</declare-styleable>
子布局部分:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="@color/design_default_color_error"
android:layout_marginTop="20dp"
android:text="右侧子布局" />
</LinearLayout>
主要布局代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.XCLinearLayoutActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#00aaaa"
android:gravity="center"
android:text="响应式XCAutoLinearLayout组件"
android:textColor="#EEE"
tools:ignore="MissingConstraints" />
<com.rcl.recyclerviewdemo.widget.XCAutoLinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="50dp"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:xc_auto_child_layout="@layout/item_linear"
app:xc_auto_self_width="1000dp"
app:xc_auto_type="child_layout">
<TextView
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="15dp"
android:text="主布局" />
</com.rcl.recyclerviewdemo.widget.XCAutoLinearLayout>
<com.rcl.recyclerviewdemo.widget.XCAutoLinearLayout
android:id="@+id/xcAutollAutoMath"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="100dp"
android:orientation="vertical"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:xc_auto_self_width="1000dp"
app:xc_auto_type="auto_match">
<TextView
android:id="@+id/helloam1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="组件调控 111" />
<TextView
android:id="@+id/helloam2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="组件调控 222" />
<TextView
android:id="@+id/helloam3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="组件调控 333" />
<TextView
android:id="@+id/helloam4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="组件调控 444" />
</com.rcl.recyclerviewdemo.widget.XCAutoLinearLayout>
<com.rcl.recyclerviewdemo.widget.XCAutoLinearLayout
android:id="@+id/xcAutollJavaControl"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="200dp"
android:orientation="vertical"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:xc_auto_self_width="1000dp"
app:xc_auto_type="java_control">
<TextView
android:id="@+id/hellojc1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="java 调控111" />
<TextView
android:id="@+id/hellojc2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="java 调控222" />
<TextView
android:id="@+id/hellojc3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="java 调控333" />
<TextView
android:id="@+id/hellojc4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="java 调控444" />
</com.rcl.recyclerviewdemo.widget.XCAutoLinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
activity部分主要是java调控:
public class XCLinearLayoutActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_xclinear_layout);
XCAutoLinearLayout xcAutoLinearLayout = findViewById(R.id.xcAutollJavaControl);
xcAutoLinearLayout.setXcWidthConfigChange(new XCAutoLinearLayout.WidthConfigChange() {
@Override
public void onWidthConfigChange(boolean isDoubleWidht) {
if (isDoubleWidht) {
findViewById(R.id.hellojc1).setVisibility(View.GONE);
findViewById(R.id.hellojc3).setVisibility(View.GONE);
} else {
findViewById(R.id.hellojc1).setVisibility(View.VISIBLE);
findViewById(R.id.hellojc3).setVisibility(View.VISIBLE);
}
}
});
}
}
自定义组件XCAutoLinearLayout
public class XCAutoLinearLayout extends LinearLayout {
private static final String TAG = "XCAutoLinearLayout";
private static final int JAVA_CONTROL_UPDATE_DELAY_TIME = 10;
/**
* 不需要处理自适应,默认情况
*/
private static final int XC_AUTO_TYPE_NO = 0;
/**
* 展示右侧子布局,需要同时设置 xc_auto_child_layout属性 例如:app:xc_auto_child_layout="@layout/item_linear"
*/
private static final int XC_AUTO_TYPE_CHILD_LAYOUT = 1;
/**
* 组件自设定逻辑适配
*/
private static final int XC_AUTO_TYPE_AUTO_MATH = 2;
/**
* 开放接口给java层,在Java层做适配
*/
private static final int XC_AUTO_TYPE_JAVA_CONTROL = 3;
/**
* layout自适应的模式
*/
private int xcAutoType = 0;
/**
* 自适应的宽度
*/
private int xcAutoChildLayout = 0;
/**
* 右侧子布局
*/
private View xcAutoChildContainer;
/**
* 是否已经展示了右侧子布局(设置了weight,会出现两次测量)
*/
private Boolean isChildContainerShow = false;
/**
* 是否已经做了组件自适应(设置了weight,会出现两次测量)
*/
private Boolean isDoAutoMatch = false;
/**
* item自适应的最大宽度
*/
private float xcAutoSelfWidth = 0;
private Context xcContext;
private XCAutoLinearLayout mySelf;
/**
* 布局的属性参数,用来赋值用
*/
private AttributeSet xcAttributeSet;
/**
* 宽度变化回调
*/
private WidthConfigChange xcWidthConfigChange = null;
/**
* 是否需要出发java逻辑调控事件
* 由于组件的初始化和界面的初始化是双线程的
* 所以组件测量时可能还没有完成事件监听的初始化
* 于是将事件触发逻辑下移到布局中,同时开启延迟触发机制
*/
private boolean isNeedDoJavaControlAction = false;
private Handler xcHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
if (xcWidthConfigChange == null) {
Log.e("jamie", "setXcWidthConfigChange---还没初始化");
xcHandler.sendEmptyMessageDelayed(1, JAVA_CONTROL_UPDATE_DELAY_TIME);
} else {
Log.e("jamie", "setXcWidthConfigChange---初始化完成");
xcWidthConfigChange.onWidthConfigChange(true);
}
return true;
}
});
public void setXcWidthConfigChange(WidthConfigChange xcWidthConfigChange) {
Log.e("jamie", "setXcWidthConfigChange---" + new Date().getTime());
this.xcWidthConfigChange = xcWidthConfigChange;
}
public XCAutoLinearLayout(@NonNull Context context) {
super(context);
xcContext = context;
mySelf = this;
}
public XCAutoLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initParams(context, attrs);
}
public XCAutoLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initParams(context, attrs);
}
/**
* 获取xml中属性值
*/
private void initParams(Context context, AttributeSet attrs) {
xcContext = context;
xcAttributeSet = attrs;
mySelf = this;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XCAutoLinearLayout);
if (typedArray != null) {
xcAutoType = typedArray.getInt(R.styleable.XCAutoLinearLayout_xc_auto_type, XC_AUTO_TYPE_NO);
xcAutoSelfWidth = px2dp(typedArray.getDimension(R.styleable.XCAutoLinearLayout_xc_auto_self_width, 0));
xcAutoChildLayout = typedArray.getResourceId(R.styleable.XCAutoLinearLayout_xc_auto_child_layout, 0);
Log.i("jamie", "xcAutoSelfWidth:" + xcAutoSelfWidth + "---xcAutoChildLayout:" + xcAutoChildLayout);
typedArray.recycle();
}
}
/**
* 在测量前,更改对应配置
*/
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (xcAutoType != XC_AUTO_TYPE_NO) {
final int width = RecyclerView.LayoutManager.chooseSize(widthSpec, getPaddingLeft() + getPaddingRight(), ViewCompat.getMinimumWidth(this));
final int height = RecyclerView.LayoutManager.chooseSize(heightSpec, getPaddingTop() + getPaddingBottom(), ViewCompat.getMinimumHeight(this));
if (width / xcAutoSelfWidth >= 2) {
doWidthAuto(width, height);
} else {
isDoAutoMatch = false;
isChildContainerShow = false;
isNeedDoJavaControlAction = false;
}
} else {
isDoAutoMatch = false;
isChildContainerShow = false;
isNeedDoJavaControlAction = false;
}
super.onMeasure(widthSpec, heightSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.e("jamie", "onLayout---" + new Date().getTime());
if (isNeedDoJavaControlAction) {
if (xcWidthConfigChange != null) {
xcWidthConfigChange.onWidthConfigChange(true);
} else {
xcHandler.sendEmptyMessageDelayed(1, JAVA_CONTROL_UPDATE_DELAY_TIME);
}
} else {
if (xcWidthConfigChange != null) {
xcWidthConfigChange.onWidthConfigChange(false);
}
}
super.onLayout(changed, l, t, r, b);
}
@Override
protected void onDraw(Canvas canvas) {
Log.e("jamie", "onDraw---" + new Date().getTime());
super.onDraw(canvas);
}
/**
* 对不同类型做分离
*/
private void doWidthAuto(int width, int height) {
switch (xcAutoType) {
case XC_AUTO_TYPE_CHILD_LAYOUT: {
childLayoutMatch(width, height);
break;
}
case XC_AUTO_TYPE_JAVA_CONTROL: {
isNeedDoJavaControlAction = true;
break;
}
case XC_AUTO_TYPE_AUTO_MATH: {
autoMatch(width, height);
break;
}
}
}
/**
* 组件自适应逻辑处理
*/
private void autoMatch(int width, int height) {
if (!isDoAutoMatch) {
LinearLayout.LayoutParams leftParams = new LinearLayout.LayoutParams(width / 2, height, 1);
LinearLayout leftLinear = new LinearLayout(xcContext, xcAttributeSet);
leftLinear.setLayoutParams(leftParams);
leftLinear.setOrientation(VERTICAL);
LinearLayout rightLinear = new LinearLayout(xcContext, xcAttributeSet);
rightLinear.setLayoutParams(leftParams);
rightLinear.setOrientation(VERTICAL);
int i = 0;
while (this.getChildCount() > 0) {
View tempView = this.getChildAt(0);
this.removeView(tempView);
if (i % 2 == 0) {
leftLinear.addView(tempView);
} else {
rightLinear.addView(tempView);
}
i++;
}
this.removeAllViews();
this.setOrientation(HORIZONTAL);
this.addView(leftLinear);
this.addView(rightLinear);
isDoAutoMatch = true;
}
}
/**
* 右侧子布局适配逻辑处理
*/
private void childLayoutMatch(int width, int height) {
if (xcAutoChildLayout != 0 && !isChildContainerShow) {
LayoutInflater inflater = (LayoutInflater) xcContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
xcAutoChildContainer = inflater.inflate(xcAutoChildLayout, null);
LinearLayout.LayoutParams leftParams = new LinearLayout.LayoutParams(width / 2, height, 1);
LinearLayout.LayoutParams rightParams = new LinearLayout.LayoutParams(width / 2, height, 1);
xcAutoChildContainer.setLayoutParams(rightParams);
LinearLayout tempLinear = new LinearLayout(xcContext, xcAttributeSet);
tempLinear.setLayoutParams(leftParams);
tempLinear.setOrientation(VERTICAL);
while (this.getChildCount() > 0) {
View tempView = this.getChildAt(0);
this.removeView(tempView);
tempLinear.addView(tempView);
}
this.setOrientation(HORIZONTAL);
this.addView(tempLinear);
this.addView(xcAutoChildContainer);
isChildContainerShow = true;
}
}
/**
* 将px值转换为dp值
*/
public int px2dp(float pxValue) {
final float scale = xcContext.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
public interface WidthConfigChange {
void onWidthConfigChange(boolean isDoubleWidht);
}
}