Scrollview вертикальный и горизонтальный в Android

152

Я очень устал искать решение для вертикальной и горизонтальной прокрутки.

Я прочитал, что в фреймворке нет никаких представлений / макетов, которые реализуют эту функцию, но мне нужно что-то вроде этого:

Мне нужно определить макет внутри другого, дочерний макет должен реализовывать прокрутку по вертикали / горизонтали для перемещения.

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

Canvas - это не мое решение, потому что мне нужно присоединить слушателей к чьим-то дочерним элементам.

Что я могу сделать?

Kronos
источник
14
Абсолютно смешно, что это не доступно из коробки с Android. Мы работали с полдюжины других технологий пользовательского интерфейса, и неслыханно не иметь такой базовой вещи, как горизонтальная / вертикальная прокрутка контейнера.
flexicious.com
Итак, наконец, мы написали свой собственный для нашей таблицы данных: androidjetpack.com/Home/AndroidDataGrid . Делает намного больше, чем просто горизонтальная / вертикальная прокрутка. Но идея заключается в том, чтобы обернуть HScroller в вертикальный скроллер. Вы также должны переопределить методы добавления / удаления дочерних методов, чтобы указать внутренний скроллер и некоторые другие махинации, но это работает.
flexicious.com

Ответы:

91

Смешав некоторые из предложенных выше предложений, удалось найти хорошее решение:

Пользовательский ScrollView:

package com.scrollable.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class VScroll extends ScrollView {

    public VScroll(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public VScroll(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VScroll(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

Пользовательский HorizontalScrollView:

package com.scrollable.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;

public class HScroll extends HorizontalScrollView {

    public HScroll(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public HScroll(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public HScroll(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

ScrollableImageActivity:

package com.scrollable.view;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;

public class ScrollableImageActivity extends Activity {

    private float mx, my;
    private float curX, curY;

    private ScrollView vScroll;
    private HorizontalScrollView hScroll;

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

        vScroll = (ScrollView) findViewById(R.id.vScroll);
        hScroll = (HorizontalScrollView) findViewById(R.id.hScroll);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float curX, curY;

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                mx = event.getX();
                my = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                curX = event.getX();
                curY = event.getY();
                vScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                hScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                mx = curX;
                my = curY;
                break;
            case MotionEvent.ACTION_UP:
                curX = event.getX();
                curY = event.getY();
                vScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                hScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                break;
        }

        return true;
    }

}

расположение:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <com.scrollable.view.VScroll android:layout_height="fill_parent"
        android:layout_width="fill_parent" android:id="@+id/vScroll">
        <com.scrollable.view.HScroll android:id="@+id/hScroll"
            android:layout_width="fill_parent" android:layout_height="fill_parent">
            <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:src="@drawable/bg"></ImageView>
        </com.scrollable.view.HScroll>
    </com.scrollable.view.VScroll>

</LinearLayout>
Махди Хиджази
источник
Это отличный ответ, у меня были другие элементы прокрутки в представлении, которые также нужно было изменить. Все, что мне нужно было сделать, это вызвать их прокрутку вместе с остальными.
Т. Маркл
1
Большой! Несколько корректировок, чтобы учесть скорость, и это было бы идеально (кроме того, LinearLayoutя думаю , вам не нужно начальное значение )
Ромуальд Брюне
Привет, Махди, дай мне знать, на каком элементе управления / виджете ты переустанавливал сенсорный слушатель в классе активности.
Шахили
Извините за задержку @DeepakSharma. Я не зарегистрировал слушателя касания в действии, я просто переопределил onTouchEvent действия.
Махди Хиджази
@MahdiHijazi Можете ли вы добавить свой код горизонтальной прокрутки и вертикальной прокрутки в github.com/MikeOrtiz/TouchImageView, это успешно масштабирует изображение при нажатии кнопки, но не
удается
43

Поскольку это, кажется, первый результат поиска в Google для "Android вертикальный + горизонтальный ScrollView", я подумал, что должен добавить это здесь. Мэтт Кларк создал собственный вид на основе источника Android, и он, кажется, отлично работает: двумерный ScrollView

Помните, что у класса на этой странице есть ошибка, вычисляющая горизонтальную ширину представления. Исправление Мануэля Хилти в комментариях:

Решение. Замените оператор в строке 808 следующим текстом:

final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);


Изменить: Ссылка больше не работает, но вот ссылка на старую версию поста .

качапа
источник
3
Спасибо, это то, что я искал.
Андрей Агибалов
1
Это работает как шарм после некоторой незначительной модификации для меня. Я переопределен fillViewportи onMeasureточно так же, как источник ScrollView (v2.1). Я также изменил два первых конструктора с помощью: this( context, null );и this(context, attrs, R.attr.scrollViewStyle);. При этом я могу использовать полосу прокрутки, используя setVerticalScrollBarEnabledи setHorizontalScrollBarEnabledв коде.
olivier_sdg
Прекрасно работает и для меня! Большое спасибо!
Авио
но как получить размер большого пальца и положение прокрутки снизу по вертикали и справа в горизонтальном режиме
Ravi
10
Похоже, ссылка исчезла.
loeschg
28

Я нашел лучшее решение.

XML: (design.xml)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent">
  <FrameLayout android:layout_width="90px" android:layout_height="90px">
    <RelativeLayout android:id="@+id/container" android:layout_width="fill_parent" android:layout_height="fill_parent">        
    </RelativeLayout>
</FrameLayout>
</FrameLayout>

Java-код:

public class Example extends Activity {
  private RelativeLayout container;
  private int currentX;
  private int currentY;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.design);

    container = (RelativeLayout)findViewById(R.id.container);

    int top = 0;
    int left = 0;

    ImageView image1 = ...
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image1, layoutParams);

    ImageView image2 = ...
    left+= 100;
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image2, layoutParams);

    ImageView image3 = ...
    left= 0;
    top+= 100;
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image3, layoutParams);

    ImageView image4 = ...
    left+= 100;     
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image4, layoutParams);
  }     

  @Override 
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            currentX = (int) event.getRawX();
            currentY = (int) event.getRawY();
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            int x2 = (int) event.getRawX();
            int y2 = (int) event.getRawY();
            container.scrollBy(currentX - x2 , currentY - y2);
            currentX = x2;
            currentY = y2;
            break;
        }   
        case MotionEvent.ACTION_UP: {
            break;
        }
    }
      return true; 
  }
}

