【问题标题】:How to create zoomable & pannable view with many draggable views on top of it?如何创建具有许多可拖动视图的可缩放和可平移视图?
【发布时间】:2013-02-09 20:20:43
【问题描述】:

我正在尝试在 Android 中创建一个board game,其中包括一个上面有许多瓷砖的棋盘,可以在棋盘周围拖动,也可以从棋盘拖到棋盘。这与Wordfeud 游戏非常相似。

棋盘有固定的尺寸。我希望用户能够通过捏合来缩放,并在板上平移,并在板上拖动图块。放大/缩小时,图块必须与板一起缩放。

我正在努力寻找正确的设置方法。我想过并尝试了两种方法:

  1. HorizontalScrollViewScrollViewRelativeLayout 结合使用作为孩子。这个RelativeLayout 然后包含所有图块。这工作正常,但我将如何实现捏缩放?
  2. 使用此示例缩放和平移视图:http://android-developers.blogspot.nl/2010/06/making-sense-of-multitouch.html。但是,我将如何在此视图顶部添加与此视图一起缩放和平移的图块?

这两个选项似乎都不是正确的解决方案。我有兴趣了解其他 Android 开发人员将如何设置它,并希望他们为我提供正确的方向。

【问题讨论】:

    标签: android pinchzoom


    【解决方案1】:

    好的,首先,我建议忘记第一个解决方案,这不是很简单。 第二个是一个好的开始。

    这是我的解决方案:

    活动类

    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);       
    }
    

    InteractiveView 类只是一个简单的RelativeLayout,它会对捏和拖动做出反应,并将容纳更多元素:

    InteractiveView 类

    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);
        }
    }
    

    然后,我们有一个“元素”类(称为 BoardTile),它将在这个 InteractiveView 上创建图块。这个类比较复杂,因为视图不会占据整个屏幕,我们必须测试触摸事件是否在对象的边界内。

    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;
                }
            }
        };
    }
    

    这不是一个完整的解决方案,您还必须在图块上调度触摸事件,以便它们考虑到 InteractiveView 的规模。

    希望它能帮助你开始!

    【讨论】:

    • 感谢您的回答,我将测试您的一些建议。我也给你一个建议:当你使用mInteractiveView.addView(lBoardTile, params) 而不是mInteractiveView.addView(lBoardTile); 时,你可以在params 中定义视图的大小和位置,这样你就不必测试触摸是否在目的。在这种情况下,paramsRelativeLayout.LayoutParams 的一个实例
    【解决方案2】:

    感谢@PopGorn,他为我指明了正确的方向。我四处寻找一些例子,其中包括触摸事件的调度。我最终使用了这个不错的答案:https://stackoverflow.com/a/12497670/757073

    【讨论】:

    • 你好@spes 我正在尝试完成同样的事情,你能帮我一下吗。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-30
    • 2016-03-23
    相关资源
    最近更新 更多