scaling around mid point of two fingers in android

2019-05-20 05:36发布

I have a HorizontalScrollView in which there are multiple views inside it. I have implemented pinch zoom gesture in which multiple views, which are between my two fingers, are scaled.

But I am facing one glitch. When I am doing pinch zoom, the mid point of pinch zoom is moving but for user experience I want this point to be remain fixed and other things should adjust itself while scaling so that mid point remains static.

Can someone tell me how to do it.

onDraw() method of custom view is

                Rect r =new Rect(0, 0, 700, 40);

    public void onDraw(Canvas canvas) {

        float kk=mScaleFactor;    //this variable will be set by pinch zoom event and represent how much to scale
        sf*=kk;                   // this view must scale according to previous scaling  


        canvas.save();
        canvas.scale(sf , 1);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLUE); 
        canvas.drawRect(r, paint);
        width=sf*700;
        canvas.restore();
        requestLayout();                               //this will change view width to fit the expanded rectangle 
        }

onMeasure method is called on requestLayout

        @Override protected void onMeasure(int widthMeasureSpec,
              int heightMeasureSpec) {
            setMeasuredDimension((int)width, 340);
          }

The above custom view is called 12 times by a subclass of HorizontalScrollView. So there are 12 child views of HorizontalScrollview.

In this class I am doing the following things

  1. Detecting the touch coordinates of two fingers.

  2. Calculating the index of child view on which first finger is touched.

  3. Calculating the index of child view on which second finger is touched.

  4. Passing scale factor of pinch zoom to all the child views between start and last. These two indices are calculated in previous two step.

  5. And finally invalidate() is called on the child view. So child can scale itself according to scale factor passed by parent view.

But there is one problem here. The mid point of two finger should remain static and other things should adjust during scaling. But my mid point is moving with scaling.

Can someone help me in doing this. Please tell me if any part of code is require.

Code of gesture listener of HorizontalScrollview is

      private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
               mScaleFactor *= detector.getScaleFactor();
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

            ViewGroup pp=takeparent(); //give object of this class

            for(int i=start;i<=last;i++)
            {
                LinearLayout ll=(LinearLayout)pp.getChildAt(i);
                DrawView view=(DrawView)ll.findViewById(R.id.drawview);
                view.mScaleFactor=mScaleFactor;
                view.invalidate();
                view.donesf=true;
            }

Sample app of mine enter image description here

Edits as suggested in comments:

        private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
               mScaleFactor *= detector.getScaleFactor();
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

            ViewGroup pp=takeparent(); pp contains all the custom child views

            //start and last are indices of range of child for which we have to apply gesture
            for(int i=start;i<=last;i++)
            {
                LinearLayout ll=(LinearLayout)pp.getChildAt(i);
                DrawView view=(DrawView)ll.findViewById(R.id.drawview);

                view.mScaleFactor=mScaleFactor;
                view.pivotx=detector.getFocusX()+view.getLeft();
                view.pivoty=detector.getFocusY()+view.getTop();
                view.invalidate();
            }




            return true;
        } 

This is custom view onDraw method

       public void onDraw(Canvas canvas) {

        sf=mScaleFactor  //this scale factor is set by parent view    
        width=sf*700;  //this width will be use to rescale the view's width in requestLayout()

        canvas.save();

        canvas.scale(sf,1,pivotx,pivoty);  //pivotX and pivotY are also set by parent's onScale method of gestureListener
        canvas.drawRect(r, paint);
        requestLayout();
        canvas.restore();

        } 

enter image description here

1条回答
一纸荒年 Trace。
2楼-- · 2019-05-20 06:08

For a view you are scaling, you can set the "invariant" point that should not move using setPivotX() and setPivotY(). When I use a ScaleGestureDetector, I use the focus point for this. For example:

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector)
    {
        ...
        mScaledView.setPivotX(detector.getFocusX());
        mScaledView.setPivotY(detector.getFocusY());
        ...

I'm not sure if you want to do this with all of the children, or just the parent view, in your case, but usually you just need to do this to a single "parent" view and it will work properly for the children of that view.

Related to this, I didn't quite understand why you're passing the scaling factor down to each child when scaling the parent view will scale all of its children too. Maybe you just need to add a single FrameLayout (or some other ViewGroup descendent) into your HorizontalScrollView that hosts the children and then just scale that (after setting its pivot appropriately)?


Updated re comment

Given that you only want to scale the views in the pinched region between the fingers, I believe you have a couple options, depending on your app:

1) Dynamically add just those views to an intermediate FrameLayout which gets scaled and has its pivot set, leaving the non-scaled views as direct children of the HorizontalScrollView; or

2) Passing down the focus point to each scaled child view after first adjusting for the child's position. For example, assuming your DrawView either directly or indirectly inherits from android.view.View, which I would strongly recommend, then you can do something like:

    for (int i = start; i <= last; i++) {
        LinearLayout ll = (LinearLayout)pp.getChildAt(i);
        DrawView view = (DrawView)ll.findViewById(R.id.drawview);

        view.setScaleX(mScaleFactor);
        view.setScaleY(mScaleFactor);
        view.setPivotX(detector.getFocusX() - view.getLeft());    // Note: minus, not plus...
        view.setPivotY(detector.getFocusY() - view.getTop());
        view.invalidate();
    }
查看更多
登录 后发表回答