Scrolling over large canvas

2019-03-11 12:05发布

I need some help understanding the fundamentals of scrolling over items that are drawn to a canvas in Android. Suppose I want to create a timeline where time at 0 is the top of a visualization and as time increased the timeline continues to be rendered below the previous point. If I wish to render this on Android I know I could simply create a bunch of items on a canvas by overriding onDraw(). However, let's suppose the visualization is bigger than the screen allows.

For example in the first picture below the large black box contains the entire canvas as I render it. I create a blue line that runs vertically up and down as well as several yellow, green and blue rectangles. The red box represents the screen of the Android that is rendering the visualization. As it initially opens all items are drawn but only the items contained within the red box show up on the screen.

Before_Scroll

Now if the user is to scroll down, items that initially appeared below the red box are in view while items that have gone out of the confines of the red box are no longer visable, as represented in the second picture.

After_Scroll

I believe I need to use scrollables but I'm quite lost how to do so. I've read over this page http://developer.android.com/training/custom-views/custom-drawing.html explaining how to create your own customer images and this page http://developer.android.com/training/custom-views/making-interactive.html explaining how to make the UI interactive, but I think I'm missing something.

A sample code that illustrates this problem (this is basic, assume there is logic dictating WHERE the boxes/lines go, etc.) is as follows:

package com.example.scrolltest;

import com.example.scrolltest.Draw;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;

public class MainActivity extends Activity {
    Draw draw;

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

    draw = new Draw(this);
    draw.setBackgroundColor(Color.WHITE);
    setContentView(draw);
    }
}

and

package com.example.scrolltest;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;


public class Draw extends View {
    Paint paint = new Paint();

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

    @Override
    public void onDraw(Canvas canvas) {

        paint.setColor(Color.GREEN);
        canvas.drawRect(30, 30, 90, 200, paint);
        paint.setColor(Color.BLUE);

        canvas.drawLine(100, 20, 100, 1900, paint);

        paint.setColor(Color.GREEN);
        canvas.drawRect(200, 2000, 400, 3000, paint);
        }
}

What I cannot figure out though, is how I use a scrollable to scroll down to the rectangles that are off of the screen. I'm also unsure if I started this correctly or should use drawables instead...

2条回答
Emotional °昔
2楼-- · 2019-03-11 12:08

Simple Method (If the height required is not very large).

Use a ScrollView and add your Draw view in it. Compute the required height for that view in onMeasure.

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

draw = new Draw(this);
draw.setBackgroundColor(Color.WHITE);
ScrollView scrollView = new ScrollView(this);
scrollView.addView(draw);
setContentView(scrollView);
}

public class Draw extends View {
        Paint paint = new Paint();

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

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // Compute the height required to render the view
            // Assume Width will always be MATCH_PARENT.
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = 3000 + 50; // Since 3000 is bottom of last Rect to be drawn added and 50 for padding.
            setMeasuredDimension(width, height);
        }

        @Override
        public void onDraw(Canvas canvas) {

            paint.setColor(Color.GREEN);
            canvas.drawRect(30, 30, 90, 200, paint);
            paint.setColor(Color.BLUE);

            canvas.drawLine(100, 20, 100, 1900, paint);

            paint.setColor(Color.GREEN);
            canvas.drawRect(200, 2000, 400, 3000, paint);
            }
    }
查看更多
劳资没心,怎么记你
3楼-- · 2019-03-11 12:21

An alternative can be to use offset X/Y values.

How you handle the offset values is up to you, all though I prefer using a class I call Camera. The access should be static.

public void render(Canvas c){
    c.drawRect(100 - offsetX, 100 - offsetY, 120 - offsetX, 120 - offsetY, Paints.BLUE);

}

And to pan the canvas:

float x, y;
@Override
public boolean onTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_DOWN){
        x = ev.getX();
        y = ev.getY();
    }else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
        float currX = ev.getX();
        float currY = ev.getY();

        float newOffsetX = (x   - currX),
                newOffsetY = (y - currY);
        //The next two if-statements are to add sensitivity to the scrolling.
        //You can drop these if you don't plan on having touch events
        //with pressing. Pressing works without this as well, but the canvas may move slightly 
        //I use DP to handle different screen sizes but it can be replaced with pixels. c is a context-instance
        if (newOffsetY < Maths.convertDpToPixel(2, c) && newOffsetY > -Maths.convertDpToPixel(2, c))
            newOffsetY = 0;

        if (newOffsetX < Maths.convertDpToPixel(2, c) && newOffsetX > -Maths.convertDpToPixel(2, c))
            newOffsetX = 0;
        offsetX += newOffsetX;
        offsetY += newOffsetY;
        x = ev.getX();
        y = ev.getY();
    }

    return true;

}

And a sample Camera-class:

public class Camera{

    public static float offsetX, offsetY;
    //The constructor. ix and iy is the initial offset. Useful if you are creating a game and need to change the initial offset to center around a starting position.
    //Most of the time it will be enough to set the values to 0
    public Camera(float ix, float iy){
        this.offsetX = ix;
        this.offsetY = iy;
    }

}
查看更多
登录 后发表回答