已实现垂直ListView,如何改为水平显示?适配API 15及以上
Hey there! I get that you're already using a ListView and want to switch from vertical to horizontal scrolling without immediately switching to RecyclerView. Since ListView doesn't have a built-in setLayoutManager method like RecyclerView does, here are two practical ways to achieve this while supporting API 15 and above:
Option 1: Custom HorizontalListView (Recommended for Performance)
This approach creates a custom ListView-like widget that handles horizontal layout and scrolling, preserving view recycling—critical for large datasets to avoid performance hits.
Step 1: Create the Custom HorizontalListView Class
public class HorizontalListView extends AbsListView { private int mScrollStartX; public HorizontalListView(Context context) { super(context); init(); } public HorizontalListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { setHorizontalScrollBarEnabled(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int parentWidth = MeasureSpec.getSize(widthMeasureSpec); int parentHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(parentWidth, parentHeight); // Prepare measure specs for child views int childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); int childHeightSpec = MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY); // Measure visible children int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { child.measure(childWidthSpec, childHeightSpec); } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int leftOffset = getScrollX(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { int childWidth = child.getMeasuredWidth(); child.layout(leftOffset, 0, leftOffset + childWidth, b - t); leftOffset += childWidth; } } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mScrollStartX = (int) event.getX(); return true; case MotionEvent.ACTION_MOVE: int deltaX = (int) (mScrollStartX - event.getX()); scrollBy(deltaX, 0); mScrollStartX = (int) event.getX(); return true; default: return super.onTouchEvent(event); } } }
Note: This is a simplified version—you can expand it with fling support, edge scrolling, and stricter view recycling logic for production use.
Step 2: Update Your Layout File
Replace your existing <ListView> with the custom widget (make sure to use your actual package name):
<com.yourpackage.HorizontalListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="wrap_content" />
Step 3: Adjust Your Adapter
You don’t need to rewrite your MyAdapter entirely—just tweak your list_item.xml to fit horizontal layout. For example, set a fixed width for the image and match the parent height:
<!-- list_item.xml --> <ImageView android:id="@+id/item_image" android:layout_width="140dp" android:layout_height="match_parent" android:scaleType="centerCrop" />
Option 2: HorizontalScrollView + LinearLayout (Quick & Simple for Small Datasets)
If your image list is small (no performance concerns), this approach avoids custom views but skips view recycling. It’s great for quick implementations.
Step 1: Update the Layout
<HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none"> <LinearLayout android:id="@+id/image_container" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" android:padding="8dp" /> </HorizontalScrollView>
Step 2: Bind Data in Your Activity
Instead of using an adapter, inflate each item and add it directly to the LinearLayout:
LinearLayout imageContainer = findViewById(R.id.image_container); LayoutInflater inflater = getLayoutInflater(); for (Drawable image : mListImages) { View itemView = inflater.inflate(R.layout.list_item, imageContainer, false); ImageView imageView = itemView.findViewById(R.id.item_image); imageView.setImageDrawable(image); imageContainer.addView(itemView); }
Bonus: Why Not Switch to RecyclerView?
You mentioned knowing about RecyclerView with a LinearLayoutManager(HORIZONTAL)—this is actually the most robust solution for API 15+. RecyclerView is backward-compatible via AndroidX or support-v7 libraries and handles view recycling, scrolling performance, and edge cases out of the box. If you can refactor your code, here’s a quick snippet:
Layout:
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" />
Activity Setup:
RecyclerView recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); recyclerView.setAdapter(new MyRecyclerAdapter(this, mListImages));
Your existing adapter logic can be adapted to extend RecyclerView.Adapter with minimal changes.
内容的提问来源于stack exchange,提问作者Maor Cohen

