jjzjj

android - 如何在其顶部创建具有许多可拖动 View 的可缩放和平移 View ?

coder 2023-12-27 原文

我正在尝试在 Android 中创建一个棋盘游戏,其中包括一个棋盘,上面有许多方 block ,可以在棋盘周围拖动,也可以在玩家的架子之间拖动。这与 Wordfeud 游戏非常相似。

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

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

  1. Horizo​​ntalScrollViewScrollView 结合使用,将 RelativeLayout 作为 subview 。此 RelativeLayout 然后包含所有图 block 。这工作正常,但我将如何实现双指缩放?
  2. 使用此示例缩放和平移 View :http://android-developers.blogspot.nl/2010/06/making-sense-of-multitouch.html .但是,我该如何在此 View 之上添加随此 View 一起缩放和平移的图 block ?

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

最佳答案

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

这是我的解决方案:

Activity 类

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,它对捏合和拖动使用react,并将容纳更多元素:

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 上的图 block 。这个类比较复杂,因为 View 不会占据整个屏幕,我们将不得不测试触摸事件是否在对象的边界内。

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

这不是一个完整的解决方案,您还必须分派(dispatch)磁贴上的触摸事件,以便它们考虑到 InteractiveView 的比例。

希望它能帮助你开始!

关于android - 如何在其顶部创建具有许多可拖动 View 的可缩放和平移 View ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14750476/

有关android - 如何在其顶部创建具有许多可拖动 View 的可缩放和平移 View ?的更多相关文章

  1. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  4. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  5. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  6. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  7. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  8. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  9. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  10. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

随机推荐