I have had pinch-to-zoom working pretty well for a few application versions now using Mike Ortiz's TouchImageView and a custom TouchViewPager so that it can work inside a view pager. This was working pretty well - the only method that TouchViewPager overrides is onInterceptTouchEvent:
public boolean onInterceptTouchEvent(MotionEvent ev) {
TouchImageView view = getTouchView();
if( null != view ){
if( !view.isAtLimit() && view.getCurrentScale() > 1){
return false;
} else {
Log.v(TAG, "View limit = " + view.isAtLimit());
Log.v(TAG, "View scale = " + view.getCurrentScale());
}
}
return super.onInterceptTouchEvent(ev);
}
Recently, we created a new view that extends FrameLayout and manages downloading the bitmap from the internet. While the bitmap download is in progress, this layout shows a ProgressBar, and then adds the TouchImageView once it's ready. Visually, this seems to work - we see a ProgressBar and then the TouchImageView loads - but the touches get all messed up and generally just don't work consistently.
I've tried delegating onTouchEvent from the progress image view to the touch image view - no luck. I've also tried overriding onInterceptTouchEvent in the progress image view to always return false, and that didn't work either.
Can anyone help with some ideas on where to continue debugging this problem? I've been working on this for about a week now, with little success.
This is the behavior I am seeing:
Activity loads, image is visible inside the ViewPager -> FrameLayout -> TouchImageView.
ViewPager works to swipe and change images, but pinch zoom does not
work on first or second images. Eventually you get to a position
that pinch zoom will work on. I can't figure out what's different.
If you do try to pinch zoom before swiping, then the ViewPager swipes won't work,
and it will take several swipes to get the ViewPager to page.
Once you are on a "working position", swipe-to-page works fine, as
well as pinch zoom and panning around a zoomed image. After this all positions work.
I was able to get this working, strangely, by implementing a reset method inside the TouchImageView class:
public void resetZoom() {
Matrix imageMatrix = getImageMatrix();
RectF drawableRect = new RectF(0, 0, bmWidth, bmHeight);
RectF viewRect = new RectF(0, 0, getWidth(), getHeight());
imageMatrix.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER);
matrix = imageMatrix;
setImageMatrix(matrix);
fitAndCenterView();
invalidate();
}
This method resets the zoom on the image when called - and when we set up the image inside the framelayout calling this gets everything set up. Oddly, this is not required when the image is not inside a FrameLayout.
I'd of course appreciate any code cleanup and whatnot that we can do.
Related
I am trying to follow the tutorial from https://www.youtube.com/watch?v=X2ls3FTPOAc in order to zoom into my drawable which lies inside an imageview.
I see here that the scaleListener object is called since the Log outputs that it's being called. However, when I use the ImageView.ScaleType.MATRIX scale type for the image-view it doesn't seem to work. I've also added a slight modification in the onScale method. Here's my code:
view1.setScaleType(ImageView.ScaleType.MATRIX); //using CENTER works
Matrix matrix = new Matrix();
view1.setImageMatrix(matrix);
DummyDraw drawD = new DummyDraw(this);
view1.setImageDrawable(drawD);
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
a few methods down I have:
public boolean onTouchEvent(MotionEvent event){
scaleGestureDetector.onTouchEvent(event);
return true; }
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
public boolean onScale(ScaleGestureDetector detector) {
Log.d("inside1044","inside scale listener onSscale method ");
float scaleFactor = detector.getScaleFactor();
scaleFactor = Math.max(0.1f, Math.min(scaleFactor,5.0f));
scaleFactor2 = scaleFactor;//a modification I made
matrix.setScale(scaleFactor,scaleFactor);
view1.invalidateDrawable(view1.getDrawable());//this is the modification I made
return true;}
}
Now, the DummyDraw class extends Drawable which just draws a pink background and a circle in the middle of the canvas. the variable scaleFactor2 is static and defined in the MainActivity, so everytime the user touches the view the method onScale is called updating the new value for scaleFactor2. I use this scaleFactor2 value somehow to try to scale the canvas of my drawable object. Here's what the code looks like inside the drawable object, inside the onDraw():
//define some variables to get the center of the canvas
canvas.save();
canvas.scale(MainActivity.scaleFactor2,MainActivity.scaleFactor2);
//draw the circle to the screen
canvas.restore();
When I place both fingers on the screen and bring them closer together the circle jumps and moves slightly, however when I move the fingers apart nothing happens. Remember, that this works only when the ImageView.ScaleType is set to CENTER not to MATRIX.
I am trying to accomplish a smooth zoom in and out of the view.
Any ideas would be appreciated.
I am using the photoview library of chris banes to handle all zoom events for my gallery.
I want to detect if the image has been zoomed in or has been zoomed out by the user without overriding all those double tap,and touch event methods.Is there an efficient way to achieve this?
To answer my own question:-
I found a much better way to implement zoom detection which works for pinch zoom as well as double tap(any zooming event, without having to override each method).Found it nowhere on the web to detect a zoom event.If there's a much more efficient way, please let me know :)
(Also I am using chris banes photoview library to handle the zooming.)
So to detect a zooming event,get the rectangle of the current photoview and when a zoom event happens , OnMatrixChangeListener gets called and there you compare the rectangles to see .
(Now simply using this listener to handle a zooming event won't work, because the listener gets called, everytime you change your image,(in case you're using it in a gallery) ,it also gets called when the orientation of the screen is changed and also when simple no-zoom intending touches are made to the screen.)
Also, when the screen orientation changes,sometimes the photoview returns a 0 rectangle,so you have to check for that as well,here's my code:-
if (savedInstanceState.getBoolean(Constants.ZOOM)) {
photoViewAttacher = new PhotoViewAttacher(backgroundImage);
mWindowRect = new RectF(photoViewAttacher.getDisplayRect());
photoViewAttacher.setOnMatrixChangeListener(new PhotoViewAttacher.OnMatrixChangedListener() {
#Override
public void onMatrixChanged(RectF rect) {
//need to differentiate between screen orientation
if (mWindowRect.left == 0 && mWindowRect.top == 0 && mWindowRect.right == 0 && mWindowRect.bottom == 0) {
mWindowRect = new RectF(rect);
}
if (Math.abs(mWindowRect.left - rect.left) < 2 && Math.abs(mWindowRect.top - rect.top) < 2 && Math.abs(mWindowRect.right - rect.right) < 2 && Math.abs(mWindowRect.bottom - rect.bottom) < 2) {
viewPager.setLocked(false);
thumbnailsContainer.startAnimation(appear);
thumbnailsContainer.setClickable(true);
} else {
viewPager.setLocked(true);
thumbnailsContainer.startAnimation(disappear);
thumbnailsContainer.setClickable(false);
}
Log.i("ZOOM", "default rect: " + mWindowRect);
Log.i("ZOOM", "zoom rect: " + rect);
}
});
}
I can see a bunch of listeners, that can be notified, when a certain event on a PhotoView occurrs.
Refer to:
IPhotoView
Here I can see the listeners:
void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener);
void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener);
void setOnSingleFlingListener(PhotoViewAttacher.OnSingleFlingListener onSingleFlingListener);
Hope this helps.
I have a ViewPager with a custom PagerAdapter that displays a set of fragments.
These fragments are (purposely) positioned on top of each other so that I can use a PageTransformer that makes it look as if the user is sliding the fragments from a stack (almost like a deck of cards).
The issue is that each fragment has their own Views/Widgets (e.g. a seekbar) which, due to the overlapping, are occupying the same coordinates and sometimes the touch event is caught by the fragment bellow the current one (e.g. the user adjusts a seekbar's position, but instead of updating the currently shown seekbar, it's the seekbar in the next fragment that's gets its progress updated).
I've come across this answer but it's not the same exact problem.
Has anyone ever found a similar issue? What's the smartest way (except for the lazy solution: change the PageTransformer to one that doesn't overlap the fragments) of dealing with this issue?
EDIT:
In my Fragment class I have:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment, container, false);
rootView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
}
as suggested by Zsombor Erdődy-Nagy, but this doesn't help: it's still possible for the widget bellow the current fragment to receive the event instead of the current one's.
I've also looked at this open issue, with no success.
If you are still looking for the solution, than you should look at this:
Issue 58918.
Here you can find the answer to your problem. Quoting from the link:
If I remember right it was after 4.1 that the framework respects a
custom child drawing order as implied Z-ordering for dispatching touch
events. If your views overlap after this page transformation they may
not receive touch events in the expected order on older platform
versions. Check which view is receiving the touch events to be
certain.
If this is what you are seeing you have a few options:
Enforce the desired ordering as you add/remove child views in your PagerAdapter
Remove the X translation applied by the PageTransformer when a page is no longer fully visible - i.e. the "position" parameter reports a full -1 or 1.
Example:
this.viewPager.setPageTransformer(true, new PageTransformer() {
#Override
public void transformPage(View page, float position) {
float translationX;
float scale;
float alpha;
if (position >= 1 || position <= -1) {
// Fix for https://code.google.com/p/android/issues/detail?id=58918
translationX = 0;
scale = 1;
alpha = 1;
} else if (position >= 0) {
translationX = -page.getWidth() * position;
scale = -0.2f * position + 1;
alpha = Math.max(1 - position, 0);
} else {
translationX = 0.5f * page.getWidth() * position;
scale = 1.0f;
alpha = Math.max(0.1f * position + 1, 0);
}
ViewHelper.setTranslationX(page, translationX);
ViewHelper.setScaleX(page, scale);
ViewHelper.setScaleY(page, scale);
ViewHelper.setAlpha(page, alpha);
}
});
In this case all your fragments must have a background View that stops TochEvents propagating to fragments in the back.
I'm guessing that you already have opaque backgrounds for these fragments, or else the fragments in the back would show through the fragment currently on the top of the stack. So try setting a ClickListener for all your fragment's root ViewGroup instance that does nothing, it just catches the touch events.
In case anyone runs into this issue (which can happen not only with ViewPager), it is just a matter of adding this attribute to the layout at the top of your hierarchy:
android:clickable="true"
This way it will handle the clicks, not doing really anything with them, but at least avoiding them to reach the fragment in the background.
I have a custom View where I implement onDraw and it successfully draws the background as well as a small overlay image at the top right. I animate that overlay image (AnimationSet with a ScaleAnimation and Rotate Animation) manually by doing:
final AnimationSet animationSet = new AnimationSet(false);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(rotationAnimation);
animationSet.setFillAfter(true);
post(new Runnable() {
#Override
public void run () {
animationSet.start();
new Handler().post(getAnimationRunnable(animationSet));
}
private Runnable getAnimationRunnable (final AnimationSet animationSet) {
return new Runnable() {
#Override
public void run () {
final Transformation outTransformation = new Transformation();
if (animationSet.getTransformation(System.currentTimeMillis(), outTransformation)) {
// store matrix for applying in onDraw
mMatrix = outTransformation.getMatrix();
// THIS INVALIDATE DOESN'T WORK
parent.invalidate(new Rect(getParentInvalidateRect()));
post(getAnimationRunnable(animationSet));
}
}
};
}
});
If I invalidate the custom View like this ...
// THIS INVALIDATE DOES WORK
invalidate(getOverlayClientRect());
then the animation happens and all is great, except that the animated image now gets clipped - because it's being scaled up, beyond the top and right bounds.
But when I pass a Rect to parent.invalidate() that intersects the custom View's top right corner area, it does not trigger an onDraw for the custom View.
So ...
will this approach work?
if so, is there any other factor than the Rect passed to parent.invalidate() that determines whether it will call the custom Views onDraw method?
After a lot of searching, the answer was obvious. I just had to set the clipChildren property of the parent to false. Didn't need to invalidate the parent, just the custom View.
That worked fine on a 4.0.3 tablet. However, on a Galaxy Nexus (4.2.2), not only did it render outside of the bounds during the animation, it actually only cleared the background of the Rect passed to invalidate. So, when the animation was done some remnants of the image were still visible outside the Rect. The solution there was to pass a Rect large enough to cover the largest of the drawn bitmaps during animation.
I have a problem scrolling my childscene. I have created a CameraScene which i am trying to scroll with a touch event. My childscene is not scrolling, however, if i scroll on the camera attached to the engine the parent scene scrolls fine.
So how do i get my child scene to scroll without the objects attached to myparents scene scrolls along?
public StatsScene(Context context, VertexBufferObjectManager vbo) {
super(new SmoothCamera(0, 0, WITDH, HEIGHT, 0, SPEEDY, 0));
this.setOnSceneTouchListener(new IOnSceneTouchListener() {
#Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if(pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
mTouchY = pSceneTouchEvent.getMotionEvent().getY();
}
else if(pSceneTouchEvent.getAction() == MotionEvent.ACTION_MOVE) {
float newY = pSceneTouchEvent.getMotionEvent().getY();
mTouchOffsetY = (newY - mTouchY);
float newScrollX = getCamera().getCenterX();
float newScrollY = getCamera().getCenterY() - mTouchOffsetY;
getCamera().setCenter(newScrollX, newScrollY);
mTouchY = newY;
}
return true;
}
});
}
I'm not really into AndEngine and I'm not sure if I get your problem right (in your code is nothing about "myparents" or "childscene"), but when something is attached to your scene, then this implies it will move with it. You could scroll your children in the other direction to maintain their position, but that could get you into trouble in the longterm. If it is possible, try to seperate your scrolling scene and your objects, meaning, that they shouldn't be children of each other. Instead, if you want them to keep them related, give them a common parent. If you move one object now, the siblings won't. Hope that helps.
From your description I would think that your parent scene is the one getting your the input so I'm guessing, please correct me if I'm wrong, that you are setting your child scene something like this:
mMainScene.attachChild(mChildScene);
In this case you will have to deal with deviating the input to the child instead of the parent. However, you have a few options here:
If your child scene occupies full screen and you don't need to worry about updating/drawing your parent scene, simply swap scenes with
mEngine.setScene(mChildScene);
If you do need to keep drawing and updating your parent scene check the MenuScene pre-made class and Scene.setChildScene() method, there is one example on how using this in the AndengineExamples project I think. Using this class will let you take the input on the child scene but still drawing and updating your main scene, it even let's you set your child in a modal way.