Как я могу сделать горизонтальный ListView в Android?

Возможный дубликат:
Horizontal ListView в Android?

Как и многие другие вещи в Android, вы не подумаете, что это такая сложная проблема, но, черт возьми, вы ошибаетесь. И, как и многое другое в Android, API даже не предоставляет достаточно расширяемой отправной точки. Будь я проклят, если собираюсь свернуть свой собственный ListView, когда все, что мне нужно, - это взять его и перевернуть на бок. \ rant

Хорошо, теперь, когда я перестал злиться, давайте поговорим о самой проблеме. Мне нужно что-то вроде Gallery, но без функции блокировки по центру. Мне действительно не нужен listSelector ListView, но он полезен. В основном, я мог бы делать то, что хочу, с LinearLayout внутри ScrollView, но мне нужно, чтобы дочерние представления исходили от ListAdapter, и мне бы очень хотелось иметь переработчик представлений. И я действительно не хочу писать код макета.

Я заглянул в исходный код некоторых из этих классов ...

Галерея: Похоже, я мог бы использовать Галерею, если бы переопределил большинство методов onXyz, скопировал весь их исходный код, но воздержался от вызова scrollIntoSlots(). Но я уверен, что если я это сделаю, я наткнусь на какое-то недоступное поле для членов или с другими непредвиденными последствиями.

AbsSpinner: Поскольку поле mRecycler является закрытым для пакета, я сомневаюсь, что смогу расширить этот класс.

AbsListView: Похоже, этот класс предназначен только для вертикальной прокрутки, поэтому здесь нет никакой помощи.

AdapterView: Мне никогда не приходилось напрямую расширять этот класс. Если вы скажете мне, что это легко сделать, и что легко раскатать свой RecycleBin, я буду очень скептически настроен, но я попробую.

Полагаю, я мог бы скопировать оба AbsSpinner и Gallery, чтобы получить то, что я хочу ... надеюсь, эти классы не используют какую-то частную переменную пакета, к которой я не могу получить доступ. Вы все думаете, что это хорошая практика? Есть ли у кого-нибудь учебные пособия или сторонние решения, которые могут направить меня в правильном направлении?

Обновление:
Единственное решение, которое я нашел до сих пор, - это делать все самостоятельно. Задав этот вопрос, я переопределил AdapterView и реализовал свой собственный HorizontalListView с нуля. Единственный способ по-настоящему переопределить функцию блокировки центра галереи - это переопределить частный метод scrollIntoSlots, который, как мне кажется, потребует создания подкласса во время выполнения. Если у вас хватит смелости сделать это, это, пожалуй, лучшее решение, но я не хочу полагаться на недокументированные методы, которые могут измениться.

Swathi EP ниже предложил мне присвоить Gallery OnTouchListener и переопределить функцию прокрутки. Если вам не важна поддержка меток в вашем списке, или если представления могут привязываться к центру в конце анимации метания, то это будет работать для вас! Однако, в конце концов, по-прежнему невозможно удалить центральную блокировку без снятия откидной опоры. И я спрашиваю вас, в какой список не выкидывают?

