构建可缩放/可扩展的容器

时间:2012-05-02 15:24:13

标签: android zoom viewgroup android-framelayout panning

我正在尝试创建一个支持平移和缩放其内容的ViewGroup。我在网上找到的只是在ImageView中实现的想法和实现,但绝不是容器。我想显示一个地图,在它上面我想显示多个标记,这些标记是ImageButtons,因此用户可以点击它们以获取更多信息。这是通过UIScrollView在iOS上实现的,但我在Android上找不到替代方案。

我决定使用FrameView,所以我可以将图像设置为背景图像,并在其上添加一个RelativeLayout,我可以在其上添加ImageButtons并使用边距定位它们。

我借用了TouchImageView here的部分实现,但我遇到了并发症。我已经开始进行平移,而且我部分成功了,它将容器放在一边,但是平移工作很可怕,它非常抖动。这是我的代码:

public class ScrollingViewGroup extends FrameLayout {

    private int x = 0;
    private int y = 0;

    // We can be in one of these 3 states
    static final int NONE = 0;
    static final int DRAG = 1;
    static final int ZOOM = 2;
    int mode = NONE;

    // Remember some things for zooming
    PointF last = new PointF();
    PointF start = new PointF();

public ScrollingViewGroup(Context context) {
        super(context);
        sharedConstructing(context);
    }

    public ScrollingViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        sharedConstructing(context);
    }

    private void sharedConstructing(Context context) {
        super.setClickable(true);
        this.context = context;
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());

    setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                mScaleDetector.onTouchEvent(event);

                PointF curr = new PointF(event.getX(), event.getY());

                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    last.set(event.getX(), event.getY());
                    start.set(last);
                    mode = DRAG;
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode == DRAG) {
                        float deltaX = curr.x - last.x;
                        float deltaY = curr.y - last.y;
                        Log.d("ScrollingViewGroup", Float.toString(deltaX));
                        Log.d("ScrollingViewGroup", Float.toString(deltaY));
                        float scaleWidth = Math.round(origWidth * saveScale);
                        float scaleHeight = Math.round(origHeight * saveScale);


                        x += deltaX;
                        y += deltaY;



                        last.set(curr.x, curr.y);

                    }
                    break;

                case MotionEvent.ACTION_UP:
                    mode = NONE;
                    int xDiff = (int) Math.abs(curr.x - start.x);
                    int yDiff = (int) Math.abs(curr.y - start.y);
                    if (xDiff < CLICK && yDiff < CLICK)
                        performClick();
                    break;

                case MotionEvent.ACTION_POINTER_UP:
                    mode = NONE;
                    break;
                }
                // setImageMatrix(matrix);
                setTranslationX(x);
                setTranslationY(y);
                invalidate();
                return true; // indicate event was handled
            }

        });
    }

非常感谢任何想法。

编辑:抖动似乎是原因,因为在移动时,deltaX和deltaY在正数和负数之间交替,检查LogCat ......仍然不确定原因。 这是由curr变量引起的,每次都给出不同的值,但是它们看起来好像是手指来回移动而不是向前移动而不是一致。例如,而不是curr.x是0,1,2,3,4等,它是0,1,0.5,2,1.5等。不知道为什么。

1 个答案:

答案 0 :(得分:0)

我有一个符合某些先决条件的解决方案:

  • 容器中的项目必须是ImageView
  • 类型
  • 所有图片需要具有相同的尺寸才能同等缩放

容器中的项目可以缩放和平移(但不能同时进行)。该代码还能够确定用户是否已点击而不是移动或缩放。 ImageView存储在FrameLayout中的ArrayList中,并进行缩放和移动。这段代码的某些部分取自Ed Burnette(Link)在ZDNet上的一篇非常好的文章,该文章取自真正优秀的Android书籍“Hello, Android”。

查看此代码。您可以在任何活动中将此类用作布局。您甚至应该能够在XML中使用它。现在有一个方法initializeViews()在构造函数中调用,您可以在其中硬编码在创建布局时应加载的图像视图。您应该在“ArrayList sampleBitmaps = new ArrayList();”行之后在此方法中添加一些位图。 对于实际使用,最好实现一个方法addImageView(ImageView项),您可以在其中动态添加视图。

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;


