我有一个android布局,其中有一个带有许多元素的scrollView。在scrollView的底部,我有一个listView,然后由适配器填充。
我遇到的问题是,android是排除listView从scrollView作为scrollView已经有一个可滚动的功能。我希望listView和内容一样长,并且主滚动视图是可滚动的。
我怎样才能实现这种行为呢?
这是我的主要布局:
<ScrollView
android:id="@+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:fillViewport="true"
android:gravity="top" >
<LinearLayout
android:id="@+id/foodItemActvity_linearLayout_fragments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
然后,我以编程方式将我的组件添加到linearlayour的id: foodItemActvity_linearLayout_fragments。下面是加载到线性布局中的一个视图。就是这个给我的卷轴带来了麻烦。
<?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="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/fragment_dds_review_textView_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reviews:"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ListView
android:id="@+id/fragment_dds_review_listView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
我的适配器然后填充这个列表视图。
当我点击主scrollView时,这是一个来自android层级查看器的图像:
如您所见,它排除了评论列表视图。
我应该能够向下滚动页面,看到8个评论,但它只显示了这3个,我可以滚动评论所在的一小部分。我想要一个全局页面滚动
经过大量的研发后完成:
Fragment_one.xml应该是这样的:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollViewParent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="400dip" >
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:id="@+id/customView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/transparent" />
</RelativeLayout>
<!-- Your other elements are here -->
</LinearLayout>
</ScrollView>
FragmentOne.java的Java类如下所示:
private ListView listView;
private View customView
onCreateView
listView = (ListView) rootView.findViewById(R.id.listView);
scrollViewParent = (ScrollView)rootView.findViewById(R.id.scrollViewParent);
customView = (View)rootView.findViewById(R.id.customView);
customView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// Disallow ScrollView to intercept touch events.
scrollViewParent.requestDisallowInterceptTouchEvent(true);
// Disable touch on transparent view
return false;
case MotionEvent.ACTION_UP:
// Allow ScrollView to intercept touch events.
scrollViewParent.requestDisallowInterceptTouchEvent(false);
return true;
case MotionEvent.ACTION_MOVE:
scrollViewParent.requestDisallowInterceptTouchEvent(true);
return false;
default:
return true;
}
}
});
我有一个类似于原始海报的问题——如何让列表视图在滚动视图中滚动——这个答案解决了我的问题。
禁止滚动包含在ScrollView中的ListView
我没有调用新的片段到现有的布局或类似的东西,就像OP所做的那样,所以我的代码看起来像这样:
<ScrollView
android:id="@+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:fillViewport="true"
android:gravity="top" >
<LinearLayout
android:id="@+id/foodItemActvity_linearLayout_fragments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/fragment_dds_review_textView_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reviews:"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ListView
android:id="@+id/my_listView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
</ScrollView>
基本上我要做的是在我调用它之前检查listview的长度,当我调用它时,我把它变成那个长度。在你的java类中使用这个函数:
public static void justifyListViewHeightBasedOnChildren (ListView listView) {
ListAdapter adapter = listView.getAdapter();
if (adapter == null) {
return;
}
ViewGroup vg = listView;
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, vg);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams par = listView.getLayoutParams();
par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
listView.setLayoutParams(par);
listView.requestLayout();
}
然后像这样调用函数:
justifyListViewHeightBasedOnChildren(listView);
结果是一个没有滚动条的列表视图,显示整个列表视图的长度,它与滚动视图的滚动条一起滚动。
好吧,这是我的答案。固定ListView高度的方法足够封闭,但并不完美。如果大多数项目都是相同的高度,那工作得很好。但如果不是,那就有大问题了。我尝试了很多次,当我输出listItem的值时。getMeasureHeight和listItem。getMeasuerWidth到日志中,我看到宽度值变化很大,这在这里是不期望的,因为同一ListView中的所有项应该具有相同的宽度。问题来了:
有些人使用度量(0,0),这实际上使视图在两个方向上都没有边界,宽度也失控了。有些人尝试获取listView的twidth,但它返回0,没有意义。
当我进一步了解android如何渲染视图时,我意识到所有这些尝试都无法达到我搜索的答案,除非这些函数在视图渲染后运行。
这一次我使用getViewTreeObserver在ListView上,我想固定高度,然后添加ongloballayoutlistener。在这个方法中,我声明了一个新的OnGlobalLayoutListener,在这个方法中,getWidth返回ListView的实际宽度。
private void getLayoutWidth(final ListView lv, final int pad){
//final ArrayList<Integer> width = new ArrayList<Integer>();
ViewTreeObserver vto = lv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
lv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//width.add(layout.getMeasuredWidth());
int width = lv.getMeasuredWidth();
ListUtils.setDynamicHeight(lv, width, pad);
}
});
}
public static class ListUtils {
//private static final int UNBOUNDED = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
public static void setDynamicHeight(ListView mListView, int width, int pad) {
ListAdapter mListAdapter = mListView.getAdapter();
mListView.getParent();
if (mListAdapter == null) {
// when adapter is null
return;
}
int height = 0;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(width - 2*pad, View.MeasureSpec.EXACTLY);
for (int i = 0; i < mListAdapter.getCount(); i++) {
View listItem = mListAdapter.getView(i, null, mListView);
listItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
//listItem.measure(UNBOUNDED, UNBOUNDED);
height += listItem.getMeasuredHeight() + 2*pad;
Log.v("ViewHeight :", mListAdapter.getClass().toString() + " " + listItem.getMeasuredHeight() + "--" + listItem.getMeasuredWidth());
}
ViewGroup.LayoutParams params = mListView.getLayoutParams();
params.height = height + (mListView.getDividerHeight() * (mListAdapter.getCount() - 1));
mListView.setLayoutParams(params);
mListView.requestLayout();
}
}
value pad,是我在ListView layout中设置的填充。
经过大量的研发后完成:
Fragment_one.xml应该是这样的:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollViewParent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="400dip" >
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:id="@+id/customView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/transparent" />
</RelativeLayout>
<!-- Your other elements are here -->
</LinearLayout>
</ScrollView>
FragmentOne.java的Java类如下所示:
private ListView listView;
private View customView
onCreateView
listView = (ListView) rootView.findViewById(R.id.listView);
scrollViewParent = (ScrollView)rootView.findViewById(R.id.scrollViewParent);
customView = (View)rootView.findViewById(R.id.customView);
customView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// Disallow ScrollView to intercept touch events.
scrollViewParent.requestDisallowInterceptTouchEvent(true);
// Disable touch on transparent view
return false;
case MotionEvent.ACTION_UP:
// Allow ScrollView to intercept touch events.
scrollViewParent.requestDisallowInterceptTouchEvent(false);
return true;
case MotionEvent.ACTION_MOVE:
scrollViewParent.requestDisallowInterceptTouchEvent(true);
return false;
default:
return true;
}
}
});
如果出于某种原因你不想使用addHeaderView和addFooterView,例如当你有几个列表时,一个好主意是重用ListAdapter来填充一个简单的线性布局,这样就没有滚动功能了。
如果你已经从ListFragment中获得了一个完整的片段,并且想用简单的线性布局将其转换为一个类似的片段,而不需要滚动(例如,将它放在ScrollView中),你可以像这样实现一个适配器片段:
// converts listFragment to linearLayout (no scrolling)
// please call init() after fragment is inflated to set listFragment to convert
public class ListAsArrayFragment extends Fragment {
public ListAsArrayFragment() {}
private ListFragment mListFragment;
private LinearLayout mRootView;
// please call me!
public void init(Activity activity, ListFragment listFragment){
mListFragment = listFragment;
mListFragment.onAttach(activity);
mListFragment.getListAdapter().registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
refreshView();
}
});
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// create an empty vertical LinearLayout as the root view of this fragment
mRootView = new LinearLayout(getActivity());
mRootView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mRootView.setOrientation(LinearLayout.VERTICAL);
return mRootView;
}
// reusing views for performance
// todo: support for more than one view type
ArrayList<View> mViewsToReuse = new ArrayList<>();
ArrayList<View> mCurrentViews = new ArrayList<>();
// re-add views to linearLayout
void refreshView(){
// remove old views from linearLayout and move them to mViewsToReuse
mRootView.removeAllViews();
mViewsToReuse.addAll(mCurrentViews);
mCurrentViews.clear();
// create new views
for(int i=0; i<mListFragment.getListAdapter().getCount(); ++i){
View viewToReuse = null;
if(!mViewsToReuse.isEmpty()){
viewToReuse = mViewsToReuse.get(mViewsToReuse.size()-1);
mViewsToReuse.remove(mViewsToReuse.size()-1);
}
final View view = mListFragment.getListAdapter().getView(i, viewToReuse, mRootView);
ViewGroup.LayoutParams oldParams = view.getLayoutParams();
view.setLayoutParams(new LinearLayout.LayoutParams(oldParams.width, oldParams.height));
final int finalI = i;
// pass click events to listFragment
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mListFragment.onListItemClick(null, view, finalI, finalI);
}
});
mRootView.addView(view);
mCurrentViews.add(view);
}
}
你也可以根据你的需要转发onCreate, onPause, onResume等到原始片段或尝试继承而不是组合(但覆盖某些方法,因此原始片段实际上没有附加到布局层次结构);但是我想尽可能地分离原始片段,因为我们只需要提取它的ListAdapter。如果你在onAttach中调用原始片段的setListAdapter,这可能就足够了。
下面是如何使用ListAsArrayFragment包含OriginalListFragment而不滚动。在父活动的onCreate中:
ListAsArrayFragment fragment = (ListAsArrayFragment) getFragmentManager().findFragmentById(R.id.someFragmentId);
OriginalListFragment originalFragment = new OriginalListFragment();
fragment.init(this, originalFragment);
// now access originalFragment.getListAdapter() to modify list entries
// and remember to call notifyDatasetChanged()