如何创建一个封闭的(圆形)ListView?

Posted

技术标签:

【中文标题】如何创建一个封闭的(圆形)ListView?【英文标题】:How to create a closed (circular) ListView? 【发布时间】:2011-01-20 22:18:48 【问题描述】:

我想创建一个自定义的 ListView(或类似的),它的行为就像一个封闭的(圆形):

    向下滚动 - 到达最后一项后,第一项开始(.., n-1, n, 1, 2, ..) 向上滚动 - 到达第一项后,最后一项开始(.., 2, 1, n, n-1, ..)

这在概念上听起来很简单,但显然没有直接的方法可以做到这一点。 谁能指出我正确的解决方案? 谢谢!

我已经收到了一个答案(来自 Streets Of Boston 在 android-Developers google groups 上),但听起来有点难看 :) -

我通过创建自己的来做到这一点 列表适配器(子类从 BaseAdapter)。

我用这样的方式编写了我自己的列表适配器 它的 getCount() 方法返回的方式 一个巨大的数字。

如果选择了项目“x”,那么这个 item 对应于适配器 position='adapter.getCount()/2+x'

对于我的适配器的方法 getItem(int position),我看看我的 备份适配器的阵列和 获取索引上的项目: (position-getCount()/2) % myDataItems.length

你需要做一些更“特别”的事情 使一切正常工作的东西, 但你明白了。

原则上还是可以的 到达终点或起点 列表,但如果您将 getCount() 设置为 大约一百万左右,这很难 要做:-)

【问题讨论】:

先生。如果您坚持使用 AdapterView 层次结构中的类(例如,ListView),波士顿的解决方案是唯一可用的选择。 我不坚持任何类型的课程。我提到 ListView 只是为了让您了解控件的行为和外观,如果需要,您也可以将其称为“表格”。一些非常自定义的东西可能是一组形成列表的单元格,当其中一个单元格超出可见区域时 - 引擎将更新(我认为来自不同的线程)它的位置(坐标)和内容(文本 + 图像) .但是这个更新过程,可能会影响滚动的流畅度。 我应该在哪里写 position=adapter.getcount()/2+x? 【参考方案1】:

我的同事 Joe,我相信我们已经找到了一种更简单的方法来解决同样的问题。在我们的解决方案中,我们扩展了 ArrayAdapter,而不是扩展 BaseAdapter。

代码如下:

public class CircularArrayAdapter< T > extends ArrayAdapter< T >
   

        public static final int HALF_MAX_VALUE = Integer.MAX_VALUE/2;
        public final int MIDDLE;
        private T[] objects;

        public CircularArrayAdapter(Context context, int textViewResourceId, T[] objects)
        
            super(context, textViewResourceId, objects);
            this.objects = objects;
            MIDDLE = HALF_MAX_VALUE - HALF_MAX_VALUE % objects.length;
        

        @Override
        public int getCount()
        
            return Integer.MAX_VALUE;
        

        @Override
        public T getItem(int position) 
        
            return objects[position % objects.length];
        
 

因此,这将创建一个名为 CircularArrayAdapter 的类,它采用对象类型 T(可能是任何东西)并使用它来创建数组列表。 T 通常是一个字符串,尽管可以是任何东西。

构造函数与 ArrayAdapter 的构造函数相同,但初始化了一个名为 middle 的常量。这是列表的中间部分。无论数组 MIDDLE 的长度是多少,都可用于将 ListView 置于列表中间。

getCount() 被覆盖以返回一个巨大的值,就像上面创建一个巨大的列表一样。

getItem() 被覆盖以返回数组上的假位置。因此,在填充列表时,列表以循环方式填充对象。

此时 CircularArrayAdapter 只是替换了创建 ListView 的文件中的 ArrayAdapter。

为了使 ListView 居中,必须在初始化 ListView 对象后创建 ListView 的文件中插入闲置行:

listViewObject.setSelectionFromTop(nameOfAdapterObject.MIDDLE, 0);

并使用先前为列表初始化的 MIDDLE 常量,视图以屏幕顶部的列表顶部项目为中心。

: ) ~ 干杯,我希望这个解决方案有用。

【讨论】:

