循环列表视图(半圆形项目)

9

我正在尝试制作一个环形ListView,其中列表项排列在半圆上。它应该看起来像这样:

enter image description here

有一个相关帖子,但它已经关闭了。

我自己制作了一个圆形的自定义ListView,它运行良好,但我的问题是我无法像图片上显示的那样将列表项排列成半圆形。我尝试了几种方法,但都没有用,我不知道该怎么做。


你为什么认为这是一个“ListView”? - CommonsWare
这可能不是一个ListView,我只是有很多项的列表需要像这样排列它们,但我不知道怎么做……它不一定要是一个ListView。 - Jilberta
这应该像ListView一样可以滚动吗? - a.bertucci
是的,它应该像ListView那样可滚动。 - Jilberta
4个回答

19

因此,当我制作演示样例应用程序时,我需要做两件事。

首先,我要编辑自定义视图上的 onDraw(Canvas)。这个视图可以是任何视图,但为了简单起见,我选择了一个 TextView。这使我能够根据方程式将视图向左或向右推动。

public class MyView extends TextView {

    private static final int MAX_INDENT = 300;
    private static final String TAG = MyView.class.getSimpleName();

    public MyView(Context context) {
        super(context);
    }

    public void onDraw(Canvas canvas){
        canvas.save();
        float indent = getIndent(getY());
        canvas.translate(indent, 0);
        super.onDraw(canvas);
        canvas.restore();
    }

    public float getIndent(float distance){
        float x_vertex = MAX_INDENT;
        DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
        float y_vertex = displayMetrics.heightPixels / 2 / displayMetrics.density;
        double a = ( 0 - x_vertex ) / ( Math.pow(( 0 - y_vertex), 2) ) ;
        float indent = (float) (a * Math.pow((distance - y_vertex), 2) + x_vertex);
        return indent;
    }
}

我需要做的第二件事是覆盖ListView类,使其实现OnScrollListener并调用setOnScrollListener(this);。现在我能够滚动列表,它会按照我在视图中设置的方程式进行跟随。

public class HalfCircleListView extends ListView implements AbsListView.OnScrollListener {
    public HalfCircleListView(Context context) {
        super(context);
        setOnScrollListener(this);
    }


    @Override
    public void onScrollStateChanged(AbsListView absListView, int i) {
        //Ignored
    }

    @Override
    public void onScroll(AbsListView absListView, int i, int i2, int i3) {
        absListView.invalidateViews();
    }
}
您可以从我的Gist中下载完整的源代码。

初始状态 视图的初始状态 滚动状态 滚动到底部

您可以看到我的数学有些错误……我使用了一个抛物线而不是圆形,所以这将需要更改。


我遇到了错误。在getY()函数处。 - Harshid
我可以把这个视图放在屏幕底部而不是左边吗? - shivani
看起来在使用ViewGroup而不是View时它无法工作,如果我用RelativeLayout替换TextView,项目就不再缩进了。这种行为可能是什么原因引起的? - Egor
@Egor,这可能是由于视图组的显示方式。IIR ViewGroups不会调用onDraw方法。我不记得如何解决这个问题,但应该不难。 - Ethan
@Ethan 这个视图可以放在 XML 布局的任何位置吗? - Erum
显示剩余8条评论

1
你可以在adapter的getView()方法中增加/减少每个视图的左边距。例如,对于前一半的视图,可以通过增加20像素(int margin = index * 20)来增加每个项的边距,并相应地减少后一半视图的边距。当然,这需要进行大量的微调才能真正看起来像一个循环列表,但我想你已经明白了这个想法。

是的,我已经尝试过这种方式,但是出现了问题。当适配器首次使用getView创建一个项目视图时,它会创建一个视图,但随后它会使用先前使用的视图,并且你无法确定它将使用哪个视图,因此它看起来很混乱。 - Jilberta
@Jilberta,这是一个什么样的问题?您根据传递给getView()方法的位置设置边距,每次执行该方法时都会进行设置。请向我们展示您的代码,我们可能能够解决您的问题。 - TomTasche
@Jilberta,你说得对,你从recycleview扩展的listview会重用视图,所以如果以某种方式重复使用它们,它们可能看起来会很乱。但是请记住,如果它们引起混乱,你也可以设置http://developer.android.com/reference/android/widget/AbsListView.html#setRecyclerListener(android.widget.AbsListView.RecyclerListener) 来澄清事情。最好的。 - Tom

1
我会尽可能使用现有的径向菜单小部件。 这个 声称支持半圆形径向菜单。我不确定 这个 是否支持。

没有截图在文档中,我怎么想象视图? - Himanshu Mori

0
经过更多的搜索,我找到了一个解决方案。
它相对优化,并且像普通的ListView一样可配置。
以下是主要代码片段:

CircularListView.java

package com.makotokw.android.widget;

import android.annotation.TargetApi;

import android.content.Context;

import android.database.DataSetObserver;

import android.os.Build;

import android.util.AttributeSet;

import android.view.KeyEvent;

import android.view.View;

