Подтвердить что ты не робот

Создание среднего элемента для застревания в заголовке (ScrollView/ListView)

Я хочу сделать элемент, который показан в середине ScrollView (или ListView) первым, а затем застревает в заголовке экрана при прокрутке.

Его прототипная реализация в CSS + JS: http://jsfiddle.net/minhee/aPcv4/embedded/result/.

На первый взгляд я бы включил ScrollView ListView, но в официальных документах говорится:

Вы никогда не должны использовать ScrollView с ListView, потому что ListView позаботится о своей собственной вертикальной прокрутке. Самое главное, что это приводит к поражению всех важных оптимизаций в ListView для работы с большими списками, поскольку оно эффективно заставляет ListView отображать весь свой список элементов, чтобы заполнить бесконечный контейнер, предоставленный ScrollView.

Итак, какие подходы я могу попытаться достичь этого пользовательского интерфейса?

Обновление. Я пробовал StickyListHeaders, но: "в настоящее время невозможно задействовать интерактивные элементы в заголовок, кнопки, переключатели и т.д. будут работать только тогда, когда заголовок не застрял". Кроме того, я считаю, что это не очень подходит для этой ситуации. Мне не нужны несколько заголовков, но только один средний элемент, чтобы застрять в заголовке.

4b9b3361

Ответ 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"
/>