这应该是解决方案。这非常简单并且完美无瑕。谢谢 @Dawson: 到达顶部时项目是否会更新(即从下到上滚动)。再次到达第一个项目后会在顶部显示最后一个项目吗? 我刚刚遇到了这个解决方案,坦率地说,代码的简单程度令人惊讶。赞一个! @prodaea 他们自己管理。如果我在数组中有 19 个项目并滚动到列表的底部,因此我的索引为 0,一切都很好。当我向下滚动一个附加项目时,我的索引为 -1,-1 % 19 在 java 中计算为 18,这是我列表中的最后一个元素。在循环列表中,最后一个元素在逻辑上在第一个元素之前,因此我们很好。考虑到这一点,应将 MIDDLE 设置为 0,使其成为 Integer.MIN_VALUE 和 Integer.MAX_VALUE 之间的中点。我很高兴你提出这个问题,因为我没有考虑过这个案例。 我不认为这是一个好主意,适配器的使用是它仅在需要时在内存中创建元素,在这里您使用整个元素列表初始化适配器,那么它不是适配器不是吗?它只是一个经理,只是在需要时传递数据。【参考方案2】:

您提到的解决方案是我过去告诉其他开发人员使用的解决方案。在 getCount() 中,简单地返回 Integer.MAX_VALUE,它会给你大约 20 亿个项目,应该足够了。

【讨论】:

感谢您的回答。休数解:) @Romain Guy:使用休数解决方案会在尝试大量数据时产生性能问题。 @Surej 我相信你试图暗示返回一个“巨大的数字”会影响性能。它不会; a brief review of the source 揭示了 ListViews 实际上并没有完全根据这个数字分配任何对象。我不确定的唯一例外是使用布局模式LAYOUT_FORCE_BOTTOM,因为它是自下而上填充的,但我没有详细查看。 @PaulLammertsma 从底部进行布局也不会影响性能。 @RomainGuy 奇怪的错误-Integer.MAX_VALUE 不起作用(getView 未调用)但 Integer.MAX_VALUE - 2 按预期工作。知道为什么吗?【参考方案3】:

根据上面的答案,我有,或者我认为我做得对。 希望这会帮助你。

private static class RecipeListAdapter extends BaseAdapter 
    private static LayoutInflater   mInflater;
    private Integer[]               mCouponImages;
    private static ImageView        viewHolder;
    public RecipeListAdapter(Context c, Integer[] coupomImages) 
        RecipeListAdapter.mInflater = LayoutInflater.from(c);
        this.mCouponImages = coupomImages;
    
    @Override
    public int getCount() 
        return Integer.MAX_VALUE;
    

    @Override
    public Object getItem(int position) 
       // you can do your own tricks here. to let it display the right item in your array.
        return position % mCouponImages.length;
    

    @Override
    public long getItemId(int position) 
        return position;
        // return position % mCouponImages.length;
    

    @Override
    public View getView(int position, View convertView, ViewGroup parent) 

        if (convertView == null) 
            convertView = mInflater.inflate(R.layout.coupon_list_item, null);
            viewHolder = (ImageView) convertView.findViewById(R.id.item_coupon);
            convertView.setTag(viewHolder);
         else 
            viewHolder = (ImageView) convertView.getTag();
        

        viewHolder.setImageResource(this.mCouponImages[position %     mCouponImages.length]);
        return convertView;
    


如果您想向下滚动列表,您也愿意这样做。 通常我们可以向上滚动并列出然后向下滚动。

// 看看我们想要滚动多少项目。在这种情况下,Integer.MAX_VALUE

int listViewLength = adapter.getCount();

// see how many items a screen can dispaly, I use variable "span"
    final int span = recipeListView.getLastVisiblePosition() - recipeListView.getFirstVisiblePosition();

//看看我们有多少页

int howManySpans = listViewLength / span;

// 启动列表视图时看看你想在哪里。你不必做“-3”的东西。 这是为了让我的应用正常工作。

recipeListView.setSelection((span * (howManySpans / 2)) - 3);

【讨论】:

【参考方案4】:

我可以看到一些很好的答案,我的一个friend 试图通过一个简单的解决方案来实现这一点。检查github 项目。

【讨论】:

我们如何创建双向循环列表视图,向上滚动时应显示最后一项。你能给我任何提示吗?【参考方案5】:

如果使用 LoadersCallbacks,我创建了 MyCircularCursor 类,它像这样包装典型的光标:

@Override
public void onLoadFinished(Loader<Cursor> pCursorLoader, Cursor pCursor) 
        mItemListAdapter.swapCursor(new MyCircularCursor(pCursor));

装饰器类代码在这里:

public class MyCircularCursor implements Cursor 

private Cursor mCursor;

public MyCircularCursor(Cursor pCursor) 
    mCursor = pCursor;


