PinnedHeaderListView 滚动和标题问题

2024-05-22

背景

我正在尝试模仿 Lollipop 的联系人应用程序显示联系人首字母的固定标题的方式,正如我所写的here https://stackoverflow.com/q/27621425/878126.

问题

由于原始代码(发现here http://code.google.com/p/android-playground/source/checkout,在“PinnedHeaderListViewSample”文件夹中)不显示除英文以外的字母,我不得不稍微更改代码,但这还不够。标题本身也是如此,它现在必须位于左侧,而不是位于行上方。

一切工作正常,直到我在 RTL 语言(在我的例子中是希伯来语)上对其进行了测试,同时设备的区域设置也更改为 RTL 语言(在我的例子中是希伯来语)。

由于某种原因,滚动和标题本身都变得非常奇怪,奇怪的是它发生在某些设备/Android 版本上。

例如,在带有 Kitkat 的 Galaxy S3 上,滚动和滚动条完全错误(我滚动到顶部,但滚动条的位置在中间)。

在搭载 Android 4.2.2 的 LG G2 上,也存在此问题,但它也不会显示标题(固定标题除外),尤其是希伯来语的标题。

在 Galaxy S4 和华为 Ascend P7(均运行 Kitkat)上,无论我做什么,一切都运行良好。

简而言之,特殊场景是:

  1. 使用 pinnedHeaderListView
  2. 让设备使用 RTL 语言环境,或者通过开发人员设置进行操作
  3. 有英语和希伯来语的列表视图项目
  4. 设置 listView 显示快速滚动条。
  5. 使用快速滚动器滚动 listView 或像不使用快速滚动器一样滚动。

The code

代码量很大,而且我做了2个POC,其中一个和我一开始的代码有很大不同(为了让它看起来像在Lollipop上)。所以我会尝试显示最少的金额。

编辑:大的 POC 代码可以在 Github 上找到,here https://github.com/AndroidDeveloperLB/ListViewVariants .

“PinnedHeaderActivity.java”

我在顶部的“名称”字段中添加了 2 个希伯来语项目:

        "אאא",
        "בבב",

在“setupListView”方法中,我使快速滚动条可见:

    listView.setFastScrollEnabled(true);

在“NamesAdapter”CTOR 中,我让它支持的不仅仅是英文字母:

    public NamesAdapter(Context context, int resourceId, int textViewResourceId, String[] objects) {
        super(context, resourceId, textViewResourceId, objects);
        final SortedSet<Character> set = new TreeSet<Character>();
        for (final String string : objects) {
            final String trimmed = string == null ? "" : string.trim();
            if (!TextUtils.isEmpty(trimmed))
                set.add(Character.toUpperCase(trimmed.charAt(0)));
            else
                set.add(' ');
        }
        final StringBuilder sb = new StringBuilder();
        for (final Character character : set)
            sb.append(character);
        this.mIndexer = new StringArrayAlphabetIndexer(objects, sb.toString());
    }

“StringArrayAlphabetIndexer.java”

在“getSectionForPosition”方法中,我将其更改为:

public int getSectionForPosition(int position) {
    try {
        if (mArray == null || mArray.length == 0)
            return 0;
        final String curName = mArray[position];
        // Linear search, as there are only a few items in the section index
        // Could speed this up later if it actually gets used.
        // TODO use binary search
        for (int i = 0; i < mAlphabetLength; ++i) {
            final char letter = mAlphabet.charAt(i);
            if (TextUtils.isEmpty(curName) && letter == ' ')
                return i;
            final String targetLetter = Character.toString(letter);
            if (compare(curName, targetLetter) == 0)
                return i;
        }
        return 0; // Don't recognize the letter - falls under zero'th section
    } catch (final Exception ex) {
        return 0;
    }
}

列表项.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <include layout="@layout/list_item_header" />

        <include
            layout="@android:layout/simple_list_item_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="50dp" />
    </FrameLayout>

    <View
        android:id="@+id/list_divider"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="@android:drawable/divider_horizontal_dark" />

</LinearLayout>

列表项标题.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/header_text"
    android:layout_width="25dip"
    android:layout_height="25dip"
    android:textStyle="bold"
    android:background="@color/pinned_header_background"
    android:textColor="@color/pinned_header_text"
    android:textSize="14sp"
    android:paddingLeft="6dip"
    android:gravity="center" />

这里有两张截图,一张看起来不太好,另一张看起来还不错:

Galaxy S3 kitkat 和 LG G2 4.2.2 - 不显示希伯来语标题,并且在底部附近有奇怪的滚动(与滚动的其余部分相比,到达底部的速度非常快):

Galaxy S4 kitkat - 显示标题很好,但底部的滚动很奇怪:

enter image description here For some reason, the Galaxy S4 didn't mirror the UI as it should, even though I've chosen it on the developer options, so it might also be the reason for why it showed the headers fine.

我尝试过的

除了尝试我制作的 2 个 POC(一个与 Material Design 风格更相似并且更复杂)之外,我还尝试了各种使用布局的方法,并且还尝试使用 LayoutDirection 来强制要显示的标题。

更难的问题是解决快速滚动条,它在更复杂的 POC 上工作起来非常奇怪,在简单的 POC 上工作起来有点奇怪(在底部附近快速滚动)。

问题

解决这些问题的正确方法是什么?

为什么 RTL 对于这种类型的 UI 存在问题?

编辑:似乎即使是 Google 的示例也不能在简单的 ListView 上很好地处理 RTL 项目:

http://developer.android.com/training/contacts-provider/retrieve-names.html

当它有希伯来语联系人时,滚动条就会变得“疯狂”。


好吧,我不知道谷歌在那里做了什么,因为代码非常不可读,所以我创建了自己的类,并且它工作得很好。

您必须记住的唯一一件事是在将项目发送到我的班级之前对项目进行排序,如果您希望标题仅包含大写字母,则必须对项目进行相应的排序(以便所有以特定字母开头的项目都将位于同一块中,无论是否大写)。

该解决方案可在 GitHub 上找到:https://github.com/AndroidDeveloperLB/ListViewVariants https://github.com/AndroidDeveloperLB/ListViewVariants

这是代码:

字符串数组字母索引器

public class StringArrayAlphabetIndexer extends SectionedSectionIndexer
  {
  /**
   * @param items                   each of the items. Note that they must be sorted in a way that each chunk will belong to
   *                                a specific header. For example, chunk with anything that starts with "A"/"a", then a chunk
   *                                that all of its items start with "B"/"b" , etc...
   * @param useOnlyUppercaseHeaders whether the header will be in uppercase or not.
   *                                if true, you must order the items so that each chunk will have its items start with either the lowercase or uppercase letter
   */
  public StringArrayAlphabetIndexer(String[] items,boolean useOnlyUppercaseHeaders)
    {
    super(createSectionsFromStrings(items,useOnlyUppercaseHeaders));
    }

  private static SimpleSection[] createSectionsFromStrings(String[] items,boolean useOnlyUppercaseHeaders)
    {
    //get all of the headers of the sections and their sections-items:
    Map<String,ArrayList<String>> headerToSectionItemsMap=new HashMap<String,ArrayList<String>>();
    Set<String> alphabetSet=new TreeSet<String>();
    for(String item : items)
      {
      String firstLetter=TextUtils.isEmpty(item)?" ":useOnlyUppercaseHeaders?item.substring(0,1).toUpperCase(Locale.getDefault()):
          item.substring(0,1);
      ArrayList<String> sectionItems=headerToSectionItemsMap.get(firstLetter);
      if(sectionItems==null)
        headerToSectionItemsMap.put(firstLetter,sectionItems=new ArrayList<String>());
      sectionItems.add(item);
      alphabetSet.add(firstLetter);
      }
    //prepare the sections, and also sort each section's items :
    SimpleSection[] sections=new SimpleSection[alphabetSet.size()];
    int i=0;
    for(String headerTitle : alphabetSet)
      {
      ArrayList<String> sectionItems=headerToSectionItemsMap.get(headerTitle);
      SimpleSection simpleSection=new AlphaBetSection(sectionItems);
      simpleSection.setName(headerTitle);
      sections[i++]=simpleSection;
      }
    return sections;
    }

  public static class AlphaBetSection extends SimpleSection
    {
    private ArrayList<String> items;

    private AlphaBetSection(ArrayList<String> items)
      {
      this.items=items;
      }

    @Override
    public int getItemsCount()
      {
      return items.size();
      }

    @Override
    public String getItem(int posInSection)
      {
      return items.get(posInSection);
      }

    }


  }

分段索引器

public class SectionedSectionIndexer implements SectionIndexer {
    private final SimpleSection[] mSectionArray;

    public SectionedSectionIndexer(final SimpleSection[] sections) {
        mSectionArray = sections;
        //
        int previousIndex = 0;
        for (int i = 0; i < mSectionArray.length; ++i) {
            mSectionArray[i].startIndex = previousIndex;
            previousIndex += mSectionArray[i].getItemsCount();
            mSectionArray[i].endIndex = previousIndex - 1;
        }
    }

    @Override
    public int getPositionForSection(final int section) {
        final int result = section < 0 || section >= mSectionArray.length ? -1 : mSectionArray[section].startIndex;
        return result;
    }

    /** given a flat position, returns the position within the section */
    public int getPositionInSection(final int flatPos) {
        final int sectionForPosition = getSectionForPosition(flatPos);
        final SimpleSection simpleSection = mSectionArray[sectionForPosition];
        return flatPos - simpleSection.startIndex;
    }

    @Override
    public int getSectionForPosition(final int flatPos) {
        if (flatPos < 0)
            return -1;
        int start = 0, end = mSectionArray.length - 1;
        int piv = (start + end) / 2;
        while (true) {
            final SimpleSection section = mSectionArray[piv];
            if (flatPos >= section.startIndex && flatPos <= section.endIndex)
                return piv;
            if (piv == start && start == end)
                return -1;
            if (flatPos < section.startIndex)
                end = piv - 1;
            else
                start = piv + 1;
            piv = (start + end) / 2;
        }
    }

    @Override
    public SimpleSection[] getSections() {
        return mSectionArray;
    }

    public Object getItem(final int flatPos) {
        final int sectionIndex = getSectionForPosition(flatPos);
        final SimpleSection section = mSectionArray[sectionIndex];
        final Object result = section.getItem(flatPos - section.startIndex);
        return result;
    }

    public Object getItem(final int sectionIndex, final int positionInSection) {
        final SimpleSection section = mSectionArray[sectionIndex];
        final Object result = section.getItem(positionInSection);
        return result;
    }

    public int getRawPosition(final int sectionIndex, final int positionInSection) {
        final SimpleSection section = mSectionArray[sectionIndex];
        return section.startIndex + positionInSection;
    }

    public int getItemsCount() {
        if (mSectionArray.length == 0)
            return 0;
        return mSectionArray[mSectionArray.length - 1].endIndex + 1;
    }

    // /////////////////////////////////////////////
    // Section //
    // //////////
    public static abstract class SimpleSection {
        private String name;
        private int startIndex, endIndex;

        public SimpleSection() {
        }

        public SimpleSection(final String sectionName) {
            this.name = sectionName;
        }

        public String getName() {
            return name;
        }

        public void setName(final String name) {
            this.name = name;
        }

        public abstract int getItemsCount();

        public abstract Object getItem(int posInSection);

  @Override
  public String toString()
    {
    return name;
    }
  }

}

BasePinnedHeaderListViewAdapter

public abstract class BasePinnedHeaderListViewAdapter extends BaseAdapter implements SectionIndexer, OnScrollListener,
    PinnedHeaderListView.PinnedHeaderAdapter
  {
    private SectionIndexer _sectionIndexer;
    private boolean mHeaderViewVisible = true;

    public void setSectionIndexer(final SectionIndexer sectionIndexer) {
        _sectionIndexer = sectionIndexer;
    }

    /** remember to call bindSectionHeader(v,position); before calling return */
    @Override
    public abstract View getView(final int position, final View convertView, final ViewGroup parent);

    public abstract CharSequence getSectionTitle(int sectionIndex);

    protected void bindSectionHeader(final TextView headerView, final View dividerView, final int position) {
        final int sectionIndex = getSectionForPosition(position);
        if (getPositionForSection(sectionIndex) == position) {
            final CharSequence title = getSectionTitle(sectionIndex);
            headerView.setText(title);
            headerView.setVisibility(View.VISIBLE);
            if (dividerView != null)
                dividerView.setVisibility(View.GONE);
        } else {
            headerView.setVisibility(View.GONE);
            if (dividerView != null)
                dividerView.setVisibility(View.VISIBLE);
        }
        // move the divider for the last item in a section
        if (dividerView != null)
            if (getPositionForSection(sectionIndex + 1) - 1 == position)
                dividerView.setVisibility(View.GONE);
            else
                dividerView.setVisibility(View.VISIBLE);
        if (!mHeaderViewVisible)
            headerView.setVisibility(View.GONE);
    }

    @Override
    public int getPinnedHeaderState(final int position) {
        if (_sectionIndexer == null || getCount() == 0 || !mHeaderViewVisible)
            return PINNED_HEADER_GONE;
        if (position < 0)
            return PINNED_HEADER_GONE;
        // The header should get pushed up if the top item shown
        // is the last item in a section for a particular letter.
        final int section = getSectionForPosition(position);
        final int nextSectionPosition = getPositionForSection(section + 1);
        if (nextSectionPosition != -1 && position == nextSectionPosition - 1)
            return PINNED_HEADER_PUSHED_UP;
        return PINNED_HEADER_VISIBLE;
    }

    public void setHeaderViewVisible(final boolean isHeaderViewVisible) {
        mHeaderViewVisible = isHeaderViewVisible;
    }

    public boolean isHeaderViewVisible() {
        return this.mHeaderViewVisible;
    }

    @Override
    public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
            final int totalItemCount) {
        ((PinnedHeaderListView) view).configureHeaderView(firstVisibleItem);
    }

    @Override
    public void onScrollStateChanged(final AbsListView arg0, final int arg1) {
    }

    @Override
    public int getPositionForSection(final int sectionIndex) {
        if (_sectionIndexer == null)
            return -1;
        return _sectionIndexer.getPositionForSection(sectionIndex);
    }

    @Override
    public int getSectionForPosition(final int position) {
        if (_sectionIndexer == null)
            return -1;
        return _sectionIndexer.getSectionForPosition(position);
    }

    @Override
    public Object[] getSections() {
        if (_sectionIndexer == null)
            return new String[] { " " };
        return _sectionIndexer.getSections();
    }

    @Override
    public long getItemId(final int position) {
        return position;
    }
}

