Создание среднего элемента для застревания в заголовке (ScrollView/ListView)
Я хочу сделать элемент, который показан в середине ScrollView
(или ListView
) первым, а затем застревает в заголовке экрана при прокрутке.
Его прототипная реализация в CSS + JS: http://jsfiddle.net/minhee/aPcv4/embedded/result/.
На первый взгляд я бы включил ScrollView
ListView
, но в официальных документах говорится:
Вы никогда не должны использовать ScrollView с ListView, потому что ListView позаботится о своей собственной вертикальной прокрутке. Самое главное, что это приводит к поражению всех важных оптимизаций в ListView для работы с большими списками, поскольку оно эффективно заставляет ListView отображать весь свой список элементов, чтобы заполнить бесконечный контейнер, предоставленный ScrollView.
Итак, какие подходы я могу попытаться достичь этого пользовательского интерфейса?
Обновление. Я пробовал StickyListHeaders, но: "в настоящее время невозможно задействовать интерактивные элементы в заголовок, кнопки, переключатели и т.д. будут работать только тогда, когда заголовок не застрял". Кроме того, я считаю, что это не очень подходит для этой ситуации. Мне не нужны несколько заголовков, но только один средний элемент, чтобы застрять в заголовке.
Ответы
Ответ 1
Я использовал (точнее, пытался использовать) библиотеку StickyListHeaders
в прошлом. После некоторых проблем с этим я придумал следующее. Это не сильно отличается от того, что предложили другие плакаты.
Основной файл макета activity_layout.xml
состоит из ListView
и LinearLayout
, которые по умолчанию невидимы. Используя метод OnScrollListener onScroll(), отображается видимость LinearLayout's
. Вам не нужно раздувать другой макет или динамически добавлять представления родителям макета. Вот как выглядит метод onScroll
:
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem > 3) { // 5th row will stick
llHeader.setVisibility(View.VISIBLE);
} else {
llHeader.setVisibility(View.GONE);
}
}
Просто переключите видимость, чтобы получить желаемый эффект. Вы можете взглянуть на следующий код. Его рабочий пример того, что вы можете ожидать. Активность содержит ListView
со строго боковым расширением BaseAdapter
. ListView
заполняется нумерованными кнопками (по одному на каждую строку, начиная с 0 и до 19).
public class StickyHeader extends Activity {
LinearLayout llHeader;
ListView lv;
SHAdapter shAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
lv = (ListView) findViewById(R.id.listView1);
llHeader = (LinearLayout) findViewById(R.id.llHeader);
shAdapter = new SHAdapter();
lv.setAdapter(shAdapter);
lv.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem > 3) {
llHeader.setVisibility(View.VISIBLE);
} else {
llHeader.setVisibility(View.GONE);
}
}
});
}
public class SHAdapter extends BaseAdapter {
Button btCurrent;
int[] arr = new int[] {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19};
@Override
public int getCount() {
return 20;
}
@Override
public Object getItem(int arg0) {
return arr[arg0];
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = getLayoutInflater().inflate(R.layout.list_item_layout, null);
btCurrent = (Button) convertView.findViewById(R.id.button1);
if ((Integer)getItem(position) == 4) {
btCurrent.setText("Number " + getItem(position) + " is sticky");
} else {
btCurrent.setText("" + getItem(position));
}
return convertView;
}
}
}
activity_layout.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
<!-- This LinearLayout visibility is toggled -->
<!-- Improvement suggested by user 'ar34z'
(see comment section below) -->
<include layout="@layout/list_item_layout" />
</RelativeLayout>
list_item_layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/llHeader"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/white"
android:orientation="vertical" >
<Button
android:id="@+id/button1"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Ответ 2
Это простейший код, иллюстрирующий основную идею:
listView.setOnScrollListener(new OnScrollListener() {
ViewGroup mainView = (ViewGroup) findViewById(R.id.main);
View pinnedView = null;
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem < PINNED_ITEM) {
mainView.removeView(pinnedView);
pinnedView = null;
} else if (pinnedView == null) {
pinnedView = adapter.getView(PINNED_ITEM, null, view);
pinnedView.setBackgroundColor(0xFF000000);
mainView.addView(pinnedView);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
});
У меня есть FrameLayout
, который содержит ничего, кроме моего ListView
:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
PINNED_ITEM
- позиция вашего элемента (например, PINNED_ITEM = 2
). Этот макет действует как оверлей для списка. ScrollListener
отслеживает текущие видимые элементы, и если он обнаруживает, что элемент должен быть закреплен, он добавляет его в макет и удаляет его в противном случае.
Строка pinnedView.setBackgroundColor(0xFF000000);
необходима для установки непрозрачного фона для элемента. Элемент будет прозрачным, если вы этого не сделаете. Вы можете настроить фон в соответствии с вашими потребностями (например, вы можете использовать фон из вашего текущего атрибута темы).
Ответ 3
Чтобы выполнить это, я поместил listView в relativeLayout и добавлю прослушиватель в прокрутку. Затем в нем, когда изменится firstVisibleItem, я продублирую элемент itemView, который вам нужен, и отобразите его в списке ListView.
Вот краткий образец того, что я думаю. Единственное, что дублирующее представление должно быть непрозрачным.
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*Create listView inside a relativelayout*/
final RelativeLayout rl = new RelativeLayout(this);
final ListView lv = new ListView(this);
rl.addView(lv);
/* Set it as a content view*/
setContentView(rl);
/*populate it*/
String[] items = { "Cupcake",
"Donut",
"Eclair",
"Froyo",
"Gingerbread",
"Honeycomb",
"Ice Cream Sandwich",
"Jelly Bean"};
int size = 10;
String[] arrayItems = new String[items.length*size];
for(int i = 0; i < arrayItems.length; i++)
arrayItems[i] = items[i%items.length] + " " + i;
/* Need to use a non transparent view*/
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.simple_list_item_1_opaque, arrayItems);
lv.setAdapter(adapter);
/* Choose the item to stick*/
final int itemToStick = 3;
/* Create things needed for duplication */
final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, 1);
final RelativeLayout.LayoutParams selectorParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, lv.getDividerHeight());
lv.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(itemToStick <= firstVisibleItem && rl.getChildCount() == 1)
{
/* Put view in a linearlayout in order to be able to add the sepline */
LinearLayout ll = new LinearLayout(view.getContext());
ll.setOrientation(LinearLayout.VERTICAL);
ll.addView(adapter.getView(itemToStick, null, null),params);
/* Create Divider */
View selector = new LinearLayout(view.getContext());
selector.setBackground(lv.getDivider());
/* add views*/
ll.addView(selector,selectorParams);
rl.addView(ll,params);
}
/* Remove view when scrolling up to it */
else if(itemToStick > firstVisibleItem)
{
if(rl.getChildCount() > 1)
rl.removeViewAt(1);
}
}
});
}
}
И simple_list_item_1_opaque:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:background="#FFFFFF"
/>