Это работает !!!

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

Kronos
источник
Только что попробовал, и это работает очень хорошо! У него нет индикаторов прокрутки или броска, но поскольку это низкоуровневый подход, эти вещи могут быть построены поверх этого довольно легко. Спасибо!
ubzack
У меня тоже получалось хорошо для написания двухмерного прокручиваемого представления, которое выводило вывод в реальном времени из результата удаленных команд SSH по сети.
Pilcrowpipe
@Kronos: Что делать, если я хочу только прокрутить горизонтально? Каким должен быть мой параметр y в container.scrollBy (currentX - x2, currentY - y2). Есть ли способ, где мы можем сделать это прокручивать только горизонтально?
Эшвин
@Kronos: он прокручивается слишком далеко. Есть ли способ остановить прокрутку, когда достигнут конец?
Эшвин
Кнопки не прокручиваются, почему?
Салих Каллай
25

Я им пользуюсь и отлично работает

<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="@+id/ScrollView02" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content"
            xmlns:android="http://schemas.android.com/apk/res/android">
<HorizontalScrollView android:id="@+id/HorizontalScrollView01" 
                      android:layout_width="wrap_content" 
                      android:layout_height="wrap_content">
<ImageView android:id="@+id/ImageView01"
           android:src="@drawable/pic" 
           android:isScrollContainer="true" 
           android:layout_height="fill_parent" 
           android:layout_width="fill_parent" 
           android:adjustViewBounds="true">
</ImageView>
</HorizontalScrollView>
</ScrollView>

Ссылка на источник находится здесь: Android-spa

Redax
источник
2
В лучшем случае работает "просто отлично" для статического содержимого. Несколько инженеров Google сказали, что у того, что вы пытаетесь использовать, будут проблемы ( groups.google.com/group/android-developers/browse_frm/thread/… и groups.google.com/group/android-beginners/browse_thread/thread/ … )
CommonsWare
4
@CommonsWare: при всем уважении к блестящим инженерам Google, в тех темах, которые они сказали вам переписать с нуля свой собственный компонент: это лучший способ. Конечно, это правда. Но они не говорят, что это решение плохое, и если оно работает ... Может быть, для них это займет минуту, для меня лучше написать какой-нибудь xml с использованием существующих компонентов. Что значит «статический контент»? Я использую кнопку и изображения.
Redax
3
Под «статическим контентом» я подразумеваю вещи, которые сами по себе не связаны с сенсорными событиями. Моя точка зрения - для других, читающих этот ответ - заключается в том, что, хотя ваше решение может работать для одного ImageViewконтента в качестве прокручиваемого контента, оно может работать или не работать для произвольного другого контента, и что Google считает, что с вашим подходом будут проблемы. Мнение Google также имеет значение в отношении будущего развития - они, возможно, не пытаются убедиться, что ваш подход работает в долгосрочной перспективе, если они советуют людям не использовать его в первую очередь.
CommonsWare
Это работает нормально для меня ... Я освежать в ImageView в этом Scrollview используя нить
Сону томас
19