import android.view.ViewGroup;

import android.widget.AbsListView;

import android.widget.ListAdapter;

import android.widget.ListView;

public class CircularListView extends ListView implements AbsListView.OnScrollListener {

    private static final String TAG = CircularListView.class.getSimpleName();

    private static final int REPEAT_COUNT = 3;

    private int mItemHeight = 0;

    private CircularListViewListener mCircularListViewListener;

    private InfiniteListAdapter mInfiniteListAdapter;

    private boolean mEnableInfiniteScrolling = true;

    private CircularListViewContentAlignment mCircularListViewContentAlignment = CircularListViewContentAlignment.Left;

    private double mRadius = -1;

    private int mSmoothScrollDuration = 80;

    public CircularListView(Context context) {
        this(context, null);
    }

    public CircularListView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.listViewStyle);
    }

    public CircularListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        setOnScrollListener(this);
        setClipChildren(false);
        setEnableInfiniteScrolling(true);
    }

    public void setAdapter(ListAdapter adapter) {
        mInfiniteListAdapter = new InfiniteListAdapter(adapter);
        mInfiniteListAdapter.setEnableInfiniteScrolling(mEnableInfiniteScrolling);
        super.setAdapter(mInfiniteListAdapter);
    }

    public CircularListViewListener getCircularListViewListener() {
        return mCircularListViewListener;
    }

    public void setCircularListViewListener(CircularListViewListener circularListViewListener) {
        this.mCircularListViewListener = circularListViewListener;
    }

    public void setEnableInfiniteScrolling(boolean enableInfiniteScrolling) {
        mEnableInfiniteScrolling = enableInfiniteScrolling;
        if (mInfiniteListAdapter != null) {
            mInfiniteListAdapter.setEnableInfiniteScrolling(enableInfiniteScrolling);
        }
        if (mEnableInfiniteScrolling) {
            setHorizontalScrollBarEnabled(false);
            setVerticalScrollBarEnabled(false);
        }
    }

    public CircularListViewContentAlignment getCircularListViewContentAlignment() {
        return mCircularListViewContentAlignment;
    }

    public void setCircularListViewContentAlignment(
            CircularListViewContentAlignment circularListViewContentAlignment) {
        if (mCircularListViewContentAlignment != circularListViewContentAlignment) {
            mCircularListViewContentAlignment = circularListViewContentAlignment;
            requestLayout();
        }
    }

    public double getRadius() {
        return mRadius;
    }

    public void setRadius(double radius) {
        if (this.mRadius != radius) {
            this.mRadius = radius;
            requestLayout();
        }
    }

    public int getCentralPosition() {
        double vCenterPos = getHeight() / 2.0f;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child != null) {
                if (child.getTop() <= vCenterPos
                        && child.getTop() + child.getHeight() >= vCenterPos) {
                    return getFirstVisiblePosition() + i;
                }
            }
        }
        return -1;
    }

    public View getCentralChild() {
        int pos = getCentralPosition();
        if (pos != -1) {
            return getChildAt(pos - getFirstVisiblePosition());
        }
        return null;
    }

    public void scrollFirstItemToCenter() {
        if (!mEnableInfiniteScrolling) {
            return;
        }

        int realTotalItemCount = mInfiniteListAdapter.getRealCount();
        if (realTotalItemCount > 0) {
            setSelectionFromTop(realTotalItemCount, getBaseCentralChildTop());
        }
    }

    public int getBaseCentralChildTop() {
        int itemHeight  = getItemHeight();
        if (itemHeight > 0) {
            return getHeight() / 2 - itemHeight / 2;
        }
        return 0;
    }

    public int getItemHeight() {
        if (mItemHeight == 0) {
            View child = getChildAt(0);
            if (child != null) {
                mItemHeight = child.getHeight();
            }
        }
        return mItemHeight;
    }

    public void setSelectionAndMoveToCenter(int position) {
        if (!mEnableInfiniteScrolling) {
            return;
        }

        int realTotalItemCount = mInfiniteListAdapter.getRealCount();
        if (realTotalItemCount == 0) {
            return;
        }

        position = position % realTotalItemCount;
        int centralPosition = getCentralPosition() % realTotalItemCount;

        int y = getBaseCentralChildTop();
        if (centralPosition == position) {
            View centralView = getCentralChild();
            y = centralView.getTop();
        }
        setSelectionFromTop(position + realTotalItemCount, y);
    }

    @TargetApi(Build.VERSION_CODES.FROYO)
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mEnableInfiniteScrolling) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_UP:
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
                            smoothScrollBy(mItemHeight, mSmoothScrollDuration);
                            return true;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
                            smoothScrollBy(-mItemHeight, mSmoothScrollDuration);
                            return true;
                        }
                        break;
                    default:
                        break;

                }
            }
        }
        return super.dispatchKeyEvent(event);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            if (!isInTouchMode()) {
                setSelectionAndMoveToCenter(getCentralPosition());
            }
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                         int totalItemCount) {
        if (!mEnableInfiniteScrolling) {
            return;
        }

        View itemView = this.getChildAt(0);
        if (itemView == null) {
            return;
        }

        int realTotalItemCount = mInfiniteListAdapter.getRealCount();
        if (realTotalItemCount == 0) {
            return;
        }

        if (mItemHeight == 0) {
            mItemHeight = itemView.getHeight();
        }

        if (firstVisibleItem == 0) {
            // scroll one unit
            this.setSelectionFromTop(realTotalItemCount, itemView.getTop());
        }

        if (totalItemCount == firstVisibleItem + visibleItemCount) {
            // back one unit
            this.setSelectionFromTop(firstVisibleItem - realTotalItemCount,
                    itemView.getTop());
        }

        if (mCircularListViewContentAlignment != CircularListViewContentAlignment.None) {

            double viewHalfHeight = view.getHeight() / 2.0f;

            double vRadius = view.getHeight();
            double hRadius = view.getWidth();

            double yRadius = (view.getHeight() + mItemHeight) / 2.0f;
            double xRadius = (vRadius < hRadius) ? vRadius : hRadius;
            if (mRadius > 0) {
                xRadius = mRadius;
            }

            for (int i = 0; i < visibleItemCount; i++) {
                itemView = this.getChildAt(i);
                if (itemView != null) {
                    double y = Math.abs(viewHalfHeight - (itemView.getTop() + (itemView.getHeight() / 2.0f)));
                    y = Math.min(y, yRadius);
                    double angle = Math.asin(y / yRadius);
                    double x = xRadius * Math.cos(angle);

                    if (mCircularListViewContentAlignment == CircularListViewContentAlignment.Left) {
                        x -= xRadius;
                    } else {
                        x = xRadius / 2 - x;
                    }
                    itemView.scrollTo((int) x, 0);
                }
            }
        } else {
            for (int i = 0; i < visibleItemCount; i++) {
                itemView = this.getChildAt(i);
                if (itemView != null) {
                    itemView.scrollTo(0, 0);
                }
            }
        }

        if (mCircularListViewListener != null) {
            mCircularListViewListener.onCircularLayoutFinished(this, firstVisibleItem, visibleItemCount, totalItemCount);
        }
    }

    class InfiniteListAdapter implements ListAdapter {

        private boolean mEnableInfiniteScrolling = true;

        private ListAdapter mCoreAdapter;

        public InfiniteListAdapter(ListAdapter coreAdapter) {
            mCoreAdapter = coreAdapter;
        }

        private void setEnableInfiniteScrolling(boolean enableInfiniteScrolling) {
            mEnableInfiniteScrolling = enableInfiniteScrolling;
        }

        public int getRealCount() {
            return mCoreAdapter.getCount();
        }

        public int positionToIndex(int position) {
            int count = mCoreAdapter.getCount();
            return (count == 0) ? 0 : position % count;
        }

        @Override
        public void registerDataSetObserver(DataSetObserver observer) {
            mCoreAdapter.registerDataSetObserver(observer);
        }

        @Override
        public void unregisterDataSetObserver(DataSetObserver observer) {
            mCoreAdapter.unregisterDataSetObserver(observer);
        }

        @Override
        public int getCount() {
            int count = mCoreAdapter.getCount();
            return (mEnableInfiniteScrolling) ? count * REPEAT_COUNT : count;
        }

        @Override
        public Object getItem(int position) {
            return mCoreAdapter.getItem(this.positionToIndex(position));
        }

        @Override
        public long getItemId(int position) {
            return mCoreAdapter.getItemId(this.positionToIndex(position));
        }

        @Override
        public boolean hasStableIds() {
            return mCoreAdapter.hasStableIds();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return mCoreAdapter.getView(this.positionToIndex(position), convertView, parent);
        }

        @Override
        public int getItemViewType(int position) {
            return mCoreAdapter.getItemViewType(this.positionToIndex(position));
        }

        @Override
        public int getViewTypeCount() {
            return mCoreAdapter.getViewTypeCount();
        }

        @Override
        public boolean isEmpty() {
            return mCoreAdapter.isEmpty();
        }

        @Override
        public boolean areAllItemsEnabled() {
            return mCoreAdapter.areAllItemsEnabled();
        }

        @Override
        public boolean isEnabled(int position) {
            return mCoreAdapter.isEnabled(this.positionToIndex(position));
        }
    }
}


**CircularListViewContentAlignment.java**

package com.makotokw.android.widget;

public enum CircularListViewContentAlignment {

    None,
    Left,
    Right
}

**CircularListViewListener.java**

package com.makotokw.android.widget;

public interface CircularListViewListener {


    void onCircularLayoutFinished(CircularListView circularListView,
                                  int firstVisibleItem,
                                  int visibleItemCount,
                                  int totalItemCount);
}

如需更多澄清,您也可以查看我的博客文章并在那里发表评论。您可以下载Eclipse的示例应用程序。
我的博客是:
http://androidpantiii.blogspot.in/2015/11/half-circular-list-view-there-were.html


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