28

I have an ImageView that is twice the height of a normale screen ( 960 dip). I would like to scroll it nicely up and down on the screen. The bottom of the screen should contain a button. I have tried various combinations of ScrollView and Imageviews without any success. I have also thinkered with the :isScrollContainer attribute without results. Anyone knows how to do this? Cheers, Luca

Tamás
  • 47,239
  • 12
  • 105
  • 124
luca
  • 369
  • 1
  • 5
  • 7

9 Answers9

34

@cV2 Thank you so much for that code. It got me going in the direction I needed. Here's my modified version which stops scrolling at the edges of the image...

    // set maximum scroll amount (based on center of image)
    int maxX = (int)((bitmapWidth / 2) - (screenWidth / 2));
    int maxY = (int)((bitmapHeight / 2) - (screenHeight / 2));

    // set scroll limits
    final int maxLeft = (maxX * -1);
    final int maxRight = maxX;
    final int maxTop = (maxY * -1);
    final int maxBottom = maxY;

    // set touchlistener
    ImageView_BitmapView.setOnTouchListener(new View.OnTouchListener()
    {
        float downX, downY;
        int totalX, totalY;
        int scrollByX, scrollByY;
        public boolean onTouch(View view, MotionEvent event)
        {
            float currentX, currentY;
            switch (event.getAction())
            {
                case MotionEvent.ACTION_DOWN:
                    downX = event.getX();
                    downY = event.getY();
                    break;

                case MotionEvent.ACTION_MOVE:
                    currentX = event.getX();
                    currentY = event.getY();
                    scrollByX = (int)(downX - currentX);
                    scrollByY = (int)(downY - currentY);

                    // scrolling to left side of image (pic moving to the right)
                    if (currentX > downX)
                    {
                        if (totalX == maxLeft)
                        {
                            scrollByX = 0;
                        }
                        if (totalX > maxLeft)
                        {
                            totalX = totalX + scrollByX;
                        }
                        if (totalX < maxLeft)
                        {
                            scrollByX = maxLeft - (totalX - scrollByX);
                            totalX = maxLeft;
                        }
                    }

                    // scrolling to right side of image (pic moving to the left)
                    if (currentX < downX)
                    {
                        if (totalX == maxRight)
                        {
                            scrollByX = 0;
                        }
                        if (totalX < maxRight)
                        {
                            totalX = totalX + scrollByX;
                        }
                        if (totalX > maxRight)
                        {
                            scrollByX = maxRight - (totalX - scrollByX);
                            totalX = maxRight;
                        }
                    }

                    // scrolling to top of image (pic moving to the bottom)
                    if (currentY > downY)
                    {
                        if (totalY == maxTop)
                        {
                            scrollByY = 0;
                        }
                        if (totalY > maxTop)
                        {
                            totalY = totalY + scrollByY;
                        }
                        if (totalY < maxTop)
                        {
                            scrollByY = maxTop - (totalY - scrollByY);
                            totalY = maxTop;
                        }
                    }

                    // scrolling to bottom of image (pic moving to the top)
                    if (currentY < downY)
                    {
                        if (totalY == maxBottom)
                        {
                            scrollByY = 0;
                        }
                        if (totalY < maxBottom)
                        {
                            totalY = totalY + scrollByY;
                        }
                        if (totalY > maxBottom)
                        {
                            scrollByY = maxBottom - (totalY - scrollByY);
                            totalY = maxBottom;
                        }
                    }

                    ImageView_BitmapView.scrollBy(scrollByX, scrollByY);
                    downX = currentX;
                    downY = currentY;
                    break;

            }

            return true;
        }
    });

I'm sure it could be refined a bit, but it works pretty well. :)

wirbly
  • 2,183
  • 1
  • 24
  • 24
  • @wirbly : i am using this code it works perfectly works for me but just want to scroll half of the screen width while we move the right and left. How we can implement that? where i have to change – Arpit Patel Mar 08 '13 at 09:55
  • Using this code, the image goes from corners to corners, idk – ruif3r Apr 21 '21 at 22:31
29

I searched so long for this Code, so I wanted to share this great peace of code:

this code is from an Activity, which has a xml file on the backend containing an ImageView called 'img'

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/img"
    android:scaleType="center"
    android:background="#fff"
    android:src="@drawable/picName"
