Android中xml设android:visibility="gone"后调用setVisibility(View.VISIBLE)不生效如何解决
我在xml文件中给id为assess_list_layout的LinearLayout设置了android:visibility="gone"属性,之后在根视图course_adapter_layout的onClick()回调中将该布局的可见性改为View.VISIBLE,但操作不生效。代码前的Log可以正常打印,assess_list_layout的LinearLayout对象也不为空,将xml中的visibility属性改为invisible时操作可以正常生效,但我的需求是初始状态下布局为gone,点击后变为visible,符合应用设计要求。
补充信息
- 将
assess_list_layout.setVisibility(View.VISIBLE)放在onClick回调外时可以正常生效 - 已经尝试过以下方案均未解决问题:
- 调整LinearLayout组件的定义位置
- 调用父视图的
invalidate()方法 - 使用
runOnUiThread()包裹操作代码 - 查找
assess_list_layout时将findViewById的调用主体从view改为v(二者为同一对象,无效果) - 调用
assess_list_layout的requestLayout()方法
- 最新进展:已经实现点击
course_adapter_layout时显示assess_list_layout区域的功能,剩余两个问题:- 展开的视图不会额外占用屏幕空间,需要上下滚动才能查看完整内容
- 快速滚动列表时,视图会重置为应用启动时的初始状态
course_adapter_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/course_adapter_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:gravity="left" > <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="20dp" android:layout_marginTop="20dp" android:padding="15dp" android:elevation="2dp" android:background="@drawable/course_header_background"> <ImageView android:layout_width="20dp" android:layout_height="20dp" android:src="@drawable/course_color_circle"/> <Space android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="0.25"/> <TextView android:id="@+id/course_adapter_course_code" android:text="TextView1" android:layout_width="wrap_content" android:layout_height="20dp" android:layout_weight="0.5"/> <Space android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="0.25"/> <TextView android:id="@+id/course_adapter_course_title" android:text="TextView2" android:layout_width="wrap_content" android:layout_height="20dp"/> </LinearLayout> <LinearLayout android:id="@+id/assess_list_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_marginRight="40dp" android:layout_marginLeft="40dp" android:background="@drawable/course_body_background" android:padding="20dp" android:visibility="gone" > <ListView android:id="@+id/course_adapter_assess_list" android:layout_width="match_parent" android:layout_height="250dp" android:layout_marginBottom="10dp"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left" android:text="More" /> <Space android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left" android:text="New"/> </LinearLayout> </LinearLayout> </LinearLayout>
CourseListAdapter.java
package com.example.schoolplanner2.adapters; public class CourseListAdapter extends ArrayAdapter<Course> { private static final String TAG = "CourseListAdapter"; private Context context; int mResource; public CourseListAdapter(@NonNull Context context, int resource, @NonNull ArrayList<Course> objects) { super(context, resource, objects); this.context = context; mResource = resource; } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { // get info String course_code = getItem(position).getCourseCode(); Double course_grade = getItem(position).getCurrentGrade(); // make inflater and inflate the layout LayoutInflater inflater = LayoutInflater.from(context); View v = inflater.inflate(mResource, parent, false); TextView tv_course_code = v.findViewById(R.id.course_adapter_course_code); TextView tv_course_title = v.findViewById(R.id.course_adapter_course_title); tv_course_code.setText(course_code); tv_course_title.setText(String.valueOf(course_grade)); // add on click to each list view element LinearLayout layout = v.findViewById(R.id.course_adapter_layout); layout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.i(TAG, "List view element has been clicked " + course_code); // expand the view to include a new fragment LinearLayout assess_list_layout = view.findViewById(R.id.assess_list_layout); assess_list_layout.setVisibility(View.VISIBLE); // get the list view and add each course to the course view ListView assessment_list_view = (ListView) view.findViewById(R.id.course_adapter_assess_list); AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view, getItem(position).getAssessmentList(), getItem(position)); assessment_list_view.setAdapter(assessAdapter); } }); return v; } }
针对剩余两个问题的处理方案
1. 展开后视图不额外占用空间、只能滚动查看的问题
该问题是外层ListView的item高度测量逻辑导致:默认ListView不会自动重新测量已渲染item的高度,动态改变item内部布局可见性时,需要手动通知ListView重新测量当前item高度,在设置assess_list_layout.setVisibility(View.VISIBLE)后添加以下代码即可:
assess_list_layout.setVisibility(View.VISIBLE); // 新增代码触发item重新测量 v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); ViewGroup.LayoutParams params = v.getLayoutParams(); params.height = v.getMeasuredHeight(); v.setLayoutParams(params);
如果替换为 RecyclerView 实现列表,仅需要调用notifyItemChanged(position)即可自动更新item高度,无需手动测量。
2. 快速滚动列表时视图重置的问题
该问题是ListView的复用机制导致:滑出屏幕的item会被回收复用,点击时设置的可见性状态没有被持久化存储,复用时会回到xml定义的gone初始状态。
解决方法是给Course类新增状态存储字段,记录item的展开状态:
- 在Course类中新增
private boolean isExpanded = false;,同时添加对应的getter和setter方法 - 在
getView()方法中,根据当前Course对象的isExpanded状态设置布局可见性:
LinearLayout assess_list_layout = v.findViewById(R.id.assess_list_layout); ListView assessment_list_view = v.findViewById(R.id.course_adapter_assess_list); if (getItem(position).isExpanded()) { assess_list_layout.setVisibility(View.VISIBLE); AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view, getItem(position).getAssessmentList(), getItem(position)); assessment_list_view.setAdapter(assessAdapter); } else { assess_list_layout.setVisibility(View.GONE); }
- 在点击事件中更新状态并通知适配器刷新:
@Override public void onClick(View view) { Log.i(TAG, "List view element has been clicked " + course_code); getItem(position).setExpanded(!getItem(position).isExpanded()); notifyDataSetChanged(); }
优化建议
可以使用RecyclerView替换外层的ListView,RecyclerView复用机制更灵活,自带item局部刷新、过渡动画等能力,实现折叠展开效果的成本更低,也不会出现原生ListView的高度测量异常问题。
内容的提问来源于stack exchange,提问作者Seth Harlaar