public class TouchContainer extends FrameLayout implements OnTouchListener {

    // constants
    private static final Config DEFAULT_COLOR_DEPTH = Bitmap.Config.ARGB_4444;
    private static final String TAG = "TouchContainer";

    // fields
    private ArrayList<ImageView> items;

    public TouchContainer(Context ctx) {
        this(ctx, null);
    }

    public TouchContainer(Context ctx, AttributeSet attrs) {
        super(ctx, attrs);

        initializeViews();  // initialize some sample Bitmaps
    }

    /**
     * This method is just to make an example 
     */
    protected void initializeViews() {
        ScaleType scaleType = ScaleType.MATRIX;

        // array needs to be created here if used in XML
        items = new ArrayList<ImageView>();


        ArrayList<Bitmap> sampleBitmaps = new ArrayList<Bitmap>();
        // here you should add some bitmaps to the Array that will then be displayed in the container
        // e.g. sampleBitmaps.add(blabla I'm a bitmap) :-)

        ImageView iv = null;
        boolean firstLoop = true;

        for (Bitmap bitmap : sampleBitmaps) {
            // Load the bitmaps into imageviews
            iv = new ImageView(getContext());
            iv.setImageBitmap(bitmap);
            iv.setScaleType(scaleType);
            if (firstLoop) {
                // add the touch listener to the first image view that is stored in the ArrayList
                iv.setOnTouchListener(this);
                firstLoop = false;
            }

            // add view to the FrameLayout
            this.addView(iv);

            // add the imageview to the array
            items.add(iv);
        }

    }



    protected void transformImages(Matrix matrix) {
        for (ImageView image : items) {
            image.setImageMatrix(matrix);
        }

    }




    Matrix matrix = new Matrix();
    Matrix savedMatrix = new Matrix();

    // states
    static final int NONE = 0;
    static final int DRAG = 1;
    static final int ZOOM = 2;
    int mode = NONE;
    static final int CLICK = 3;


    PointF start = new PointF();
    PointF mid = new PointF();
    float oldDist = 1f;

    public boolean onTouch(View v, MotionEvent event) {


      switch (event.getAction() & MotionEvent.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN:
         savedMatrix.set(matrix);
         start.set(event.getX(), event.getY());
         Log.d(TAG, "mode=DRAG");
         mode = DRAG;
         break;
      case MotionEvent.ACTION_POINTER_DOWN:
         oldDist = spacing(event);
         Log.d(TAG, "oldDist=" + oldDist);
         if (oldDist > 10f) {
            savedMatrix.set(matrix);
            midPoint(mid, event);
            mode = ZOOM;
            Log.d(TAG, "mode=ZOOM");
         }
         break;
      case MotionEvent.ACTION_UP:
          // figure out if user clicked
          mode = NONE;
          int xDiff = (int) Math.abs(event.getX() - start.x);
          int yDiff = (int) Math.abs(event.getY() - start.y);
          if (xDiff < CLICK && yDiff < CLICK)
              performClick(); 
          break;
      case MotionEvent.ACTION_POINTER_UP:
         mode = NONE;
         Log.d(TAG, "mode=NONE");
         break;
      case MotionEvent.ACTION_MOVE:
         if (mode == DRAG) {

            matrix.set(savedMatrix);
            matrix.postTranslate(event.getX() - start.x,
                  event.getY() - start.y);
         }
         else if (mode == ZOOM) {
            float newDist = spacing(event);
            Log.d(TAG, "newDist=" + newDist);
            if (newDist > 10f) {
               matrix.set(savedMatrix);
               float scale = newDist / oldDist;
               matrix.postScale(scale, scale, mid.x, mid.y);
            }
         }
         break;
      }


      transformImages(matrix);


      return true;
   }




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


       private void midPoint(PointF point, MotionEvent event) {
          float x = event.getX(0) + event.getX(1);
          float y = event.getY(0) + event.getY(1);
          point.set(x / 2, y / 2);
       }





}