/>

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.xml_name_layout);

    final ImageView switcherView = (ImageView) this.findViewById(R.id.img);

    switcherView.setOnTouchListener(new View.OnTouchListener() {

        public boolean onTouch(View arg0, 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();
                    switcherView.scrollBy((int) (mx - curX), (int) (my - curY));
                    mx = curX;
                    my = curY;
                    break;
                case MotionEvent.ACTION_UP:
                    curX = event.getX();
                    curY = event.getY();
                    switcherView.scrollBy((int) (mx - curX), (int) (my - curY));
                    break;
            }

            return true;
        }
    });

}

did the job perfectly for me... horizontal & vertical scrolling included (enabled)

only negative side is... you can scroll over the edge of the picture... but this is no problem for me.. and spending some time you could easily implement this feature :)

good luck && have fun

cV2
  • 5,229
  • 3
  • 43
  • 53
27

this is how I fixed it :p

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:scrollbarAlwaysDrawVerticalTrack="true">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:contentDescription="Specs"
        android:scrollbars="vertical"
        android:src="@drawable/image"/>
</ScrollView>
Shohan Ahmed Sijan
  • 4,391
  • 1
  • 33
  • 39
frapeti
  • 1,090
  • 13
  • 18
9

@wirbly Thank you so much for your code, it works exactly the way I want. But when i first read your code, I was a little confused about the four variables that you forgot to define .

So, i want to add the definition for the code to make it clearer.

Resources res=getResources();
Bitmap mBitmap = BitmapFactory.decodeResource(res, R.drawable.p_1920x1080); 
BitmapDrawable bDrawable = new BitmapDrawable(res, mBitmap);

//get the size of the image and  the screen
int bitmapWidth = bDrawable.getIntrinsicWidth();
int bitmapHeight = bDrawable.getIntrinsicHeight();
int screenWidth = this.getWindowManager().getDefaultDisplay().getWidth();  
int screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();

Hope it's helpful.

hukeping
  • 665
  • 7
  • 12
6

