Android中的横向ListView?

220

能否让 ListView 水平显示?我已经使用了一个画廊视图来实现此功能,但是选定的项会自动移动到屏幕中央。我不想让选定的项停留在我点击的同一位置。有什么解决办法吗?我的想法是将 ListView 设置为水平滚动。请分享您的想法?


1
请使用此链接创建水平ListView:https://dev59.com/questions/1n3aa4cB1Zd3GeqPidSx,希望能对您有所帮助。 - Faiz Anwar
2
@Indra,你提到的那个博客已经被删除了。 - Animesh Mangla
您可以查看此示例http://thedeveloperworldisyours.com/android/horizontal-listview-on-android/。 - Cabezas
19个回答

127
根据Android文档,RecyclerView是在列表视图中组织项目并水平显示的新方法。 优点:
  1. 通过使用Recyclerview适配器,ViewHolder模式会自动实现
  2. 易于执行动画
  3. 还有许多其他特性
RecyclerView的更多信息:
  1. grokkingandroid.com
  2. antonioleiva.com

示例:

survivingwithandroid.com

只需添加以下代码块,即可使 ListView 从垂直变为水平

代码片段

LinearLayoutManager layoutManager= new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL, false);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(layoutManager);

1
如果LayoutManager的问题被Google解决,这将是最佳解决方案。https://code.google.com/p/android/issues/detail?id=74772 - Ponsuyambu
要构建一个简单的回收视图,请使用以下示例:http://www.android-examples.com/android-simple-recyclerview-example-tutorial/ - Matcoil
2
好的,小部件(widgets)的方式是什么? - Vladislav
与ListView或GridView等其他适配器支持的视图不同,RecyclerView允许客户端代码为子视图提供自定义布局排列。这些排列由RecyclerView.LayoutManager控制。必须为RecyclerView提供LayoutManager才能使其正常工作。 - Yohanim
RecyclerView仍然不能像ListView一样处理项目上的点击(不要提及RecyclerView.Selection类)那么好和容易,这就是为什么我认为RecyclerView不是最好的答案之一的少数原因。 - Edward Quixote
问题是关于ListView的,但回答提供了Recycle View的内容,我认为我们应该坚持回答问题。 - Sachin Puri

60

Paul不费心修复他的库中的错误,也不接受用户提供的修复。因此,我建议使用另一个功能类似的库:

https://github.com/sephiroth74/HorizontalVariableListView

更新:在2013年7月24日,作者(sephiroth74)基于android 4.2.2 ListView的代码发布了完全重写的版本。我必须说,它没有之前版本中存在的所有错误,并且运行良好!


2
这应该是一个被接受的答案,因为HorizontalVariableListView比Paul的更先进。例如,它有一个适当的方法来选择列表中的位置,而Paul的则用“TODO:某天实现”代替。 - Yan.Yurkin
5
请注意,此解决方案需要 Android 2.3 或更高版本。 - WSBT
1
sephiroth74的HorizontalVariableListView正是你所期望的:它是来自Android源代码的ListView的副本,其中所有方法都已更新为水平移动而不是垂直移动。我还没有遇到任何问题。非常感谢你指引我去那里,Malachiasz! - SilithCrowe
这个库仍然不支持将wrap_content应用于ListView。 - Kalpesh Lakhani
有人能够弄清如何使用wrap_content来设置layout_height吗?当使用wrap_content时,它会搞乱一切。 - Snake

28

@Paul的答案提供了一个很好的解决方案,但是此代码不允许在项目子元素上使用onClickListeners(回调函数从未被调用)。我已经苦苦挣扎了一段时间,终于找到了解决方案,并决定在这里发布修改后的代码(以防有人需要)。

