How do I create a scalable and customizable view with many drag-and-drop views on top of it?

I am trying to create board game

on Android that includes a board with many fragments on top of it that can be dragged around the board and from and to a turntable stand. It's a lot like a game Wordfeud

.

The board has a fixed size. I want the user to be able to pinch to zoom in and move around the board and drag tiles around the board. Plates need to scale with the board when scaling / outputting.

I am struggling to find the right way to set it up. I thought and tried two ways:

  • Used HorizontalScrollView

    in conjunction with ScrollView

    with RelativeLayout

    as a child. This one RelativeLayout

    then contains all the tiles. This works fine, but how can I implement scaling to zoom in?
  • Using this example to zoom and pan a view: http://android-developers.blogspot.nl/2010/06/making-sense-of-multitouch.html . But how could I add tiles on top of this view that zoom in and out with this view?

Both options don't seem to be the right solution. I am curious to see how other Android developers will install this and hope they provide me with the right direction.

+3


source to share


2 answers


Thanks to @PopGorn who pointed me in the right direction. I looked at several examples that involved dispatching touch events. I got this good answer: fooobar.com/questions/440441 / ...



+1


source


OK, firstly, I would recommend forgetting about the first solution, which is not very easy. The second is a good start.

Here is my solution:

Action class

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;

public class MainActivity extends Activity {

    private RelativeLayout mMainLayout;
    private InteractiveView mInteractiveView;

    private int mScreenWidth;
    private int mScreenHeight;

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

        // Set fullscreen mode
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.activity_main);  

        // Retrieve the device dimensions to adapt interface
        mScreenWidth = getApplicationContext().getResources()
                .getDisplayMetrics().widthPixels;
        mScreenHeight = getApplicationContext().getResources()
                .getDisplayMetrics().heightPixels;        

        mMainLayout = (RelativeLayout)findViewById(R.id.mainlayout);

        // Create the interactive view holding the elements
        mInteractiveView = new InteractiveView(this);
        mInteractiveView.setLayoutParams(new RelativeLayout.LayoutParams(-2,-2 ));
        mInteractiveView.setPosition(-mScreenWidth/2, -mScreenHeight/2);

        mMainLayout.addView(mInteractiveView);

        // Adding a background to this view
        ImageView lImageView = new ImageView(this);
        lImageView.setLayoutParams(new RelativeLayout.LayoutParams(-1,-1));
        lImageView.setImageResource(R.drawable.board);

        mInteractiveView.addView(lImageView);

        // Adding a tile we can move on the top of the board
        addElement(50, 50);     
    }

    // Creation of a smaller element
    private void addElement(int pPosX, int pPosY) {

        BoardTile lBoardTile = new BoardTile(this);
        Bitmap lSourceImage = BitmapFactory.decodeResource(getResources(), R.drawable.tile);
        Bitmap lImage = Bitmap.createScaledBitmap(lSourceImage, 100, 100, true);
        lBoardTile.setImage(lImage);        
        Point lPoint = new Point();
        lPoint.x = pPosX;
        lPoint.y = pPosY;
        lBoardTile.setPosition(lPoint);

        mInteractiveView.addView(lBoardTile);       
}

      

The InteractiveView class is just a RelativeLayout that reacts to pinch and drag and will contain additional elements:

InteractiveView class



import android.content.Context;
import android.graphics.Canvas;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;

public class InteractiveView extends RelativeLayout{

    private float mPositionX = 0;
    private float mPositionY = 0;
    private float mScale = 1.0f;

    public InteractiveView(Context context) {
        super(context);     
        this.setWillNotDraw(false);
        this.setOnTouchListener(mTouchListener);
    }   

    public void setPosition(float lPositionX, float lPositionY){
        mPositionX = lPositionX;
        mPositionY = lPositionY;
    }

    public void setMovingPosition(float lPositionX, float lPositionY){
        mPositionX += lPositionX;
        mPositionY += lPositionY;
    }