The easiest way is imho to use a webview and load the image into it via a local html file. This way you will also automatically get the zoom controls if you want to use them. For a large image (i.e. if it's 1000 or 3000 px wide) you will notice that Android (Coliris) isn't very good at displaying large zoomed images very sharp, even if the original images is sharp and uncompressed). This is a known issue. The solution for that is to break down the large image into smaller tiles and put them together again via html (div's or table). I use this approach to provide a subway map (larger than the screen and scrollable) to the user.

    WebView webView = (WebView)findViewById(R.id.webView);
    webView.getSettings().setBuiltInZoomControls(true);
    webView.getSettings().setUseWideViewPort(true);
    webView.getSettings().setDefaultZoom(WebSettings.ZoomDensity.FAR);

    webView.loadUrl( "content://com.myapp.android.localfile/sdcard/myappdata/common/mtr_map.html");

This might work for most cases / 'regular apps', although depends on your particular case. If you are talking about a image as a scrollable background of a game, it might not be useful for you.

Instead of a html file, you could also load the image directly (png, jpg). If you don't want the zoom control, just turn them off.

Mathias Conradt
  • 28,420
  • 21
  • 138
  • 192
  • I do this a lot, but I came to this question for the other techniques. I +1'd you though! – CQM Jul 11 '11 at 01:54
  • Check this thread and comments out, related topic & solution (with code): http://stackoverflow.com/questions/6476853/catiledlayer-alternative-for-android – Mathias Conradt Jul 11 '11 at 05:38
  • +1 thanks for the answer, but how to display an image in the WebView from the memory? – Buffalo Aug 20 '12 at 09:21
  • 2
    sorry for my downvote, but i would try everything to **avoid the usage of a WebView** since it is a very heavy View. – martyglaubitz Nov 10 '12 at 13:57
  • @martyonair Yes, WebView is quite heavy, however, compared to what such a large image (twice the screen size) brings along anyway, it all becomes relative. I think there's one benefit of the WebView, which is if you want to use the zoom function as well. If not, then the other solutions/views (as suggested in the other answers) are probably better, I agree. – Mathias Conradt Nov 11 '12 at 02:17
  • 1
    @MathiasLin it may be true that the WebView is very convinient to display images, but we experienced that the memory overhead through the bindings to the native WebKit Widget is **to damn high**. furthermore this View seems to leak memory: http://code.google.com/p/android/issues/detail?id=9375 – martyglaubitz Nov 11 '12 at 10:40
  • 1
    @martyonair Yep, you're right. And the bug report you linked, was actually reported by me back then :) I'm aware of that, so basically, it's only about the convenient zoom feature, assuming with only one WebView - memory consumption might not matter that much to you in certain cases. Which I guess can be implemented in other ways as well though. – Mathias Conradt Nov 11 '12 at 23:22
  • 1
    @MathiasLin i remember that i used Matrix Scaling to implement pinch to zoom on ImageViews, the solution in this post: http://stackoverflow.com/questions/10630373/android-image-view-pinch-zooming worked for me **you reported this bug ? dafuq - world's small ^^** – martyglaubitz Nov 12 '12 at 08:22
3

Another way is to create a HorizontalScrollView, add the imageView into it and then add the HorizontalScrollView into a ScrollView. This allows you to scroll up, down, left, right.

Hussain
  • 5,552
  • 4
  • 40
  • 50
Mr X
  • 171
  • 1
  • 3
  • 7
  • 3
    I've just tried that, as it seemed nice and easy, but the user experience is unfortunately disappointing. Scrolling only happens horizontally OR vertically at one time, cannot happen in diagonal. Worse: assuming the vertical scroll is the outside container, then if you make a diagonal swipe which is not near-perfectly horizontal, the scrolling will happen vertically, even if the vertical component of your swipe is much smaller than its horizontal component. – Balint Feb 01 '14 at 21:03
2

it works for me

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/img"
            android:scaleType="centerCrop"
            android:adjustViewBounds="true"/>

        <Button
            style="@style/btn"
            android:id="@+id/btn"
            android:layout_height="60dp"
            android:layout_marginTop="20dp"
            android:background="@drawable/btn"
            android:onClick="click"
            android:text="text" />
    </LinearLayout>

</ScrollView>
Liena
  • 91
  • 3
0

Create a JavaClass for ZoomableImageView this name:

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;

public class ZoomableImageView extends androidx.appcompat.widget.AppCompatImageView implements ScaleGestureDetector.OnScaleGestureListener {
    private Matrix matrix;
    private float scaleFactor = 1.0f;
    private ScaleGestureDetector scaleGestureDetector;

    private PointF lastTouchPoint;

    public ZoomableImageView(Context context) {
        super(context);
        initialize(context);
    }

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

    public ZoomableImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize(context);
    }

    private void initialize(Context context) {
        matrix = new Matrix();
        scaleGestureDetector = new ScaleGestureDetector(context, this);
        setScaleType(ScaleType.MATRIX);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        scaleGestureDetector.onTouchEvent(event);

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastTouchPoint = new PointF(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                float dx = event.getX() - lastTouchPoint.x;
                float dy = event.getY() - lastTouchPoint.y;
                lastTouchPoint.set(event.getX(), event.getY());
                matrix.postTranslate(dx, dy);
                setImageMatrix(matrix);
                break;
        }

        return true;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        scaleFactor *= detector.getScaleFactor();
        scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 5.0f));

        matrix.setScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
        setImageMatrix(matrix);

        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        // Nothing to do here
    }
}

This is XML

<com.coderfaysal.madrasah.ZoomableImageView
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     />
0

hey guys found an easy and 100% reliable solution as Mr X mentioned above(as the other codes mentioned weren't working working on some devices or breaking) Simply use ScrollView provided by android and it takes care of stuff

something like this

<ScrollView android:layout_width="fill_parent" android:id="@+id/scrollView1"
        android:layout_height="wrap_content" android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true" android:layout_above="@+id/button1"
        android:layout_alignParentRight="true" android:scrollbarAlwaysDrawVerticalTrack="true">
        <LinearLayout android:id="@+id/linearLayout1"
            android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center_horizontal">
            <ImageView android:src="@android:drawable/ic_menu_report_image"
                android:layout_height="wrap_content" android:id="@+id/imageView1"
                android:layout_width="wrap_content"></ImageView>
        </LinearLayout>
    </ScrollView>

and in oncreate something like this

if (mImageName != null) {
        InputStream is = null;
        try {
            is = this.getResources().getAssets().open("mathematics/"+mImageName);
        } catch (IOException e) {
        }
        Bitmap image = BitmapFactory.decodeStream(is);

        mImageView.setImageBitmap(image);
}

Cheers!

neelabh
  • 479
  • 6
  • 19