不要覆盖dispatchTouchEvent方法,而是覆盖onTouchEvent方法。 使用与dispatchTouchEvent相同的代码并删除该方法(您可以在此http://developer.android.com/guide/topics/ui/ui-events.html#EventHandlers了解两者之间的区别)

@Override
public boolean onTouchEvent(MotionEvent event) {
    boolean handled = mGesture.onTouchEvent(event);
    return handled;
}

接下来,添加以下代码,该代码将决定从项子元素那里夺取该事件并将其交给我们的onTouchEvent处理,或者让它由它们处理。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch( ev.getActionMasked() ){
        case MotionEvent.ACTION_DOWN:
             mInitialX = ev.getX();
             mInitialY = ev.getY();             
             return false;
        case MotionEvent.ACTION_MOVE:
             float deltaX = Math.abs(ev.getX() - mInitialX);
             float deltaY = Math.abs(ev.getY() - mInitialY);
             return ( deltaX > 5 || deltaY > 5 );
        default:
             return super.onInterceptTouchEvent(ev);
    }
}

最后,不要忘记在你的类中声明变量:

private float mInitialX;
private float mInitialY;

13
为什么不将这个作为拉取请求发送给Paul呢? - Cristian
我有这些跳跃,如果我使用dispatchTouchEvent代码将无法工作。必须使用onTouchEvent!你所说的解决方案是什么,请帮忙。 - hasan
嗨,有没有解决跳跃问题的消息?我认为AdapterView本身会尝试在滚动开始时重新定位可见屏幕部分的项目。如何禁用这个功能? - ticofab
@Xavi Gil,我在使用水平列表视图时遇到了问题。我正在使用Paul库,它的工作正常,但是当我快速滚动时,它会卡在右侧,甚至无法滚动。请建议我该怎么办? - rupesh
我根据你的建议修改了我的代码,但是当我触摸文本时,我的setOnTouchListener被调用了10次。我不知道为什么会出现这种情况。 - Faiz Anwar
显示剩余5条评论

23
自从Google推出了Android Support Library v7 21.0.0版本,您可以使用RecyclerView来水平滚动项目。 RecyclerView小部件是ListView的更高级和灵活的版本。
要使用RecyclerView,只需添加依赖项:
com.android.support:recyclerview-v7:23.0.1

这是一个示例:

public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);

        MyAdapter adapter = new MyAdapter(myDataset);
        recyclerView.setAdapter(adapter);
    }
}

有关RecyclerView的更多信息:


20

这篇文章有点晚(很晚),但我会发布它以便以后有人可以看到。

截至Android L预览版,支持库中有一个RecyclerView恰好符合你的需求。

现在,你只能通过L preview SDK获取它,并且需要将minSdk设置为L。但是,你可以将所有必要的文件复制到你的项目中,并以这种方式使用它们,直到L正式发布。

你可以在这里下载预览文档。

注意:RecyclerView 的 API 可能会发生更改,也可能存在漏洞。

更新

水平列表视图的源代码如下:

LinearLayoutManager layoutManager
    = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);

RecyclerView myList = findViewById(R.id.my_recycler_view);
myList.setLayoutManager(layoutManager);

你好,它能正常工作吗?因为我尝试了一下,但是我无法创建一个水平的RecyclerView。你能提供一些代码吗?谢谢。 - Cocorico
只需设置LinearLayoutManager - 参见此处:https://dev59.com/Jl4b5IYBdhLWcg3w-1-c - Stéphane
但是将自定义视图绑定到它时,会有很多空白空间,宽度不是Wrap Content,在CustomView项目后面有很多空白空间。有没有解决方案? - Jithin Angel'z Jas'z

19

这里下载jar文件。

现在将其放入您的libs文件夹中,右键单击它并选择“作为库添加”。

现在在main.xml中放置此代码。

 <com.devsmart.android.ui.HorizontalListView
    android:id="@+id/hlistview"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />

现在如果您想在Activity类中使用带图像的水平列表视图,则可以使用以下代码:

  HorizontalListView hListView = (HorizontalListView) findViewById(R.id.hlistview);
    hListView.setAdapter(new HAdapter(this));


 private class HAdapter extends BaseAdapter {

    LayoutInflater inflater;

    public HAdapter(Context context) {
        inflater = LayoutInflater.from(context);

    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return Const.template.length;
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        HViewHolder holder;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.listinflate, null);
            holder = new HViewHolder();
            convertView.setTag(holder);

        } else {
            holder = (HViewHolder) convertView.getTag();
        }
        holder.img = (ImageView) convertView.findViewById(R.id.image);
        holder.img.setImageResource(Const.template[position]);
        return convertView;
    }

}

class HViewHolder {
    ImageView img;
}

