我试图模仿Lollipop的联系人应用程序显示联系人的固定标题的方式'第一封信,正如我写的关于here。
由于原始代码(找不到here,在" PinnedHeaderListViewSample"文件夹中)没有显示除英文字母以外的字母,我不得不稍微更改代码,但这还不够。标题本身也是如此,它现在必须在左边,而不是在行之上。
一切正常,直到我用RTL语言测试它(在我的情况下是希伯来语),而设备的语言环境也改为RTL语言(在我的情况下是希伯来语)。
出于某种原因,在滚动和标题本身中,事情变得非常奇怪,而奇怪的是它出现在Android的某些设备/版本上。
例如,在带有Kitkat的Galaxy S3上,滚动和滚动条完全错误(我滚动到顶部,但滚动条的位置在中间)。
在使用Android 4.2.2的LG G2上,它也存在这个问题,但它也没有显示标题(固定标题除外),特别是希伯来语中没有。
在Galaxy S4和Huwawei Ascend P7(都运行Kitkat)上,无论我做什么,一切都很好。
简而言之,特殊情况是:
代码量非常大,而且我已经制作了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 - 不显示希伯来语标题,并且在底部附近进行奇怪的滚动(与滚动的其余部分相比,非常快到底):
Galaxy S4 kitkat - 显示标题很好,但底部的滚动很奇怪:
出于某种原因,即使我已经在开发者选项中选择了它,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;
答案 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&§ions.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);
}
}
}