IndexedPinnedHeaderListViewAdapter

public abstract class IndexedPinnedHeaderListViewAdapter extends BasePinnedHeaderListViewAdapter
  {
  private int _pinnedHeaderBackgroundColor;
  private int _pinnedHeaderTextColor;

  public void setPinnedHeaderBackgroundColor(final int pinnedHeaderBackgroundColor)
    {
    _pinnedHeaderBackgroundColor=pinnedHeaderBackgroundColor;
    }

  public void setPinnedHeaderTextColor(final int pinnedHeaderTextColor)
    {
    _pinnedHeaderTextColor=pinnedHeaderTextColor;
    }

  @Override
  public CharSequence getSectionTitle(final int sectionIndex)
    {
    return getSections()[sectionIndex].toString();
    }

  @Override
  public void configurePinnedHeader(final View v,final int position,final int alpha)
    {
    final TextView header=(TextView)v;
    final int sectionIndex=getSectionForPosition(position);
    final Object[] sections=getSections();
    if(sections!=null&&sections.length!=0)
      {
      final CharSequence title=getSectionTitle(sectionIndex);
      header.setText(title);
      }
    if(VERSION.SDK_INT<VERSION_CODES.HONEYCOMB)
      if(alpha==255)
        {
        header.setBackgroundColor(_pinnedHeaderBackgroundColor);
        header.setTextColor(_pinnedHeaderTextColor);
        }
      else
        {
        header.setBackgroundColor(Color.argb(alpha,Color.red(_pinnedHeaderBackgroundColor),
            Color.green(_pinnedHeaderBackgroundColor),Color.blue(_pinnedHeaderBackgroundColor)));
        header.setTextColor(Color.argb(alpha,Color.red(_pinnedHeaderTextColor),
            Color.green(_pinnedHeaderTextColor),Color.blue(_pinnedHeaderTextColor)));
        }
    else
      {
      header.setBackgroundColor(_pinnedHeaderBackgroundColor);
      header.setTextColor(_pinnedHeaderTextColor);
      header.setAlpha(alpha/255.0f);
      }
    }

  }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PinnedHeaderListView 滚动和标题问题 的相关文章

随机推荐