@Override
public int getCount() 
    return mCursor.getCount() == 0 ? 0 : Integer.MAX_VALUE;


@Override
public int getPosition() 
    return mCursor.getPosition();


@Override
public boolean move(int pOffset) 
    return mCursor.move(pOffset);


@Override
public boolean moveToPosition(int pPosition) 
    int position = MathUtils.mod(pPosition, mCursor.getCount());
    return mCursor.moveToPosition(position);


@Override
public boolean moveToFirst() 
    return mCursor.moveToFirst();


@Override
public boolean moveToLast() 
    return mCursor.moveToLast();


@Override
public boolean moveToNext() 
    if (mCursor.isLast()) 
        mCursor.moveToFirst();
        return true;
     else 
        return mCursor.moveToNext();
    


@Override
public boolean moveToPrevious() 
    if (mCursor.isFirst()) 
        mCursor.moveToLast();
        return true;
     else 
        return mCursor.moveToPrevious();
    


@Override
public boolean isFirst() 
    return false;


@Override
public boolean isLast() 
    return false;


@Override
public boolean isBeforeFirst() 
    return false;


@Override
public boolean isAfterLast() 
    return false;


@Override
public int getColumnIndex(String pColumnName) 
    return mCursor.getColumnIndex(pColumnName);


@Override
public int getColumnIndexOrThrow(String pColumnName) throws IllegalArgumentException 
    return mCursor.getColumnIndexOrThrow(pColumnName);


@Override
public String getColumnName(int pColumnIndex) 
    return mCursor.getColumnName(pColumnIndex);


@Override
public String[] getColumnNames() 
    return mCursor.getColumnNames();


@Override
public int getColumnCount() 
    return mCursor.getColumnCount();


@Override
public byte[] getBlob(int pColumnIndex) 
    return mCursor.getBlob(pColumnIndex);


@Override
public String getString(int pColumnIndex) 
    return mCursor.getString(pColumnIndex);


@Override
public short getShort(int pColumnIndex) 
    return mCursor.getShort(pColumnIndex);


@Override
public int getInt(int pColumnIndex) 
    return mCursor.getInt(pColumnIndex);


@Override
public long getLong(int pColumnIndex) 
    return mCursor.getLong(pColumnIndex);


@Override
public float getFloat(int pColumnIndex) 
    return mCursor.getFloat(pColumnIndex);


@Override
public double getDouble(int pColumnIndex) 
    return mCursor.getDouble(pColumnIndex);


@Override
public int getType(int pColumnIndex) 
    return 0;


@Override
public boolean isNull(int pColumnIndex) 
    return mCursor.isNull(pColumnIndex);


@Override
public void deactivate() 
    mCursor.deactivate();


@Override
@Deprecated
public boolean requery() 
    return mCursor.requery();


@Override
public void close() 
    mCursor.close();


@Override
public boolean isClosed() 
    return mCursor.isClosed();


@Override
public void registerContentObserver(ContentObserver pObserver) 
    mCursor.registerContentObserver(pObserver);


@Override
public void unregisterContentObserver(ContentObserver pObserver) 
    mCursor.unregisterContentObserver(pObserver);


@Override
public void registerDataSetObserver(DataSetObserver pObserver) 
    mCursor.registerDataSetObserver(pObserver);


@Override
public void unregisterDataSetObserver(DataSetObserver pObserver) 
    mCursor.unregisterDataSetObserver(pObserver);


@Override
public void setNotificationUri(ContentResolver pCr, Uri pUri) 
    mCursor.setNotificationUri(pCr, pUri);


@Override
public boolean getWantsAllOnMoveCalls() 
    return mCursor.getWantsAllOnMoveCalls();


@Override
public Bundle getExtras() 
    return mCursor.getExtras();


@Override
public Bundle respond(Bundle pExtras) 
    return mCursor.respond(pExtras);


@Override
public void copyStringToBuffer(int pColumnIndex, CharArrayBuffer pBuffer) 
    mCursor.copyStringToBuffer(pColumnIndex, pBuffer);


【讨论】:

以上是关于如何创建一个封闭的(圆形)ListView?的主要内容,如果未能解决你的问题,请参考以下文章

如何创建一个随机封闭平滑CGPath?

Flutter Listview语言选择需要在选择时进行圆形复选框设计

Android基础——快速开发之打造万能适配器

如何模仿棒棒糖联系人应用程序上的 listView 粘性项目?

如何将轮廓层次结构从 python openCV 转换为 emgu cv 以查找封闭轮廓

单击 ListView 上的侦听器