Так что, увы, у меня это не сработало. :-( Но если вам интересен такой подход, читайте дальше ...

Мне также пришлось внести некоторые дополнения в код Swathi, чтобы получить то, что я хотел. В GestureListener.onTouch, помимо делегирования детектору жестов, мне также приходилось возвращать истину для событий ACTION_UP и ACTION_CANCEL. Это успешно отключило функцию блокировки по центру, но также отключило подбрасывание. Мне удалось снова включить бросок, имея моего собственного делегата GestureListener для onFling метода галереи. Если вы хотите попробовать это, перейдите к вашему образцу кода ApiDemos и замените класс Gallery1.java следующим кодом:

import com.example.android.apis.R;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;

public class Gallery1 extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.gallery_1);

        // Reference the Gallery view
        final Gallery g = (Gallery) findViewById(R.id.gallery);

        // Set the adapter to our custom adapter (below)
        g.setAdapter(new ImageAdapter(this));

        // Set a item click listener, and just Toast the clicked position
        g.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView parent, View v, int position, long id) {
                Toast.makeText(Gallery1.this, "" + position, Toast.LENGTH_SHORT).show();
            }
        });

        // Gesture detection
        final GestureDetector gestureDetector = new GestureDetector(new MyGestureDetector(g));
        OnTouchListener gestureListener = new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean retVal = gestureDetector.onTouchEvent(event);
                int action = event.getAction();
                if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                    retVal = true;
                    onUp();
                }
                return retVal;
            }

            public void onUp() {
                // Here I am merely copying the Gallery's onUp() method.
                for (int i = g.getChildCount() - 1; i >= 0; i--) {
                    g.getChildAt(i).setPressed(false);
                }
                g.setPressed(false);
            }
        };
        g.setOnTouchListener(gestureListener);

        // We also want to show context menu for longpressed items in the gallery
        registerForContextMenu(g);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        menu.add(R.string.gallery_2_text);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        Toast.makeText(this, "Longpress: " + info.position, Toast.LENGTH_SHORT).show();
        return true;
    }

    public class ImageAdapter extends BaseAdapter {
        int mGalleryItemBackground;

        public ImageAdapter(Context c) {
            mContext = c;
            // See res/values/attrs.xml for the <declare-styleable> that defines
            // Gallery1.
            TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
            mGalleryItemBackground = a.getResourceId(
                    R.styleable.Gallery1_android_galleryItemBackground, 0);
            a.recycle();
        }

        public int getCount() {
            return mImageIds.length;
        }

        public Object getItem(int position) {
            return position;
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView i = new ImageView(mContext);

            i.setImageResource(mImageIds[position]);
            i.setScaleType(ImageView.ScaleType.FIT_XY);
            i.setLayoutParams(new Gallery.LayoutParams(136, 88));

            // The preferred Gallery item background
            i.setBackgroundResource(mGalleryItemBackground);

            return i;
        }

        private Context mContext;

        private Integer[] mImageIds = {
                R.drawable.gallery_photo_1,
                R.drawable.gallery_photo_2,
                R.drawable.gallery_photo_3,
                R.drawable.gallery_photo_4,
                R.drawable.gallery_photo_5,
                R.drawable.gallery_photo_6,
                R.drawable.gallery_photo_7,
                R.drawable.gallery_photo_8
        };
    }

    public class MyGestureDetector extends SimpleOnGestureListener {

        private Gallery gallery;

        public MyGestureDetector(Gallery gallery) {
            this.gallery = gallery;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 
                float velocityY) {
            return gallery.onFling(e1, e2, velocityX, velocityY);
        }
    }

}

person Neil Traft    schedule 06.10.2010    source источник


Ответы (8)


Прочитав этот пост, я реализовал свой собственный горизонтальный ListView. Вы можете найти его здесь: http://dev-smart.com/horizontal-listview/ Позвольте мне знать, если это помогает.

person Paul    schedule 08.03.2011
comment
Один вопрос: почему вы делаете синхронизацию потоков в своей реализации? Все работает на 100% в потоке пользовательского интерфейса. - person Paul Turchenko; 08.09.2011
comment
Этот виджет НЕ уважает параметр макета wrap_content. Если вы не запрограммируете его высоту жестко, это заставит вас потратить остаток дня, пытаясь выяснить, почему ни одно из ваших представлений под ним не отображается, поскольку оно поглощает все пространство экрана. - person John; 07.10.2011
comment
Я добавил поддержку wrap_content в атрибут layout_height. Это исправление можно найти в разветвленной версии HorizontalViewList: github.com/inazaruk/android-dev -smart-lib - person inazaruk; 08.08.2012
comment
Хорошая работа, но я пробовал ваш горизонтальный список, но у меня проблема с настраиваемым элементом xml, настраиваемым адаптером и загрузчиком изображений (кешем), ссылка на изображения потеряна, и мне нужно дважды щелкнуть, чтобы отобразить их. С родным вертикальным контейнером такой проблемы не возникает. Ссылка на загрузчик изображений: android-developers.blogspot.com/2010/ 07 / Спасибо за помощь! - person Hpsaturn; 22.11.2012
comment
Я попытался поместить этот горизонтальный ListView внутрь listView. (Вложенный) И производительность (эффект прокрутки) ужасна. Кто-нибудь пробовал вложить HorizontalListView в ListView, как это делает приложение Pulse? - person eugene; 28.11.2012
comment
@PaulTurchenko У меня проблема с этим HorizontalListVIew. Он создает пустое пространство при прокрутке справа налево, так что он всегда увеличивает пространство слева (перед первым элементом). Вы можете мне помочь в этом. - person Noundla Sandeep; 26.04.2013
comment
Я использовал HorizontalListView от Dev-Smart, он идеален, но я не могу применить нажатый стиль для щелчка элемента в представлении списка. Какие-либо предложения? - person nesimtunc; 04.06.2013
comment
Я не могу найти исходный код для создания файла jar. Ссылка на github претерпела некоторые изменения: запущен новый проект github по адресу github.com/dinocore1/Android-Horizontal -ListView может ли кто-нибудь указать мне исходный код или сам файл jar? - person Duna; 21.08.2013
comment
@Lunatikul Конечно: github.com/dinocore1/DevsmartLib-Android - person Mark Phillip; 26.09.2013
comment
setSelection не работает ... - person desgraci; 08.10.2013
comment
он не работает с android-support-v7-appcompat - person Guilherme Gregores; 23.03.2014
comment
Павел, ссылка на нее не рабочая, может подсказать альтернативу? - person Fernando Leal; 25.07.2014
comment
Эта библиотека была отличным обходным решением, спасибо автору Полу .... Но теперь с помощью Recycler view это очень просто! .... Всем, кто смотрит сюда, обязательно стоит посмотреть на recyclerview. - person Devrath; 15.04.2015