Мое решение основано на ответе Махди Хиджази , но без каких-либо пользовательских представлений:

Планировка:

<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/scrollHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ScrollView 
        android:id="@+id/scrollVertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" >

        <WateverViewYouWant/>

    </ScrollView>
</HorizontalScrollView>

Код (onCreate / onCreateView):

    final HorizontalScrollView hScroll = (HorizontalScrollView) value.findViewById(R.id.scrollHorizontal);
    final ScrollView vScroll = (ScrollView) value.findViewById(R.id.scrollVertical);
    vScroll.setOnTouchListener(new View.OnTouchListener() { //inner scroll listener         
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return false;
        }
    });
    hScroll.setOnTouchListener(new View.OnTouchListener() { //outer scroll listener         
        private float mx, my, curX, curY;
        private boolean started = false;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            curX = event.getX();
            curY = event.getY();
            int dx = (int) (mx - curX);
            int dy = (int) (my - curY);
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    if (started) {
                        vScroll.scrollBy(0, dy);
                        hScroll.scrollBy(dx, 0);
                    } else {
                        started = true;
                    }
                    mx = curX;
                    my = curY;
                    break;
                case MotionEvent.ACTION_UP: 
                    vScroll.scrollBy(0, dy);
                    hScroll.scrollBy(dx, 0);
                    started = false;
                    break;
            }
            return true;
        }
    });

Вы можете изменить порядок просмотра прокрутки. Просто измените их порядок в макете и в коде. И, очевидно, вместо WeverViewYouWant вы помещаете макет / представления, которые вы хотите прокрутить в обоих направлениях.

Yan.Yurkin
источник
Я настоятельно рекомендую вернуть false в hScroll.setOnTouchListener. Когда я изменил на false, список начал прокручиваться «плавно автоматически» на палец вверх в обоих направлениях.
Грета Радисаускайте
1
Работает при первой горизонтальной прокрутке. Когда вертикаль первая, идет только вертикально
Ирхала
6

Вариант № 1: Вы можете придумать новый дизайн пользовательского интерфейса, который не требует одновременной горизонтальной и вертикальной прокрутки.

Вариант № 2: Вы можете получить исходный код ScrollViewи HorizontalScrollViewузнать, как его реализовала основная команда Android, и создать свою собственную BiDirectionalScrollViewреализацию.

Вариант № 3: Вы можете избавиться от зависимостей, которые требуют от вас использовать систему виджетов и рисовать прямо на холсте.

Вариант № 4: Если вы наткнулись на приложение с открытым исходным кодом, которое, кажется, реализует то, что вы ищете, посмотрите, как они это сделали.

CommonsWare
источник
6

Попробуй это

<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="@+id/Sview" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        xmlns:android="http://schemas.android.com/apk/res/android">
<HorizontalScrollView 
   android:id="@+id/hview" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content">
<ImageView .......
 [here xml code for image]
</ImageView>
</HorizontalScrollView>
</ScrollView>
MBMJ
источник
6

Поскольку ссылка на двухмерное прокручивание не работает, я не смог ее получить, поэтому создал свой собственный компонент. Он справляется со сбрасыванием и работает правильно для меня. Единственным ограничением является то, что wrap_content может не работать должным образом для этого компонента.

https://gist.github.com/androidseb/9902093

androidseb
источник
2

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

Дайте мне знать, на какой пользовательский интерфейс вы ориентируетесь.

С Уважением,

Рави Пандит

Рави Пандит
источник
1

Играя с кодом, вы можете поместить HorizontalScrollView в ScrollView. Таким образом, вы можете использовать два метода прокрутки в одном представлении.

Источник: http://androiddevblog.blogspot.com/2009/12/creating-two-dimensions-scroll-view.html

Я надеюсь, что это может помочь вам.

Eriatolc
источник
1
Это не хорошее решение. Если вы замечаете, что при перетаскивании вида вы всегда перетаскиваете либо вертикально, либо горизонтально. Вы не можете перемещаться по диагонали, используя вложенные представления прокрутки.
Скай Келси
Что хуже, чем отсутствие диагональной прокрутки, так это отсутствие обеих полос прокрутки, остающихся на краю обзора, потому что одна вложена. Лучшее ScrollView - это ответ, а ссылка Cachapa на код Мэтта Кларка - лучшее решение в настоящее время благодаря полноте и обобщению.
Хуперникетес