它不支持任何选择器或自定义选择器?有什么想法吗? - Nitin Misra
我可以将数字数组相加,而不是图像吗? - souravlahoti
将其放入libs文件夹后,右键单击它,然后点击“添加为库”。 - M. Usman Khan
如何开发类似于Pulse、BBC News中的HorizontalListView列表,请帮忙。 - Harsha
@Harsha 你需要自定义它。 - Siddhpura Amit
显示剩余2条评论

15

其实非常简单:只需将列表视图旋转到侧面

mlistView.setRotation(-90);

然后,在填充子项时(应该在getView方法中),将子项旋转回原位:

 mylistViewchild.setRotation(90);

编辑:如果旋转后您的ListView不合适,请将ListView放置在此RotateLayout中,像这样:

 <com.github.rongi.rotate_layout.layout.RotateLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:angle="90"> <!-- Specify rotate angle here -->

    <ListView
       android:layout_width="match_parent"
       android:layout_height="match_parent">
    </ListView>
</com.github.rongi.rotate_layout.layout.RotateLayout>

2
我已经尝试过了 - 不是那么简单。我没能成功将旋转视图适配到屏幕的宽度和高度上。而且这个API只在新设备上可用。 - Malachiasz
1
你一直在尝试你所说的吗?我已经尝试过了,但都不起作用。 - Malachiasz
这个库的新版本(https://dev59.com/8nA75IYBdhLWcg3wipmH#16589629)已经完美地完成了它应该做的工作,并且可以在Android 2.3上运行,而setRotation()自3.0以来就可用,所以我不想再去尝试旋转,但我鼓励你自己尝试并呈现出可行的代码。 - Malachiasz
旋转本身可能很简单,但解决方案却远非直截了当。它对我也没起作用。 - Melllvar
从来没想过解决方案会这么简单。对我有用。 - Rahul
显示剩余3条评论

9
注意:Android现在支持使用RecyclerView实现水平列表视图,因此此答案已过时。有关RecyclerView的信息,请访问以下链接:https://developer.android.com/reference/android/support/v7/widget/RecyclerView

我开发了一种逻辑来实现它,而不使用任何外部水平滚动库,这是我实现的水平视图,并在此处发布了我的答案:https://stackoverflow.com/a/33301582/5479863

我的JSON响应是:

{"searchInfo":{"status":"1","message":"Success","clist":[{"id":"1de57434-795e-49ac-0ca3-5614dacecbd4","name":"Theater","image_url":"http://52.25.198.71/miisecretory/category_images/movie.png"},{"id":"62fe1c92-2192-2ebb-7e92-5614dacad69b","name":"CNG","image_url":"http://52.25.198.71/miisecretory/category_images/cng.png"},{"id":"8060094c-df4f-5290-7983-5614dad31677","name":"Wine-shop","image_url":"http://52.25.198.71/miisecretory/category_images/beer.png"},{"id":"888a90c4-a6b0-c2e2-6b3c-561788e973f6","name":"Chemist","image_url":"http://52.25.198.71/miisecretory/category_images/chemist.png"},{"id":"a39b4ec1-943f-b800-a671-561789a57871","name":"Food","image_url":"http://52.25.198.71/miisecretory/category_images/food.png"},{"id":"c644cc53-2fce-8cbe-0715-5614da9c765f","name":"College","image_url":"http://52.25.198.71/miisecretory/category_images/college.png"},{"id":"c71e8757-072b-1bf4-5b25-5614d980ef15","name":"Hospital","image_url":"http://52.25.198.71/miisecretory/category_images/hospital.png"},{"id":"db835491-d1d2-5467-a1a1-5614d9963c94","name":"Petrol-Pumps","image_url":"http://52.25.198.71/miisecretory/category_images/petrol.png"},{"id":"f13100ca-4052-c0f4-863a-5614d9631afb","name":"ATM","image_url":"http://52.25.198.71/miisecretory/category_images/atm.png"}]}}

布局文件:

    <?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"
    android:weightSum="5">    
    <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="4" />
    <HorizontalScrollView
        android:id="@+id/horizontalScroll"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <LinearLayout
            android:id="@+id/ll"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="horizontal">    
        </LinearLayout>
    </HorizontalScrollView>
</LinearLayout>

类文件:

LinearLayout linearLayout = (LinearLayout) findViewById(R.id.ll);
        for (int v = 0; v < collectionInfo.size(); v++) {
            /*---------------Creating frame layout----------------------*/

            FrameLayout frameLayout = new FrameLayout(ActivityMap.this);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, getPixelsToDP(90));
            layoutParams.rightMargin = getPixelsToDP(10);
            frameLayout.setLayoutParams(layoutParams);

            /*--------------end of frame layout----------------------------*/

            /*---------------Creating image view----------------------*/
            final ImageView imgView = new ImageView(ActivityMap.this); //create imageview dynamically
            LinearLayout.LayoutParams lpImage = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            imgView.setImageBitmap(collectionInfo.get(v).getCatImage());
            imgView.setLayoutParams(lpImage);
            // setting ID to retrieve at later time (same as its position)
            imgView.setId(v);
            imgView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    // getting id which is same as its position
                    Log.i(TAG, "Clicked on " + collectionInfo.get(v.getId()).getCatName());
                    // getting selected category's data list
                    new GetSelectedCategoryData().execute(collectionInfo.get(v.getId()).getCatID());
                }
            });
            /*--------------end of image view----------------------------*/

            /*---------------Creating Text view----------------------*/
            TextView textView = new TextView(ActivityMap.this);//create textview dynamically
            textView.setText(collectionInfo.get(v).getCatName());
            FrameLayout.LayoutParams lpText = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER);
            // Note: LinearLayout.LayoutParams 's gravity was not working so I putted Framelayout as 3 paramater is gravity itself
            textView.setTextColor(Color.parseColor("#43A047"));
            textView.setLayoutParams(lpText);
            /*--------------end of Text view----------------------------*/

            //Adding views at appropriate places
            frameLayout.addView(imgView);
            frameLayout.addView(textView);
            linearLayout.addView(frameLayout);

        }

 private int getPixelsToDP(int dp) {
        float scale = getResources().getDisplayMetrics().density;
        int pixels = (int) (dp * scale + 0.5f);
        return pixels;
    }