Приходилось ли вам использовать HorizontalScrollView для обертывания элементов списка? Это позволит прокручивать каждый из элементов вашего списка по горизонтали (то, что вы вставляете, зависит от вас, и вы можете сделать их динамическими элементами, похожими на ListView). Это будет хорошо работать, если вы будете искать только один ряд элементов.

person Thira    schedule 18.10.2010
comment
Я не использую этот подход по двум причинам: (1) он не расширяет AdapterView, поэтому я не могу использовать его взаимозаменяемо с моими кодами ListView и ListAdapter, и (2) нет повторного использования представлений. Возможно, можно было бы добавить эту функциональность в представление прокрутки, но мне кажется, что это испортит поведение прокрутки, и это потребовало бы почти такой же работы, как простое расширение AdapterView. - person Neil Traft; 19.10.2010

Вы знаете, могло бы быть возможным использовать существующий ListView с некоторым разумным переопределением dispatchDraw() (чтобы повернуть холст на 90 градусов), onTouch() (чтобы поменять местами X и Y координат MotionEvent) и, возможно, onMeasure () или что-то еще, чтобы заставить его думать, что это y на x, а не x на y ...

Я понятия не имею, сработает ли это на самом деле, но было бы интересно узнать. :)

person Reuben Scratton    schedule 12.11.2010
comment
Ха-ха, мне нравится твое отношение ... я вроде как боюсь попробовать ... Сначала я собираюсь опробовать предложение Swathi EP. - person Neil Traft; 12.11.2010
comment
Итак, я пошел дальше и реализовал свой собственный подкласс AdapterView, поскольку полагал, что это потребует столько же работы и меньшего риска. Однако испытание вашего предложения было бы забавным экспериментом. :-) - person Neil Traft; 14.11.2010
comment
С дополнительным бонусом, что техника может работать для любого типа просмотра. Сложнее всего найти практическое применение! :) - person Reuben Scratton; 15.11.2010
comment
Изучив это, если ваше решение не требует повторной реализации кода из суперкласса в вашем подклассе, который использует mLeft или любое m [свойство] в этом отношении, вам может повезти. Вы просто не можете получить доступ к этим свойствам из своих собственных классов, и это делает практически любое решение невозможным. - person Emile; 25.11.2010

Это может быть очень поздний ответ, но он работает для нас. Мы используем ту же галерею, что и в Android, только мы настроили левое поле таким образом, чтобы левый край экрана считался центром галереи. Это действительно хорошо сработало для нас.

person bhasker_kottapally    schedule 22.01.2012
comment
Галерея устарела и заменена на HorizontalScrollView в API с тех пор, как она была опубликована. Просто прикрепите к нему LinearLayout, и теперь вы можете добавлять элементы с помощью кода. Отлично работает, но не перерабатывает огромное количество предметов. - person G_V; 12.11.2014

Я использовал Паулса (см. Его answer) Реализация HorizontalListview, и она работает, большое спасибо за то, что поделились!

Я немного изменил его HorizontalListView-Class (кстати, Пол, в вашем имени класса есть опечатка, ваше имя класса - «HorizontialListView» вместо «HorizontalListView», «i» слишком много), чтобы обновить дочерние представления при выборе.

ОБНОВЛЕНИЕ: мой код, который я разместил здесь, был неправильным, я полагаю, поскольку у меня возникли проблемы с выделением (я думаю, что это связано с переработкой представления), мне нужно вернуться к чертежной доске .. .

ОБНОВЛЕНИЕ 2: Хорошо. Проблема решена, я просто прокомментировал "removeNonVisibleItems (dx);" в "onLayout (..)", я думаю, это снизит производительность, но поскольку я использую только очень маленькие списки, это не проблема для меня.

Я в основном использовал это руководство здесь, на сайте developerlife и просто заменил ListView на Pauls HorizontalListView и сделал изменяется, чтобы разрешить «постоянный» выбор (дочерний элемент, на котором щелкнули, меняет свой внешний вид, а при повторном щелчке он меняет его обратно).

Я новичок, поэтому, вероятно, в коде много уродливых вещей, дайте мне знать, если вам понадобятся подробности.

person free    schedule 06.04.2011
comment
Приятно, что он заработал, но он не перерабатывает представления, данные статичны, порядок элементов не гарантируется, и очень сложно настроить / исправить, не зная точно, что вы делаете, из-за отсутствия комментарии в нем. Если вы точно знаете, что делаете, вам вообще не нужно это решение. Чувак, я бы хотел, чтобы команда Android уже реализовала это. Я сохраняю только несколько элементов на просмотр прокрутки, чтобы он был быстрым, при этом я использую LinkedList, чтобы гарантировать позиционирование при перерисовке. Это не идеально, но работает достаточно хорошо. - person G_V; 12.11.2014

