PinnedHeaderListView滚动和标题问题

时间:2014-12-28 12:27:13

标签: android android-listview right-to-left pinned-header-list-view

背景

我试图模仿Lollipop的联系人应用程序显示联系人的固定标题的方式'第一封信,正如我写的关于here

问题

由于原始代码(找不到here,在" PinnedHeaderListViewSample"文件夹中)没有显示除英文字母以外的字母,我不得不稍微更改代码,但这还不够。标题本身也是如此,它现在必须在左边,而不是在行之上。

一切正常,直到我用RTL语言测试它(在我的情况下是希伯来语),而设备的语言环境也改为RTL语言(在我的情况下是希伯来语)。

出于某种原因,在滚动和标题本身中,事情变得非常奇怪,而奇怪的是它出现在Android的某些设备/版本上。

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

在使用Android 4.2.2的LG G2上,它也存在这个问题,但它也没有显示标题(固定标题除外),特别是希伯来语中没有。

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

简而言之,特殊情况是:

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

    代码量非常大,而且我已经制作了2个POC,而其中一个与我开始使用的代码(在Lollipop上看起来很像)完全不同。所以我会尝试显示最小数量。

    编辑:大型POC代码可在Github上获得,here

    " PinnedHeaderActivity.java"

    我已将2个希伯来语项目添加到顶部,进入"名称"字段:

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

    in" 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());
        }
    

    &#34; StringArrayAlphabetIndexer.java&#34;

    在&#34; getSectionForPosition&#34;方法,我已将其更改为:

    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;
        }
    }
    

    list_item.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>
    

    list_item_header.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" />
    

    这是2个截图,一个看起来不太好,另一个看起来不错:

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

    enter image description here

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

    enter image description here 出于某种原因,即使我已经在开发者选项中选择了它,Galaxy S4也没有像它应该的那样反映UI,所以它也可能是为什么它显示标题很好的原因。

    我尝试了什么

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

    更难的问题是要解决快速滚动条,这对于更复杂的POC来说真的很奇怪,而在简单的POC上有点奇怪(在靠近底部的地方快速滚动)。

    问题

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

    为什么RTL在此类UI中存在问题?

    编辑:似乎即使是Google的例子也无法在简单的ListView上处理RTL项目:

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

    当它有希伯来语联系人时,滚动条会疯狂地#34;

1 个答案:

答案 0 :(得分:2)

好的,我不知道谷歌在那里做了什么,因为代码非常难以阅读,所以我自己创建了类,它运行正常。

您必须记住的唯一事情是在将项目发送到我的课程之前对其进行排序,如果您希望标题只有大写字母,则必须相应地对项目进行排序(以便所有以a开头的项目)特定字母将在同一个块中,无论它是否为大写字母。

该解决方案可在GitHub上获得,此处: https://github.com/AndroidDeveloperLB/ListViewVariants

以下是代码:

<强> StringArrayAlphabetIndexer

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);
      }

    }


  }

SectionedSectionIndexer

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);
      }
    }

  }