这里使用的技巧是给ImageView分配了一个id,例如"imgView.setId(v)",然后再对其应用onClickListner,接着再获取视图的id...我也在代码中添加了注释以便更易理解。希望这对你有所帮助...编程愉快... :)

http://i.stack.imgur.com/lXrpG.png


1
我已经编辑并发布了完整的解释,感谢您的建议 :) - Abdul Aziz
你好,感谢你的代码。你能发一下 getPixelsToDP() 方法吗? - Skizo-ozᴉʞS ツ
@Skizo 我已经在 Java 代码的末尾添加了那个方法,请看一下。 - Abdul Aziz

9
我的解决方案是简单地使用 ViewPager 小部件。它不像 Gallery 一样被锁定在中心位置,并且内置了回收视图的特性(就像 ListView)。你可能会在 Google Play 应用中看到类似的方法,每当你处理水平滚动列表时。
你只需要扩展 PagerAdapter 并在那里执行一些调整。
public class MyPagerAdapter extends PagerAdapter {

    private Context mContext;

    public MyPagerAdapter(Context context) {
        this.mContext = context;
    }

    // As per docs, you may use views as key objects directly 
    // if they aren't too complex
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        View view = inflater.inflate(R.layout.item, null);
        container.addView(view);
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
    }

    @Override
    public int getCount() {
        return 10;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    // Important: page takes all available width by default,
    // so let's override this method to fit 5 pages within single screen
    @Override
    public float getPageWidth(int position) {
        return 0.2f;
    }
}

因此,您将拥有一个带有适配器的横向可滚动小部件,就像这样:enter image description here

3
它可以水平滚动,就像 ListView 一样。唯一的区别是:它逐一滚动项目,所以不能将滚动位置停在下一个项目的中间。但这就是 ViewPager 的工作方式。 - fraggjkee

6

3
可以。但如果我有大量的项目,则无法使用适配器等内容和点击事件。 - Praveen
尝试一下这个:http://indrapatel.blogspot.in/2013/10/horizontal-listview-in-android.html - Indra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接