Галерея - лучшее решение, попробовал. Я работал над одним почтовым приложением, в котором письма во входящем ящике отображались в виде списка, мне нужен был горизонтальный вид, я просто преобразовал список в галерею, и все работало нормально, как мне было нужно, без каких-либо ошибок. Для эффекта прокрутки я включил прослушиватель жестов для галереи. Надеюсь, этот ответ может вам помочь.

person Swathi EP    schedule 01.11.2010
comment
Что вы имеете в виду, говоря, что я включил прослушиватель жестов для галереи? Вы только что переопределили метод onScroll галереи и сами выполняли прокрутку? Если да, то как вы сказали детям галереи сменить позицию? - person Neil Traft; 01.11.2010
comment
Я написал класс, расширяющий SimpleOnGestureListener, в котором я переопределил метод onfling (). В приведенном ниже коде показано обнаружение жестов и прослушиватель жестов моей галереи: - person Swathi EP; 02.11.2010
comment
// Определение жестов gestureDetector = new GestureDetector (new MyGestureDetector ()); gestureListener = новый View.OnTouchListener () {общедоступное логическое значение onTouch (View v, событие MotionEvent) {if (gestureDetector.onTouchEvent (событие)) {return true; } return false; }}; myGallery.setOnTouchListener (gestureListener); - person Swathi EP; 02.11.2010
comment
Я был очень занят, и у меня не было возможности попробовать это, но это звучит многообещающе. Надеюсь, я доберусь до этого в эти выходные ... - person Neil Traft; 12.11.2010
comment
Опробовал ваше решение сегодня. Это было не так просто, как вы сказали, и, в конечном счете, это не соответствовало моим потребностям. Я добавлю подробности в свой исходный пост для всех, кому это интересно. +1 за усилие. - person Neil Traft; 14.11.2010

Вы изучали компонент ViewFlipper? Может быть, это поможет тебе.

http://developer.android.com/reference/android/widget/ViewFlipper.html

С помощью этого компонента вы можете прикрепить два или более дочерних представления. Если вы добавите анимацию перевода и обнаружение жестов, у вас может получиться красиво горизонтальная прокрутка.

person brent    schedule 12.11.2010
comment
Я обязательно изучу это, если когда-нибудь реализую просмотр постраничного просмотра, подобный домашнему экрану. Но поскольку одновременно может отображаться только одна вещь, это не совсем похоже на представление списка и будет демонстрировать поведение блокировки по центру, которого я пытаюсь избежать. - person Neil Traft; 14.11.2010

Мое приложение использует ListView в режиме портрета, который просто переключается в галерею в альбомном режиме. Оба они используют один BaseAdapter. Это выглядит так, как показано ниже.

       setContentView(R.layout.somelayout);
       orientation = getResources().getConfiguration().orientation;

       if ( orientation == Configuration.ORIENTATION_LANDSCAPE )
       {

            Gallery gallery = (Gallery)findViewById( R.id.somegallery );

            gallery.setAdapter( someAdapter );

            gallery.setOnItemClickListener( new OnItemClickListener() {
            @Override
            public void onItemClick( AdapterView<?> parent, View view,
                    int position, long id ) {

                onClick( position );
            }
        });
       }
       else
       {
            setListAdapter( someAdapter );

            getListView().setOnScrollListener(this);
       }    

Для обработки событий прокрутки я унаследовал собственный виджет из галереи и переопределил onFling (). Вот layout.xml:

    <view 
    class="package$somegallery"
    android:id="@+id/somegallery" 
    android:layout_height="fill_parent" 
    android:layout_width="fill_parent">
    </view>

и код:

    public static class somegallery extends Gallery
    {
        private Context mCtx;

        public somegallery(Context context, AttributeSet attrs)
        {
            super(context, attrs);
            mCtx = context;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                float velocityY) {

            ( (CurrentActivity)mCtx ).onScroll();

            return super.onFling(e1, e2, velocityX, velocityY);
        }
   }
person ackio    schedule 12.11.2010
comment
Насколько я понимаю, это все еще будет иметь поведение блокировки центра галереи. Что делает onScroll() метод вашей деятельности? - person Neil Traft; 14.11.2010
comment
На самом деле я удалил здесь часть кода перед вызовом onScroll (), поскольку он не затрагивает вопрос. Если вам интересно, я вызываю getLastVisiblePosition (), который используется в onScroll, чтобы определить, виден ли последний элемент, и выполнить некоторую логику. Что касается центрального замка - да, у него такое поведение. - person ackio; 15.11.2010