    public void setScale(float lScale){ 
        mScale = lScale;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {        
        canvas.save();      
        canvas.translate(getWidth() / 2, getHeight() / 2);  
        canvas.translate(mPositionX*mScale, mPositionY*mScale);     
        canvas.scale(mScale, mScale);
        super.dispatchDraw(canvas);
        canvas.restore();
    }   

    // touch events
    private final int NONE = 0;
    private final int DRAG = 1;
    private final int ZOOM = 2;
    private final int CLICK = 3;

    // pinch to zoom
    private float mOldDist;
    private float mNewDist;
    private float mScaleFactor = 0.01f;

    // position
    private float mPreviousX;
    private float mPreviousY;

    int mode = NONE;

    @SuppressWarnings("deprecation")
    public OnTouchListener mTouchListener = new  OnTouchListener(){
        public boolean onTouch(View v, MotionEvent e) {
            float x = e.getX();
            float y = e.getY();
            switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN: // one touch: drag            
                mode = CLICK;
                break;
            case MotionEvent.ACTION_POINTER_2_DOWN: // two touches: zoom            
                mOldDist = spacing(e);          
                mode = ZOOM; // zoom
                break;
            case MotionEvent.ACTION_UP: // no mode          
                mode = NONE;
                break;
            case MotionEvent.ACTION_POINTER_2_UP: // no mode
                mode = NONE;            
                break;
            case MotionEvent.ACTION_MOVE: // rotation
                if (e.getPointerCount() > 1 && mode == ZOOM) {
                    mNewDist = spacing(e) - mOldDist;   

                    mScale += mNewDist*mScaleFactor;
                    invalidate();

                    mOldDist = spacing(e);  

                } else if (mode == CLICK || mode == DRAG) {
                    float dx = (x - mPreviousX)/mScale;
                    float dy = (y - mPreviousY)/mScale;

                    setMovingPosition(dx, dy);
                    invalidate();
                    mode = DRAG;                        
                }
                break;
            }
            mPreviousX = x;
            mPreviousY = y;
            return true;
        }
    };

    // finds spacing
    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return FloatMath.sqrt(x * x + y * y);
    }
}

      

Then we have an "element" class (called BoardTile) that will create the fragments that go into this InteractiveView. This class is more complex because the view does not render the entire screen and we will need to check if the touch event is within the bounds of the object.

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.MotionEvent;
import android.view.View;

public class BoardTile extends View
{
    private Bitmap mCardImage;
    private final Paint mPaint = new Paint();
    private final Point mSize = new Point();
    private final Point mStartPosition = new Point();
    private Region mRegion;

    public BoardTile(Context context)
    {
        super(context);
        mRegion = new Region();
        this.setOnTouchListener(mTouchListener);
    }

    public final Bitmap getImage() { return mCardImage; }
    public final void setImage(Bitmap image)
    {
        mCardImage = image;
        setSize(mCardImage.getWidth(), mCardImage.getHeight());
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        Point position = getPosition();
        canvas.drawBitmap(mCardImage, position.x, position.y, mPaint);
    }

    public final void setPosition(final Point position)
    {
        mRegion.set(position.x, position.y, position.x + mSize.x, position.y + mSize.y);
    }

    public final Point getPosition()
    {
        Rect bounds = mRegion.getBounds();
        return new Point(bounds.left, bounds.top);
    }

    public final void setSize(int width, int height)
    {
        mSize.x = width;
        mSize.y = height;

        Rect bounds = mRegion.getBounds();
        mRegion.set(bounds.left, bounds.top, bounds.left + width, bounds.top + height);
    }

    public final Point getSize() { return mSize; }

    public OnTouchListener mTouchListener = new  OnTouchListener(){
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            // Is the event inside of this view?
            if(!mRegion.contains((int)event.getX(), (int)event.getY()))
            {
                return false;
            }

            if(event.getAction() == MotionEvent.ACTION_DOWN)
            {
                mStartPosition.x = (int)event.getX();
                mStartPosition.y = (int)event.getY();
                bringToFront();
                return true;
            }
            else if(event.getAction() == MotionEvent.ACTION_MOVE)
            {
                int x = 0, y = 0;

                x = (int)event.getX() - mStartPosition.x;            
                y = (int)event.getY() - mStartPosition.y;            

                mRegion.translate(x, y);
                mStartPosition.x = (int)event.getX();
                mStartPosition.y = (int)event.getY();

                invalidate();

                return true;
            }
            else
            {   
                return false;
            }
        }
    };
}

      

This is not a complete solution, you will also have to send touch events to the tiles to take into account the InteractiveView scale.

Hope this helps you get started!

+3


source







All Articles